From aca6e6bb55bb86a3eae82e0a77a7c8c6675a5e8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Pluta?= Date: Tue, 30 Jul 2019 16:41:49 +0200 Subject: [PATCH] Create tools for compiling waves --- smnp/module/synth/__init__.py | 4 +- smnp/module/synth/function/compile.py | 141 ++++++++++++++++++++++++++ smnp/module/synth/function/plot.py | 107 ++----------------- smnp/module/synth/function/synth.py | 110 +++----------------- smnp/module/synth/lib/wave.py | 14 ++- 5 files changed, 172 insertions(+), 204 deletions(-) create mode 100644 smnp/module/synth/function/compile.py diff --git a/smnp/module/synth/__init__.py b/smnp/module/synth/__init__.py index fc2169d..a8b743a 100644 --- a/smnp/module/synth/__init__.py +++ b/smnp/module/synth/__init__.py @@ -1,4 +1,4 @@ -from smnp.module.synth.function import synth, pause, plot +from smnp.module.synth.function import synth, pause, plot, compile -functions = [ synth.function, pause.function, plot.function ] +functions = [ synth.function, pause.function, plot.function, compile.function ] methods = [] \ No newline at end of file diff --git a/smnp/module/synth/function/compile.py b/smnp/module/synth/function/compile.py new file mode 100644 index 0000000..8a03845 --- /dev/null +++ b/smnp/module/synth/function/compile.py @@ -0,0 +1,141 @@ +from smnp.error.runtime import RuntimeException +from smnp.function.model import Function, CombinedFunction +from smnp.function.signature import varargSignature +from smnp.module.synth.lib.wave import compilePolyphony + +from smnp.type.model import Type +from smnp.type.signature.matcher.list import listOf +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 +DEFAULT_ATTACK = 100 + + +def getBpm(config): + key = Type.string("bpm") + if key in config.value: + bpm = config.value[key] + if bpm.type != Type.INTEGER or bpm.value <= 0: + raise RuntimeException("The 'bpm' property must be positive integer", None) + + return bpm.value + + return DEFAULT_BPM + + +def getOvertones(config): + key = Type.string("overtones") + if key in config.value: + overtones = config.value[key] + rawOvertones = [overtone.value for overtone in overtones.value] + if overtones.type != Type.LIST or not all(overtone.type in [Type.FLOAT, Type.INTEGER] for overtone in overtones.value): + raise RuntimeException("The 'overtones' property must be list of floats", None) + + if len(rawOvertones) < 1: + raise RuntimeException("The 'overtones' property must contain one overtone at least", None) + + if any(overtone < 0 for overtone in rawOvertones): + raise RuntimeException("The 'overtones' property mustn't contain negative values", None) + + if sum(rawOvertones) > 1.0: + raise RuntimeException("The 'overtones' property must contain overtones which sum is not greater than 1.0", None) + + return rawOvertones + + 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 + + +def getAttack(config): + key = Type.string("attack") + if key in config.value: + attack = config.value[key] + if not attack.type in [Type.INTEGER, Type.FLOAT] or attack.value < 0: + raise RuntimeException("The 'attack' property must be non-negative integer or float", None) + + return attack.value + + return DEFAULT_ATTACK + + +class Config: + def __init__(self, bpm, overtones, decay, attack): + self.bpm = bpm + self.overtones = overtones + self.decay = decay + self.attack = attack + + @staticmethod + def default(): + return Config(DEFAULT_BPM, DEFAULT_OVERTONES, DEFAULT_DECAY, DEFAULT_ATTACK) + + +_signature1 = varargSignature(listOf(Type.NOTE, Type.INTEGER)) +def _function1(env, notes): + return Type.list([Type.float(float(m)) for m in __function1(notes)]) + + +def __function1(notes): + return compilePolyphony([note.value for note in notes], Config.default()) + + +_signature2 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER)) +def _function2(env, notes): + return Type.list([Type.float(float(m)) for m in __function2(notes)]) + + +def __function2(notes): + return compilePolyphony([ notes ], Config.default()) + + +_signature3 = varargSignature(listOf(Type.NOTE, Type.INTEGER), ofType(Type.MAP)) +def _function3(env, config, notes): + return Type.list([ Type.float(float(m)) for m in __function3(config, notes) ]) + + +def __function3(config, notes): + rawNotes = [note.value for note in notes] + + bpm = getBpm(config) + overtones = getOvertones(config) + decay = getDecay(config) + attack = getAttack(config) + + return compilePolyphony(rawNotes, Config(bpm, overtones, decay, attack)) + + +_signature4 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER), ofType(Type.MAP)) +def _function4(env, config, notes): + return Type.list([ Type.float(float(m)) for m in __function4(config, notes) ]) + + +def __function4(config, notes): + bpm = getBpm(config) + overtones = getOvertones(config) + decay = getDecay(config) + attack = getAttack(config) + + return compilePolyphony([ notes ], Config(bpm, overtones, decay, attack)) + + +function = CombinedFunction( + 'wave', + Function(_signature1, _function1), + Function(_signature2, _function2), + Function(_signature3, _function3), + Function(_signature4, _function4), +) \ No newline at end of file diff --git a/smnp/module/synth/function/plot.py b/smnp/module/synth/function/plot.py index 20a3ac6..4d415f9 100644 --- a/smnp/module/synth/function/plot.py +++ b/smnp/module/synth/function/plot.py @@ -1,106 +1,13 @@ -from smnp.error.runtime import RuntimeException -from smnp.function.model import CombinedFunction, Function +from smnp.function.model import Function from smnp.function.signature import signature from smnp.module.synth.lib.wave import plot from smnp.type.model import Type -from smnp.type.signature.matcher.type import ofType +from smnp.type.signature.matcher.list import listOf -DEFAULT_BPM = 120 -DEFAULT_OVERTONES = [0.4, 0.3, 0.1, 0.1, 0.1] -DEFAULT_DECAY = 4 -DEFAULT_ATTACK = 100 - -# TODO: this code is shared with synth.py module, remove repetition -def getBpm(config): - key = Type.string("bpm") - if key in config.value: - bpm = config.value[key] - if bpm.type != Type.INTEGER or bpm.value <= 0: - raise RuntimeException("The 'bpm' property must be positive integer", None) - - return bpm.value - - return DEFAULT_BPM +_signature = signature(listOf(Type.FLOAT)) +def _function(env, wave): + rawWave = [ m.value for m in wave.value ] + plot(rawWave) -def getOvertones(config): - key = Type.string("overtones") - if key in config.value: - overtones = config.value[key] - rawOvertones = [overtone.value for overtone in overtones.value] - if overtones.type != Type.LIST or not all(overtone.type in [Type.FLOAT, Type.INTEGER] for overtone in overtones.value): - raise RuntimeException("The 'overtones' property must be list of floats", None) - - if len(rawOvertones) < 1: - raise RuntimeException("The 'overtones' property must contain one overtone at least", None) - - if any(overtone < 0 for overtone in rawOvertones): - raise RuntimeException("The 'overtones' property mustn't contain negative values", None) - - if sum(rawOvertones) > 1.0: - raise RuntimeException("The 'overtones' property must contain overtones which sum is not greater than 1.0", None) - - return rawOvertones - - 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 - - -def getAttack(config): - key = Type.string("attack") - if key in config.value: - attack = config.value[key] - if not attack.type in [Type.INTEGER, Type.FLOAT] or attack.value < 0: - raise RuntimeException("The 'attack' property must be non-negative integer or float", None) - - return attack.value - - return DEFAULT_ATTACK - - -class Config: - def __init__(self, bpm, overtones, decay, attack): - self.bpm = bpm - self.overtones = overtones - self.decay = decay - self.attack = attack - - @staticmethod - def default(): - return Config(DEFAULT_BPM, DEFAULT_OVERTONES, DEFAULT_DECAY, DEFAULT_ATTACK) - - -_signature1 = signature(ofType(Type.NOTE)) -def _function1(env, note): - config = Config.default() - - plot(note.value, config) - - -_signature2 = signature(ofType(Type.MAP), ofType(Type.NOTE)) -def _function2(env, config, note): - bpm = getBpm(config) - overtones = getOvertones(config) - decay = getDecay(config) - attack = getAttack(config) - - plot(note.value, Config(bpm, overtones, decay, attack)) - - -function = CombinedFunction( - 'plot', - Function(_signature1, _function1), - Function(_signature2, _function2) -) - +function = Function(_signature, _function, 'plotWave') diff --git a/smnp/module/synth/function/synth.py b/smnp/module/synth/function/synth.py index f29bf8f..04816be 100644 --- a/smnp/module/synth/function/synth.py +++ b/smnp/module/synth/function/synth.py @@ -1,119 +1,40 @@ -from smnp.error.runtime import RuntimeException from smnp.function.model import Function, CombinedFunction from smnp.function.signature import varargSignature +from smnp.module.synth.function import compile from smnp.module.synth.lib.wave import play - from smnp.type.model import Type from smnp.type.signature.matcher.list import listOf 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 -DEFAULT_ATTACK = 100 - - -def getBpm(config): - key = Type.string("bpm") - if key in config.value: - bpm = config.value[key] - if bpm.type != Type.INTEGER or bpm.value <= 0: - raise RuntimeException("The 'bpm' property must be positive integer", None) - - return bpm.value - - return DEFAULT_BPM - - -def getOvertones(config): - key = Type.string("overtones") - if key in config.value: - overtones = config.value[key] - rawOvertones = [overtone.value for overtone in overtones.value] - if overtones.type != Type.LIST or not all(overtone.type in [Type.FLOAT, Type.INTEGER] for overtone in overtones.value): - raise RuntimeException("The 'overtones' property must be list of floats", None) - - if len(rawOvertones) < 1: - raise RuntimeException("The 'overtones' property must contain one overtone at least", None) - - if any(overtone < 0 for overtone in rawOvertones): - raise RuntimeException("The 'overtones' property mustn't contain negative values", None) - - if sum(rawOvertones) > 1.0: - raise RuntimeException("The 'overtones' property must contain overtones which sum is not greater than 1.0", None) - - return rawOvertones - - 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 - - -def getAttack(config): - key = Type.string("attack") - if key in config.value: - attack = config.value[key] - if not attack.type in [Type.INTEGER, Type.FLOAT] or attack.value < 0: - raise RuntimeException("The 'attack' property must be non-negative integer or float", None) - - return attack.value - - return DEFAULT_ATTACK - - -class Config: - def __init__(self, bpm, overtones, decay, attack): - self.bpm = bpm - self.overtones = overtones - self.decay = decay - self.attack = attack - - @staticmethod - def default(): - return Config(DEFAULT_BPM, DEFAULT_OVERTONES, DEFAULT_DECAY, DEFAULT_ATTACK) - - _signature1 = varargSignature(listOf(Type.NOTE, Type.INTEGER)) def _function1(env, notes): - rawNotes = [note.value for note in notes] - play(rawNotes, Config.default()) + wave = compile.__function1(notes) + play(wave) _signature2 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER)) def _function2(env, notes): - play([ notes ], Config.default()) + wave = compile.__function2(notes) + play(wave) _signature3 = varargSignature(listOf(Type.NOTE, Type.INTEGER), ofType(Type.MAP)) def _function3(env, config, notes): - rawNotes = [note.value for note in notes] - bpm = getBpm(config) - overtones = getOvertones(config) - decay = getDecay(config) - attack = getAttack(config) - - play(rawNotes, Config(bpm, overtones, decay, attack)) + wave = compile.__function3(config, notes) + play(wave) _signature4 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER), ofType(Type.MAP)) def _function4(env, config, notes): - bpm = getBpm(config) - overtones = getOvertones(config) - decay = getDecay(config) - attack = getAttack(config) + wave = compile.__function4(config, notes) + play(wave) - play([ notes ], Config(bpm, overtones, decay, attack)) + +_signature5 = varargSignature(listOf(Type.FLOAT)) +def _function5(env, waves): + for wave in waves: + rawWave = [m.value for m in wave.value] + play(rawWave) function = CombinedFunction( @@ -122,4 +43,5 @@ function = CombinedFunction( Function(_signature2, _function2), Function(_signature3, _function3), Function(_signature4, _function4), + Function(_signature5, _function5) ) \ No newline at end of file diff --git a/smnp/module/synth/lib/wave.py b/smnp/module/synth/lib/wave.py index 05df133..2f106b6 100644 --- a/smnp/module/synth/lib/wave.py +++ b/smnp/module/synth/lib/wave.py @@ -13,17 +13,15 @@ def pause(value, bpm): time.sleep(60 * 4 / value / bpm) -def plot(note, config): - Y = sineForNote(note, config) - X = np.arange(len(Y)) - plt.plot(X, Y) +def plot(wave): + X = np.arange(len(wave)) + plt.plot(X, wave) plt.show() -def play(notes, config): - compiled = compilePolyphony(notes, config) - sd.play(compiled) - time.sleep(len(compiled) / FS) +def play(wave): + sd.play(wave) + time.sleep(len(wave) / FS) def compilePolyphony(notes, config):