From 0dcf5287e15c9dc25adef1aea490977f316aef3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Pluta?= Date: Sun, 28 Jul 2019 19:48:39 +0200 Subject: [PATCH] Add polyphony AND add overtones do synthesed tones --- smnp/module/synth/function/pause.py | 4 +-- smnp/module/synth/function/synth.py | 29 +++++++++++----- smnp/module/synth/lib/player.py | 24 ------------- smnp/module/synth/lib/wave.py | 52 +++++++++++++++++++++++++++-- 4 files changed, 71 insertions(+), 38 deletions(-) delete mode 100644 smnp/module/synth/lib/player.py diff --git a/smnp/module/synth/function/pause.py b/smnp/module/synth/function/pause.py index e1869f8..4af81df 100644 --- a/smnp/module/synth/function/pause.py +++ b/smnp/module/synth/function/pause.py @@ -1,13 +1,13 @@ from smnp.function.model import Function from smnp.function.signature import signature -from smnp.module.synth.lib import player +from smnp.module.synth.lib.wave import pause from smnp.type.model import Type from smnp.type.signature.matcher.type import ofTypes _signature = signature(ofTypes(Type.INTEGER)) def _function(env, value): bpm = env.findVariable('bpm') - player.pause(value.value, bpm.value) + pause(value.value, bpm.value) function = Function(_signature, _function, 'pause') \ No newline at end of file diff --git a/smnp/module/synth/function/synth.py b/smnp/module/synth/function/synth.py index 0c6c834..7002310 100644 --- a/smnp/module/synth/function/synth.py +++ b/smnp/module/synth/function/synth.py @@ -1,13 +1,24 @@ -from smnp.function.model import Function -from smnp.function.signature import signature -from smnp.module.synth.lib.player import play +from smnp.function.model import Function, CombinedFunction +from smnp.function.signature import varargSignature +from smnp.module.synth.lib.wave import play + from smnp.type.model import Type -from smnp.type.signature.matcher.type import ofType +from smnp.type.signature.matcher.list import listOf +from smnp.type.signature.matcher.type import ofTypes -_signature = signature(ofType(Type.NOTE)) -def _function(env, note): - bpm = env.findVariable('bpm') - play(note.value, bpm.value) +_signature1 = varargSignature(listOf(Type.NOTE, Type.INTEGER)) +def _function1(env, notes): + rawNotes = [note.value for note in notes] + play(rawNotes, env.findVariable("bpm").value, env.findVariable("overtones").value) -function = Function(_signature, _function, 'synthNote') \ No newline at end of file +_signature2 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER)) +def _function2(env, notes): + play([ notes ], env.findVariable("bpm").value, env.findVariable("overtones").value) + + +function = CombinedFunction( + 'synth', + Function(_signature1, _function1), + Function(_signature2, _function2) +) \ No newline at end of file diff --git a/smnp/module/synth/lib/player.py b/smnp/module/synth/lib/player.py deleted file mode 100644 index ad1e56b..0000000 --- a/smnp/module/synth/lib/player.py +++ /dev/null @@ -1,24 +0,0 @@ -import time - -from smnp.module.synth.lib.wave import sine -from smnp.note.model import Note - - -def playNotes(notes, bpm): - for note in notes: - { - Note: play, - int: pause - }[type(note)](note, bpm) - - -def play(note, bpm): - frequency = note.toFrequency() - duration = 60 * 4 / note.duration / bpm - duration *= 1.5 if note.dot else 1 - sine(frequency, duration) - - -def pause(value, bpm): - time.sleep(60 * 4 / value / bpm) - diff --git a/smnp/module/synth/lib/wave.py b/smnp/module/synth/lib/wave.py index 3c2e1d7..8259423 100644 --- a/smnp/module/synth/lib/wave.py +++ b/smnp/module/synth/lib/wave.py @@ -3,10 +3,56 @@ import time import numpy as np import sounddevice as sd +from smnp.type.model import Type + FS = 44100 +def pause(value, bpm): + time.sleep(60 * 4 / value / bpm) + + +def play(notes, bpm, overtones): + compiled = compilePolyphony(notes, bpm, overtones) + 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] + return sum(adjustSize(compiledLines)) + + +def adjustSize(compiledLines): + maxSize = max(len(line) for line in compiledLines) + + return [np.concatenate([line, np.zeros(maxSize - len(line))]) for line in compiledLines] + + +def compileNotes(notes, bpm, overtones): + dispatcher = { + Type.NOTE: lambda note, overtones: sineForNote(note.value, bpm, overtones), + Type.INTEGER: lambda note, overtones: silenceForPause(note.value, bpm) + } + + return np.concatenate([dispatcher[note.type](note, overtones) for note in notes]) + + +def sineForNote(note, bpm, overtones): + frequency = note.toFrequency() + duration = 60 * 4 / note.duration / bpm + duration *= 1.5 if note.dot else 1 + return sound(frequency, duration, overtones) + + +def sound(frequency, duration, overtones): + return sum(a.value * sine((i+1) * frequency, duration) for i, a in enumerate(overtones)) + + def sine(frequency, duration): - samples = (np.sin(2*np.pi*np.arange(FS*duration)*frequency/FS)).astype(np.float32) - sd.play(samples, FS) - time.sleep(duration) \ No newline at end of file + return (np.sin(2 * np.pi * np.arange(FS * duration) * frequency / FS)).astype(np.float32) + + +def silenceForPause(value, bpm): + duration = 60 * 4 / value / bpm + return np.zeros(int(FS * duration))