Compare commits
3 Commits
create-vir
...
add-cli
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24bcb25bee | ||
|
|
83ef3f6888 | ||
|
|
3061dae723 |
0
smnp/cli/__init__.py
Normal file
0
smnp/cli/__init__.py
Normal file
23
smnp/cli/parser.py
Normal file
23
smnp/cli/parser.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import argparse
|
||||
|
||||
VERSION = "0.1"
|
||||
DESCRIPTION = """
|
||||
Simple Music Notation Processor is a command line tool enabling you to do some music stuff using custom domain-specific language.
|
||||
"""
|
||||
|
||||
class CliParser(object):
|
||||
def __init__(self):
|
||||
self.parser = argparse.ArgumentParser(description=DESCRIPTION)
|
||||
self.parser.add_argument('file', nargs='*', help='a file containing SMNP code')
|
||||
self.parser.add_argument('-c', '--code', action='append', default=[], type=str, help='a string with SMNP code')
|
||||
self.parser.add_argument('-m', '--mic', action='store_true', help='test microphone level')
|
||||
self.parser.add_argument('-C', '--config', type=argparse.FileType('-r'), help='a file containing settings (not implemented yet)')
|
||||
self.parser.add_argument('-p', '--params', action='append', help='pass arguments to program (not implemented yet)')
|
||||
self.parser.add_argument('-v', '--version', action='version')
|
||||
self.parser.add_argument('--tokens', action='store_true', help='print tokens of parsed code')
|
||||
self.parser.add_argument('--ast', action='store_true', help='print abstract syntax tree of parsed code')
|
||||
self.parser.add_argument('--dry-run', action='store_true', help='don\'t execute passed code')
|
||||
self.parser.version = VERSION
|
||||
|
||||
def parse(self):
|
||||
return self.parser.parse_args()
|
||||
29
smnp/main.py
29
smnp/main.py
@@ -1,17 +1,38 @@
|
||||
import sys
|
||||
|
||||
from smnp.cli.parser import CliParser
|
||||
from smnp.error.base import SmnpException
|
||||
from smnp.library.loader import loadStandardLibrary
|
||||
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
|
||||
Interpreter.interpretFile(file, printTokens=args.tokens, printAst=args.ast, execute=not args.dry_run, baseEnvironment=stdLibraryEnv)
|
||||
|
||||
|
||||
def interpretString(args, string):
|
||||
stdLibraryEnv = loadStandardLibrary() if not args.dry_run else None
|
||||
Interpreter.interpretString(string, printTokens=args.tokens, printAst=args.ast, execute=not args.dry_run, baseEnvironment=stdLibraryEnv, source='<cli>')
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
stdLibraryEnv = loadStandardLibrary()
|
||||
Interpreter.interpretFile(sys.argv[1], printTokens=False, printAst=False, execute=True, baseEnvironment=stdLibraryEnv)
|
||||
parser = CliParser()
|
||||
args = parser.parse()
|
||||
|
||||
if args.mic:
|
||||
nd = NoiseDetector()
|
||||
nd.test()
|
||||
|
||||
for code in args.code:
|
||||
interpretString(args, code)
|
||||
|
||||
for file in args.file:
|
||||
interpretFile(args, file)
|
||||
|
||||
except SmnpException as e:
|
||||
print(e.message())
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("Program interrupted")
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user