1 Commits

Author SHA1 Message Date
Bartłomiej Pluta
24bcb25bee Enable tuning 2019-09-06 22:59:09 +02:00
5 changed files with 56 additions and 31 deletions

View File

@@ -1,11 +1,9 @@
import sys from smnp.cli.parser import CliParser
import time
from smnp.error.base import SmnpException from smnp.error.base import SmnpException
from smnp.library.loader import loadStandardLibrary 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.module.mic.lib.detector.noise import NoiseDetector
from smnp.program.interpreter import Interpreter
def interpretFile(args, file): def interpretFile(args, file):
stdLibraryEnv = loadStandardLibrary() if not args.dry_run else None stdLibraryEnv = loadStandardLibrary() if not args.dry_run else None

View File

@@ -12,6 +12,7 @@ DEFAULT_BPM = 120
DEFAULT_OVERTONES = [0.4, 0.3, 0.1, 0.1, 0.1] DEFAULT_OVERTONES = [0.4, 0.3, 0.1, 0.1, 0.1]
DEFAULT_DECAY = 4 DEFAULT_DECAY = 4
DEFAULT_ATTACK = 100 DEFAULT_ATTACK = 100
DEFAULT_TUNING = 440
def getBpm(config): def getBpm(config):
@@ -72,16 +73,29 @@ def getAttack(config):
return DEFAULT_ATTACK 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: class Config:
def __init__(self, bpm, overtones, decay, attack): def __init__(self, bpm, overtones, decay, attack, tuning):
self.bpm = bpm self.bpm = bpm
self.overtones = overtones self.overtones = overtones
self.decay = decay self.decay = decay
self.attack = attack self.attack = attack
self.tuning = tuning
@staticmethod @staticmethod
def default(): 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)) _signature1 = varargSignature(listOf(Type.NOTE, Type.INTEGER))
@@ -114,8 +128,9 @@ def __function3(config, notes):
overtones = getOvertones(config) overtones = getOvertones(config)
decay = getDecay(config) decay = getDecay(config)
attack = getAttack(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)) _signature4 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER), ofType(Type.MAP))
@@ -128,8 +143,9 @@ def __function4(config, notes):
overtones = getOvertones(config) overtones = getOvertones(config)
decay = getDecay(config) decay = getDecay(config)
attack = getAttack(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( function = CombinedFunction(

View File

@@ -4,6 +4,7 @@ import matplotlib.pyplot as plt
import numpy as np import numpy as np
import sounddevice as sd import sounddevice as sd
from smnp.note.pitch import Tuning
from smnp.type.model import Type from smnp.type.model import Type
FS = 44100 FS = 44100
@@ -25,7 +26,8 @@ def play(wave):
def compilePolyphony(notes, config): 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)) return sum(adjustSize(compiledLines))
@@ -35,17 +37,17 @@ def adjustSize(compiledLines):
return [np.concatenate([line, np.zeros(maxSize - len(line))]) for line in compiledLines] return [np.concatenate([line, np.zeros(maxSize - len(line))]) for line in compiledLines]
def compileNotes(notes, config): def compileNotes(notes, config, tuning):
dispatcher = { 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) Type.INTEGER: lambda note, overtones: silenceForPause(note.value, config)
} }
return np.concatenate([dispatcher[note.type](note, config) for note in notes]) return np.concatenate([dispatcher[note.type](note, config) for note in notes])
def sineForNote(note, config): def sineForNote(note, config, tuning):
frequency = note.toFrequency() frequency = note.toFrequency(tuning)
duration = 60 * 4 / note.duration / config.bpm duration = 60 * 4 / note.duration / config.bpm
duration *= 1.5 if note.dot else 1 duration *= 1.5 if note.dot else 1
return sound(frequency, duration, config) return sound(frequency, duration, config)

View File

@@ -16,8 +16,8 @@ class Note:
self.duration = duration self.duration = duration
self.dot = dot self.dot = dot
def toFrequency(self): def toFrequency(self, tuning):
return self.note.toFrequency() * 2 ** self.octave return tuning[self.note] * 2 ** self.octave
def transpose(self, interval): def transpose(self, interval):
origIntRepr = self._intRepr() origIntRepr = self._intRepr()

View File

@@ -2,6 +2,7 @@ from enum import Enum
from smnp.error.note import NoteException from smnp.error.note import NoteException
_semitone = 2**(1/12)
class NotePitch(Enum): class NotePitch(Enum):
C = 0 C = 0
@@ -17,21 +18,21 @@ class NotePitch(Enum):
AIS = 10 AIS = 10
H = 11 H = 11
def toFrequency(self): # def toFrequency(self):
return { # return {
NotePitch.C: 16.35, # NotePitch.C: 16.35,
NotePitch.CIS: 17.32, # NotePitch.CIS: 17.32,
NotePitch.D: 18.35, # NotePitch.D: 18.35,
NotePitch.DIS: 19.45, # NotePitch.DIS: 19.45,
NotePitch.E: 20.60, # NotePitch.E: 20.60,
NotePitch.F: 21.83, # NotePitch.F: 21.83,
NotePitch.FIS: 23.12, # NotePitch.FIS: 23.12,
NotePitch.G: 24.50, # NotePitch.G: 24.50,
NotePitch.GIS: 25.96, # NotePitch.GIS: 25.96,
NotePitch.A: 27.50, # NotePitch.A: 27.50,
NotePitch.AIS: 29.17, # NotePitch.AIS: 29.17,
NotePitch.H: 30.87 # NotePitch.H: 30.87
}[self] # }[self]
def __str__(self): def __str__(self):
return self.name return self.name
@@ -50,6 +51,14 @@ class NotePitch(Enum):
raise NoteException(f"Note '{string}' does not exist") 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 = { stringToPitch = {
'cb': NotePitch.H, 'cb': NotePitch.H,
'c': NotePitch.C, 'c': NotePitch.C,