From 06579e8e78c1b4b58f6053c16dcd7d0976b87a82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Pluta?= Date: Fri, 6 Sep 2019 22:54:22 +0200 Subject: [PATCH] Enable tuning --- smnp/main.py | 8 +++--- smnp/module/synth/function/compile.py | 24 ++++++++++++++--- smnp/module/synth/lib/wave.py | 12 +++++---- smnp/note/model.py | 4 +-- smnp/note/pitch.py | 39 ++++++++++++++++----------- 5 files changed, 56 insertions(+), 31 deletions(-) diff --git a/smnp/main.py b/smnp/main.py index 91c0481..3802d0a 100644 --- a/smnp/main.py +++ b/smnp/main.py @@ -1,11 +1,9 @@ -import sys -import time - +from smnp.cli.parser import CliParser from smnp.error.base import SmnpException from smnp.library.loader import loadStandardLibrary -from smnp.program.interpreter import Interpreter -from smnp.cli.parser import CliParser from smnp.module.mic.lib.detector.noise import NoiseDetector +from smnp.program.interpreter import Interpreter + def interpretFile(args, file): stdLibraryEnv = loadStandardLibrary() if not args.dry_run else None diff --git a/smnp/module/synth/function/compile.py b/smnp/module/synth/function/compile.py index 8a03845..ee7f10b 100644 --- a/smnp/module/synth/function/compile.py +++ b/smnp/module/synth/function/compile.py @@ -12,6 +12,7 @@ DEFAULT_BPM = 120 DEFAULT_OVERTONES = [0.4, 0.3, 0.1, 0.1, 0.1] DEFAULT_DECAY = 4 DEFAULT_ATTACK = 100 +DEFAULT_TUNING = 440 def getBpm(config): @@ -72,16 +73,29 @@ def getAttack(config): return DEFAULT_ATTACK +def getTuning(config): + key = Type.string("tuning") + if key in config.value: + tuning = config.value[key] + if not tuning.type in [Type.INTEGER, Type.FLOAT] or tuning.value < 0: + raise RuntimeException("The 'tuning' property must be non-negative integer or float", None) + + return tuning.value + + return DEFAULT_TUNING + + class Config: - def __init__(self, bpm, overtones, decay, attack): + def __init__(self, bpm, overtones, decay, attack, tuning): self.bpm = bpm self.overtones = overtones self.decay = decay self.attack = attack + self.tuning = tuning @staticmethod def default(): - return Config(DEFAULT_BPM, DEFAULT_OVERTONES, DEFAULT_DECAY, DEFAULT_ATTACK) + return Config(DEFAULT_BPM, DEFAULT_OVERTONES, DEFAULT_DECAY, DEFAULT_ATTACK, DEFAULT_TUNING) _signature1 = varargSignature(listOf(Type.NOTE, Type.INTEGER)) @@ -114,8 +128,9 @@ def __function3(config, notes): overtones = getOvertones(config) decay = getDecay(config) attack = getAttack(config) + tuning = getTuning(config) - return compilePolyphony(rawNotes, Config(bpm, overtones, decay, attack)) + return compilePolyphony(rawNotes, Config(bpm, overtones, decay, attack, tuning)) _signature4 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER), ofType(Type.MAP)) @@ -128,8 +143,9 @@ def __function4(config, notes): overtones = getOvertones(config) decay = getDecay(config) attack = getAttack(config) + tuning = getTuning(config) - return compilePolyphony([ notes ], Config(bpm, overtones, decay, attack)) + return compilePolyphony([ notes ], Config(bpm, overtones, decay, attack, tuning)) function = CombinedFunction( diff --git a/smnp/module/synth/lib/wave.py b/smnp/module/synth/lib/wave.py index 2f106b6..d774feb 100644 --- a/smnp/module/synth/lib/wave.py +++ b/smnp/module/synth/lib/wave.py @@ -4,6 +4,7 @@ import matplotlib.pyplot as plt import numpy as np import sounddevice as sd +from smnp.note.pitch import Tuning from smnp.type.model import Type FS = 44100 @@ -25,7 +26,8 @@ def play(wave): def compilePolyphony(notes, config): - compiledLines = [1 / len(notes) * compileNotes(line, config) for line in notes] + tuning = Tuning(config.tuning) + compiledLines = [1 / len(notes) * compileNotes(line, config, tuning) for line in notes] return sum(adjustSize(compiledLines)) @@ -35,17 +37,17 @@ def adjustSize(compiledLines): return [np.concatenate([line, np.zeros(maxSize - len(line))]) for line in compiledLines] -def compileNotes(notes, config): +def compileNotes(notes, config, tuning): dispatcher = { - Type.NOTE: lambda note, overtones: sineForNote(note.value, config), + Type.NOTE: lambda note, overtones: sineForNote(note.value, config, tuning), Type.INTEGER: lambda note, overtones: silenceForPause(note.value, config) } return np.concatenate([dispatcher[note.type](note, config) for note in notes]) -def sineForNote(note, config): - frequency = note.toFrequency() +def sineForNote(note, config, tuning): + frequency = note.toFrequency(tuning) duration = 60 * 4 / note.duration / config.bpm duration *= 1.5 if note.dot else 1 return sound(frequency, duration, config) diff --git a/smnp/note/model.py b/smnp/note/model.py index d41e674..79507ea 100644 --- a/smnp/note/model.py +++ b/smnp/note/model.py @@ -16,8 +16,8 @@ class Note: self.duration = duration self.dot = dot - def toFrequency(self): - return self.note.toFrequency() * 2 ** self.octave + def toFrequency(self, tuning): + return tuning[self.note] * 2 ** self.octave def transpose(self, interval): origIntRepr = self._intRepr() diff --git a/smnp/note/pitch.py b/smnp/note/pitch.py index 3fa5e77..f9ebe2e 100644 --- a/smnp/note/pitch.py +++ b/smnp/note/pitch.py @@ -2,6 +2,7 @@ from enum import Enum from smnp.error.note import NoteException +_semitone = 2**(1/12) class NotePitch(Enum): C = 0 @@ -17,21 +18,21 @@ class NotePitch(Enum): AIS = 10 H = 11 - def toFrequency(self): - return { - NotePitch.C: 16.35, - NotePitch.CIS: 17.32, - NotePitch.D: 18.35, - NotePitch.DIS: 19.45, - NotePitch.E: 20.60, - NotePitch.F: 21.83, - NotePitch.FIS: 23.12, - NotePitch.G: 24.50, - NotePitch.GIS: 25.96, - NotePitch.A: 27.50, - NotePitch.AIS: 29.17, - NotePitch.H: 30.87 - }[self] + # def toFrequency(self): + # return { + # NotePitch.C: 16.35, + # NotePitch.CIS: 17.32, + # NotePitch.D: 18.35, + # NotePitch.DIS: 19.45, + # NotePitch.E: 20.60, + # NotePitch.F: 21.83, + # NotePitch.FIS: 23.12, + # NotePitch.G: 24.50, + # NotePitch.GIS: 25.96, + # NotePitch.A: 27.50, + # NotePitch.AIS: 29.17, + # NotePitch.H: 30.87 + # }[self] def __str__(self): return self.name @@ -50,6 +51,14 @@ class NotePitch(Enum): raise NoteException(f"Note '{string}' does not exist") +class Tuning(object): + def __init__(self, a4=440): + self.tuning = { value: a4/_semitone**(57-i) for i, value in enumerate(NotePitch.__members__) } + + def __getitem__(self, item): + return self.tuning[str(item)] + + stringToPitch = { 'cb': NotePitch.H, 'c': NotePitch.C,