diff --git a/smnp/module/synth/function/synth.py b/smnp/module/synth/function/synth.py index 482faf7..913cbeb 100644 --- a/smnp/module/synth/function/synth.py +++ b/smnp/module/synth/function/synth.py @@ -10,6 +10,7 @@ from smnp.type.signature.matcher.type import ofTypes, ofType DEFAULT_BPM = 120 DEFAULT_OVERTONES = [0.4, 0.3, 0.1, 0.1, 0.1] +DEFAULT_DECAY = 4 def getBpm(config): @@ -46,16 +47,38 @@ def getOvertones(config): return DEFAULT_OVERTONES +def getDecay(config): + key = Type.string("decay") + if key in config.value: + decay = config.value[key] + if not decay.type in [Type.INTEGER, Type.FLOAT] or decay.value < 0: + raise RuntimeException("The 'decay' property must be non-negative integer or float", None) + + return decay.value + + return DEFAULT_DECAY + + +class Config: + def __init__(self, bpm, overtones, decay): + self.bpm = bpm + self.overtones = overtones + self.decay = decay + + @staticmethod + def default(): + return Config(DEFAULT_BPM, DEFAULT_OVERTONES, DEFAULT_DECAY) + _signature1 = varargSignature(listOf(Type.NOTE, Type.INTEGER)) def _function1(env, notes): rawNotes = [note.value for note in notes] - play(rawNotes, DEFAULT_BPM, DEFAULT_OVERTONES) + play(rawNotes, Config.default()) _signature2 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER)) def _function2(env, notes): - play([ notes ], DEFAULT_BPM, DEFAULT_OVERTONES) + play([ notes ], Config.default()) _signature3 = varargSignature(listOf(Type.NOTE, Type.INTEGER), ofType(Type.MAP)) @@ -63,15 +86,18 @@ def _function3(env, config, notes): rawNotes = [note.value for note in notes] bpm = getBpm(config) overtones = getOvertones(config) + decay = getDecay(config) - play(rawNotes, bpm, overtones) + play(rawNotes, Config(bpm, overtones, decay)) _signature4 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER), ofType(Type.MAP)) def _function4(env, config, notes): bpm = getBpm(config) overtones = getOvertones(config) - play([ notes ], bpm, overtones) + decay = getDecay(config) + + play([ notes ], Config(bpm, overtones, decay)) function = CombinedFunction( diff --git a/smnp/module/synth/lib/wave.py b/smnp/module/synth/lib/wave.py index 5df57be..5c39985 100644 --- a/smnp/module/synth/lib/wave.py +++ b/smnp/module/synth/lib/wave.py @@ -12,14 +12,14 @@ def pause(value, bpm): time.sleep(60 * 4 / value / bpm) -def play(notes, bpm, overtones): - compiled = compilePolyphony(notes, bpm, overtones) +def play(notes, config): + compiled = compilePolyphony(notes, config) sd.play(compiled) time.sleep(len(compiled) / FS) -def compilePolyphony(notes, bpm, overtones): - compiledLines = [1 / len(notes) * compileNotes(line, bpm, overtones) for line in notes] +def compilePolyphony(notes, config): + compiledLines = [1 / len(notes) * compileNotes(line, config) for line in notes] return sum(adjustSize(compiledLines)) @@ -29,30 +29,36 @@ def adjustSize(compiledLines): return [np.concatenate([line, np.zeros(maxSize - len(line))]) for line in compiledLines] -def compileNotes(notes, bpm, overtones): +def compileNotes(notes, config): dispatcher = { - Type.NOTE: lambda note, overtones: sineForNote(note.value, bpm, overtones), - Type.INTEGER: lambda note, overtones: silenceForPause(note.value, bpm) + Type.NOTE: lambda note, overtones: sineForNote(note.value, config), + Type.INTEGER: lambda note, overtones: silenceForPause(note.value, config) } - return np.concatenate([dispatcher[note.type](note, overtones) for note in notes]) + return np.concatenate([dispatcher[note.type](note, config) for note in notes]) -def sineForNote(note, bpm, overtones): +def sineForNote(note, config): frequency = note.toFrequency() - duration = 60 * 4 / note.duration / bpm + duration = 60 * 4 / note.duration / config.bpm duration *= 1.5 if note.dot else 1 - return sound(frequency, duration, overtones) + return sound(frequency, duration, config) -def sound(frequency, duration, overtones): - return sum(a * sine((i+1) * frequency, duration) for i, a in enumerate(overtones)) +def sound(frequency, duration, config): + return decay(sum(a * sine((i+1) * frequency, duration) for i, a in enumerate(config.overtones)), config) + + +def decay(wave, config): + magnitude = np.exp(-config.decay/len(wave) * np.arange(len(wave))) + + return magnitude * wave def sine(frequency, duration): return (np.sin(2 * np.pi * np.arange(FS * duration) * frequency / FS)).astype(np.float32) -def silenceForPause(value, bpm): - duration = 60 * 4 / value / bpm +def silenceForPause(value, config): + duration = 60 * 4 / value / config.bpm return np.zeros(int(FS * duration))