#!/usr/bin/env python import argparse import sys # Usage: # ./generate_sample_functions.py --sampleutil_autogen_h ../src/sampleutil_autogen.h --channelmixer_autogen_cpp ../src/engine/channelmixer_autogen.cpp BASIC_INDENT = 4 COPY_WITH_GAIN_METHOD_PATTERN = 'copy%(i)dWithGain' def copy_with_gain_method_name(i): return COPY_WITH_GAIN_METHOD_PATTERN % {'i' : i} RAMPING_GAIN_METHOD_PATTERN = 'copy%(i)dWithRampingGain' def copy_with_ramping_gain_method_name(i): return RAMPING_GAIN_METHOD_PATTERN % {'i': i} def method_call(method_name, args): return '%(method_name)s(%(args)s)' % {'method_name': method_name, 'args': ', '.join(args)} def hanging_indent(base, groups, hanging_suffix, terminator, depth=0): return map(lambda line: ' ' * BASIC_INDENT * depth + line, map(''.join, zip( [base] + [' ' * len(base)] * (len(groups) - 1), groups, [hanging_suffix] * (len(groups) - 1) + [terminator]))) def write_channelmixer_autogen(output, num_channels): output.append('#include "engine/channelmixer.h"') output.append('#include "util/timer.h"') output.append('#include "sampleutil.h"') output.append('////////////////////////////////////////////////////////') output.append('// THIS FILE IS AUTO-GENERATED. DO NOT EDIT DIRECTLY! //') output.append('// SEE scripts/generate_sample_functions.py //') output.append('////////////////////////////////////////////////////////') output.append('') output.append('// static') def write_mixchannels(ramping, output): if ramping: header = 'void ChannelMixer::mixChannelsRamping(' else: header = 'void ChannelMixer::mixChannels(' args = ['const EngineMaster::GainCalculator& gainCalculator', 'QVarLengthArray* activeChannels', 'QVarLengthArray* channelGainCache', 'CSAMPLE* pOutput', 'unsigned int iBufferSize'] output.extend(hanging_indent(header, args, ',', ') {')) def write(data, depth=0): output.append(' ' * (BASIC_INDENT * depth) + data) write('int totalActive = activeChannels->size();', depth=1) write('if (totalActive == 0) {', depth=1) write('ScopedTimer t("EngineMaster::mixChannels%(variant)s_0active");' % {'variant': 'Ramping' if ramping else ''}, depth=2) write('SampleUtil::clear(pOutput, iBufferSize);', depth=2) for i in xrange(1, num_channels+1): write('} else if (totalActive == %d) {' % i, depth=1) write('ScopedTimer t("EngineMaster::mixChannels%(variant)s_%(i)dactive");' % {'variant': 'Ramping' if ramping else '', 'i': i}, depth=2) if ramping: write('CSAMPLE_GAIN oldGain[%(i)d];' % {'i': i}, depth=2) write('CSAMPLE_GAIN newGain[%(i)d];' % {'i': i}, depth=2) for j in xrange(i): write('EngineMaster::ChannelInfo* pChannel%(j)d = activeChannels->at(%(j)d);' % {'j': j}, depth=2) write('const int channelIndex%(j)d = pChannel%(j)d->m_index;' % {'j': j}, depth=2) write('EngineMaster::GainCache& gainCache%(j)d = (*channelGainCache)[channelIndex%(j)d];' % {'j': j}, depth=2) if ramping: write('oldGain[%(j)d] = gainCache%(j)d.m_gain;' % {'j': j}, depth=2) write('if (gainCache%(j)d.m_fadeout) {' % {'j': j}, depth=2) write('newGain[%(j)d] = 0;' % {'j': j}, depth=3) write('gainCache%(j)d.m_fadeout = false;' % {'j': j}, depth=3) write('} else {', depth=2) write('newGain[%(j)d] = gainCalculator.getGain(pChannel%(j)d);' % {'j': j}, depth=3) write('}', depth=2) write('gainCache%(j)d.m_gain = newGain[%(j)d];' % {'j': j}, depth=2) write('CSAMPLE* pBuffer%(j)d = pChannel%(j)d->m_pBuffer;' % {'j': j}, depth=2) arg_groups = ['pOutput'] + ['pBuffer%(j)d, newGain[%(j)d]' % {'j': j} for j in xrange(i)] + ['iBufferSize'] call_prefix = "SampleUtil::" + copy_with_gain_method_name(i) + '(' if ramping: arg_groups_ramping = ['pOutput'] + ['pBuffer%(j)d, oldGain[%(j)d], newGain[%(j)d]' % {'j': j} for j in xrange(i)] + ['iBufferSize'] call_prefix_ramping = "SampleUtil::" + copy_with_ramping_gain_method_name(i) + '(' write('int i = 0;', depth=2) write('for(; i < totalActive; ++i) {', depth=2) write('if (oldGain[i] != newGain[i]) {', depth=3) write('break;', depth=4) write('}', depth=3) write('}', depth=2) write('if (i == totalActive) {', depth=2) output.extend(hanging_indent(call_prefix, arg_groups, ',', ');', depth=3)) write('} else {', depth=2) output.extend(hanging_indent(call_prefix_ramping, arg_groups_ramping, ',', ');', depth=3)) write('}', depth=2) else: output.extend(hanging_indent(call_prefix, arg_groups, ',', ');', depth=2)) write('} else {', depth=1) write('ScopedTimer t("EngineMaster::mixChannels%(variant)s_%%1active", activeChannels->size());' % {'variant': 'Ramping' if ramping else ''}, depth=2) write('// Set pOutput to all 0s', depth=2) write('SampleUtil::clear(pOutput, iBufferSize);', depth=2) write('for (int i = 0; i < activeChannels->size(); ++i) {', depth=2) write('EngineMaster::ChannelInfo* pChannelInfo = activeChannels->at(i);', depth=3) write('const int channelIndex = pChannelInfo->m_index;', depth=3) write('EngineMaster::GainCache& gainCache = (*channelGainCache)[channelIndex];', depth=3) if ramping: write('CSAMPLE_GAIN oldGain = gainCache.m_gain;', depth=3) write('CSAMPLE_GAIN newGain;', depth=3) write('if (gainCache.m_fadeout) {', depth=3) write('newGain = 0;', depth=4) write('gainCache.m_fadeout = false;', depth=4) write('} else {', depth=3) write('newGain = gainCalculator.getGain(pChannelInfo);', depth=4) write('}', depth=3) write('gainCache.m_gain = newGain;', depth=3) write('CSAMPLE* pBuffer = pChannelInfo->m_pBuffer;', depth=3) if ramping: write('SampleUtil::addWithRampingGain(pOutput, pBuffer, oldGain, newGain, iBufferSize);', depth=3) else: write('SampleUtil::addWithGain(pOutput, pBuffer, newGain, iBufferSize);', depth=3) write('}', depth=2) write('}', depth=1) output.append('}') write_mixchannels(False, output) write_mixchannels(True, output) def write_sampleutil_autogen(output, num_channels): output.append('#ifndef SAMPLEUTILAUTOGEN_H') output.append('#define SAMPLEUTILAUTOGEN_H') output.append('////////////////////////////////////////////////////////') output.append('// THIS FILE IS AUTO-GENERATED. DO NOT EDIT DIRECTLY! //') output.append('// SEE scripts/generate_sample_functions.py //') output.append('////////////////////////////////////////////////////////') for i in xrange(1, num_channels + 1): copy_with_gain(output, 0, i) copy_with_ramping_gain(output, 0, i) output.append('#endif /* SAMPLEUTILAUTOGEN_H */') def copy_with_gain(output, base_indent_depth, num_channels): def write(data, depth=0): output.append(' ' * (BASIC_INDENT * (depth + base_indent_depth)) + data) header = "static inline void %s(" % copy_with_gain_method_name(num_channels) arg_groups = ['CSAMPLE* _RESTRICT pDest'] + [ "const CSAMPLE* _RESTRICT pSrc%(i)d, CSAMPLE_GAIN gain%(i)d" % {'i': i} for i in xrange(num_channels)] + ['int iNumSamples'] output.extend(hanging_indent(header, arg_groups, ',', ') {', depth=base_indent_depth)) for i in xrange(num_channels): write('if (gain%(i)d == CSAMPLE_GAIN_ZERO) {' % {'i': i}, depth=1) if (num_channels > 1): args = ['pDest',] + ['pSrc%(i)d, gain%(i)d' % {'i': j} for j in xrange(num_channels) if i != j] + ['iNumSamples',] write('%s;' %method_call(copy_with_gain_method_name(num_channels - 1), args), depth=2) else: write('clear(pDest, iNumSamples);', depth=2) write('return;', depth=2) write('}', depth=1) write('// note: LOOP VECTORIZED.', depth=1) write('for (int i = 0; i < iNumSamples; ++i) {', depth=1) terms = ['pSrc%(i)d[i] * gain%(i)d' % {'i': i} for i in xrange(num_channels)] assign = 'pDest[i] = ' output.extend(hanging_indent(assign, terms, ' +', ';', depth=2)) write('}', depth=1) write('}') def copy_with_ramping_gain(output, base_indent_depth, num_channels): def write(data, depth=0): output.append(' ' * (BASIC_INDENT * (depth + base_indent_depth)) + data) header = "static inline void %s(" % copy_with_ramping_gain_method_name(num_channels) arg_groups = ['CSAMPLE* _RESTRICT pDest'] + [ "const CSAMPLE* _RESTRICT pSrc%(i)d, CSAMPLE_GAIN gain%(i)din, CSAMPLE_GAIN gain%(i)dout" % {'i': i} for i in xrange(num_channels)] + ['int iNumSamples'] output.extend(hanging_indent(header, arg_groups, ',', ') {', depth=base_indent_depth)) for i in xrange(num_channels): write('if (gain%(i)din == CSAMPLE_GAIN_ZERO && gain%(i)dout == CSAMPLE_GAIN_ZERO) {' % {'i': i}, depth=1) if (num_channels > 1): args = ['pDest',] + ['pSrc%(i)d, gain%(i)din, gain%(i)dout' % {'i': j} for j in xrange(num_channels) if i != j] + ['iNumSamples',] write('%s;' % method_call(copy_with_ramping_gain_method_name(num_channels - 1), args), depth=2) else: write('clear(pDest, iNumSamples);', depth=2) write('return;', depth=2) write('}', depth=1) for i in xrange(num_channels): write('const CSAMPLE_GAIN gain_delta%(i)d = (gain%(i)dout - gain%(i)din) / (iNumSamples / 2);' % {'i': i}, depth=1) write('const CSAMPLE_GAIN start_gain%(i)d = gain%(i)din + gain_delta%(i)d;' % {'i': i}, depth=1) write('// note: LOOP VECTORIZED.', depth=1) write('for (int i = 0; i < iNumSamples / 2; ++i) {', depth=1) increments = ['const CSAMPLE_GAIN gain%(i)d = start_gain%(i)d + gain_delta%(i)d * i;' % {'i': i} for i in xrange(num_channels)] for i in xrange(num_channels): write('const CSAMPLE_GAIN gain%(i)d = start_gain%(i)d + gain_delta%(i)d * i;' % {'i': i}, depth=2) terms1 = [] terms2 = [] for i in xrange(num_channels): terms1.append('pSrc%(i)d[i * 2] * gain%(i)d' % {'i': i}) terms2.append('pSrc%(i)d[i * 2 + 1] * gain%(i)d' % {'i': i}) assign1 = 'pDest[i * 2] = ' assign2 = 'pDest[i * 2 + 1] = ' output.extend(hanging_indent(assign1, terms1, ' +', ';', depth=2)) output.extend(hanging_indent(assign2, terms2, ' +', ';', depth=2)) write('}', depth=1) write('}') def main(args): sampleutil_output_lines = [] write_sampleutil_autogen(sampleutil_output_lines, args.max_channels) output = (open(args.sampleutil_autogen_h, 'w') if args.sampleutil_autogen_h else sys.stdout) output.write('\n'.join(sampleutil_output_lines) + '\n') channelmixer_output_lines = [] write_channelmixer_autogen(channelmixer_output_lines, args.max_channels) output = (open(args.channelmixer_autogen_cpp, 'w') if args.channelmixer_autogen_cpp else sys.stdout) output.write('\n'.join(channelmixer_output_lines) + '\n') if __name__ == '__main__': parser = argparse.ArgumentParser( description='Auto-generate sample processing and mixing functions.' + 'Example Call:' + './generate_sample_functions.py --sampleutil_autogen_h ../src/sampleutil_autogen.h --channelmixer_autogen_cpp ../src/engine/channelmixer_autogen.cpp') parser.add_argument('--sampleutil_autogen_h') parser.add_argument('--channelmixer_autogen_cpp') parser.add_argument('--max_channels', type=int, default=32) args = parser.parse_args() main(args)