Skip to content

Commit

Permalink
Merge pull request #39 from ywwg/crossfade_util
Browse files Browse the repository at this point in the history
Smoothly ramp changes in gain.
  • Loading branch information
ywwg committed Jul 27, 2013
2 parents a41a3dc + 62dda7c commit 11f3c5a
Show file tree
Hide file tree
Showing 15 changed files with 12,522 additions and 1,077 deletions.
3 changes: 2 additions & 1 deletion build/depends.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,7 @@ def sources(self, build):
"engine/enginemicrophone.cpp",
"engine/enginedeck.cpp",
"engine/enginepassthrough.cpp",
"engine/channelmixer_autogen.cpp",

"engine/enginecontrol.cpp",
"engine/ratecontrol.cpp",
Expand Down Expand Up @@ -735,7 +736,7 @@ def sources(self, build):
"util/timer.cpp",
"util/performancetimer.cpp",
"util/version.cpp",
"util/rlimit.cpp",
"util/rlimit.cpp",

# Add the QRC file which compiles in some extra resources
# (prefs icons, etc.)
Expand Down
222 changes: 222 additions & 0 deletions scripts/generate_sample_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
#!/usr/bin/env python
import argparse
import sys

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')

header = 'void ChannelMixer::mixChannels('
args = ['const QList<EngineMaster::ChannelInfo*>& channels',
'const EngineMaster::GainCalculator& gainCalculator',
'unsigned int channelBitvector',
'unsigned int maxChannels',
'QList<CSAMPLE>* 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 activeChannels[%d] = {' % num_channels, depth=1)
for i in xrange(num_channels):
if i == num_channels - 1:
write('-1};', depth=2)
else:
write('-1,', depth=2)

write('unsigned int totalActive = 0;', depth=1)
write('for (unsigned int i = 0; i < maxChannels; ++i) {', depth=1)
write('if ((channelBitvector & (1 << i)) == 0) {', depth=2)
write('continue;', depth=3)
write('}', depth=2)
write('if (totalActive < %d) {' % num_channels, depth=2)
write('activeChannels[totalActive] = i;', depth=3)
write('}', depth=2)
write('++totalActive;', depth=2)
write('}', depth=1)

write('if (totalActive == 0) {', depth=1)
write('ScopedTimer t("EngineMaster::mixChannels_0active");', depth=2)
write('SampleUtil::applyGain(pOutput, 0.0f, iBufferSize);', depth=2)
for i in xrange(1, num_channels+1):
write('} else if (totalActive == %d) {' % i, depth=1)
write('ScopedTimer t("EngineMaster::mixChannels_%(i)dactive");' % {'i': i}, depth=2)
for j in xrange(i):
write('const int pChannelIndex%(j)d = activeChannels[%(j)d];' % {'j': j}, depth=2)
write('EngineMaster::ChannelInfo* pChannel%(j)d = channels[pChannelIndex%(j)d];' % {'j': j}, depth=2)
write('CSAMPLE oldGain%(j)d = (*channelGainCache)[pChannelIndex%(j)d];' % {'j': j}, depth=2)
write('CSAMPLE newGain%(j)d = gainCalculator.getGain(pChannel%(j)d);' % {'j': j}, depth=2)
write('(*channelGainCache)[pChannelIndex%(j)d] = 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, oldGain%(j)d, newGain%(j)d' % {'j': j} for j in xrange(i)] + ['iBufferSize']
call_prefix = "SampleUtil::" + copy_with_ramping_gain_method_name(i) + '('
output.extend(hanging_indent(call_prefix, arg_groups, ',', ');', depth=2))


write('} else {', depth=1)
write('// Set pOutput to all 0s', depth=2)
write('SampleUtil::applyGain(pOutput, 0.0f, iBufferSize);', depth=2)
write('for (unsigned int i = 0; i < maxChannels; ++i) {', depth=2)
write('if (channelBitvector & (1 << i)) {', depth=3)
write('EngineMaster::ChannelInfo* pChannelInfo = channels[i];', depth=4)
write('CSAMPLE* pBuffer = pChannelInfo->m_pBuffer;', depth=4)
write('CSAMPLE gain = gainCalculator.getGain(pChannelInfo);', depth=4)
write('SampleUtil::addWithGain(pOutput, pBuffer, gain, iBufferSize);', depth=4)
write('}', depth=3)
write('}', depth=2)
write('}', depth=1)
output.append('}')

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* pDest'] + [
"const CSAMPLE* pSrc%(i)d, CSAMPLE gain%(i)d" % {'i': i}
for i in xrange(num_channels)] + ['int iNumSamples']

output.extend(hanging_indent(header, arg_groups, ',', ') {',
depth=base_indent_depth))

if (num_channels == 1):
write('copyWithGain(pDest, pSrc0, gain0, iNumSamples);', depth=1)
write('return;', depth=1)
write('}')
return

for i in xrange(num_channels):
write('if (gain%(i)d == 0.0) {' % {'i': i}, depth=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)
write('return;', depth=2)
write('}', 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* pDest'] + [
"const CSAMPLE* pSrc%(i)d, CSAMPLE gain%(i)din, CSAMPLE gain%(i)dout" % {'i': i}
for i in xrange(num_channels)] + ['int iNumSamples']

output.extend(hanging_indent(header, arg_groups, ',', ') {',
depth=base_indent_depth))

if (num_channels == 1):
write('copyWithRampingGain(pDest, pSrc0, gain0in, gain0out, iNumSamples);', depth=1)
write('return;', depth=1)
write('}')
return


for i in xrange(num_channels):
write('if (gain%(i)din == 0.0 && gain%(i)dout == 0.0) {' % {'i': i}, depth=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)
write('return;', depth=2)
write('}', depth=1)

for i in xrange(num_channels):
write('const CSAMPLE delta%(i)d = 2.0 * (gain%(i)dout - gain%(i)din) / iNumSamples;' % {'i': i}, depth=1)
write('CSAMPLE gain%(i)d = gain%(i)din;' % {'i': i}, depth=1)

increments = ['i += 2',] + ['gain%(i)d += delta%(i)d' % {'i': i} for i in xrange(num_channels)]

write('for (int i = 0; i < iNumSamples; %s) {' % ', '.join(increments), depth=1)

terms1 = []
terms2 = []
for i in xrange(num_channels):
terms1.append('pSrc%(i)d[i] * gain%(i)d' % {'i': i})
terms2.append('pSrc%(i)d[i + 1] * gain%(i)d' % {'i': i})

assign1 = 'pDest[i] = '
assign2 = 'pDest[i + 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.')
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)
18 changes: 18 additions & 0 deletions src/engine/channelmixer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#ifndef CHANNELMIXER_H
#define CHANNELMIXER_H

#include "defs.h"
#include "engine/enginemaster.h"

class ChannelMixer {
public:
static void mixChannels(const QList<EngineMaster::ChannelInfo*>& channels,
const EngineMaster::GainCalculator& gainCalculator,
unsigned int channelBitvector,
unsigned int maxChannels,
QList<CSAMPLE>* channelGainCache,
CSAMPLE* pOutput,
unsigned int iBufferSize);
};

#endif /* CHANNELMIXER_H */
Loading

0 comments on commit 11f3c5a

Please sign in to comment.