Improve support for functions #1
This commit is contained in:
@@ -1,10 +0,0 @@
|
|||||||
import pysine
|
|
||||||
from Note import Note, NotePitch
|
|
||||||
import time
|
|
||||||
|
|
||||||
BREAK = 0
|
|
||||||
|
|
||||||
|
|
||||||
#TODO: duration powinno byc w prawdziwych nutach: 4 = cwierc, 2 = pol etc.
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
from Tokenizer import tokenize, TokenType
|
|
||||||
from Parser import parse
|
|
||||||
from AST import *
|
from AST import *
|
||||||
from Note import Note
|
from Note import Note
|
||||||
from Error import RuntimeException
|
from Error import RuntimeException
|
||||||
@@ -25,20 +23,7 @@ def evaluateString(string, environment):
|
|||||||
value = value.replace('{' + k + '}', objectString(v)) #TODO: poprawic
|
value = value.replace('{' + k + '}', objectString(v)) #TODO: poprawic
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def objectString(obj):
|
|
||||||
if isinstance(obj, str):
|
|
||||||
return obj
|
|
||||||
if isinstance(obj, int):
|
|
||||||
return str(obj)
|
|
||||||
if isinstance(obj, Note):
|
|
||||||
return obj.note.name
|
|
||||||
if isinstance(obj, list):
|
|
||||||
return "(" + ", ".join([objectString(v) for v in obj]) + ")"
|
|
||||||
if isinstance(obj, float):
|
|
||||||
return f"{int(obj*100)}%"
|
|
||||||
if obj is None:
|
|
||||||
raise RuntimeException(None, f"Trying to interpret void")
|
|
||||||
raise RuntimeException(None, f"Don't know how to interpret {str(obj)}")
|
|
||||||
|
|
||||||
def evaluateNote(note, environment):
|
def evaluateNote(note, environment):
|
||||||
return note.value
|
return note.value
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
import numpy as np
|
|
||||||
import sounddevice as sd
|
|
||||||
from Note import Note
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
|
|
||||||
FS = 44100
|
|
||||||
|
|
||||||
|
|
||||||
def play(args, env):
|
|
||||||
if len(args) > 0 and isinstance(args[0], list):
|
|
||||||
playList(args[0], env)
|
|
||||||
return
|
|
||||||
playList(args, env)
|
|
||||||
|
|
||||||
def playList(notes, env):
|
|
||||||
bpm = env.findVariable("bpm", int)
|
|
||||||
if all(isinstance(x, Note) or isinstance(x, int) for x in notes):
|
|
||||||
for x in notes:
|
|
||||||
if isinstance(x, Note):
|
|
||||||
playNote(x, bpm)
|
|
||||||
if isinstance(x, int):
|
|
||||||
doPause(x, bpm)
|
|
||||||
|
|
||||||
def playNote(note, bpm):
|
|
||||||
frequency = note.toFrequency()
|
|
||||||
duration = 60 * 4 / note.duration / bpm
|
|
||||||
duration *= 1.5 if note.dot else 1
|
|
||||||
sine(frequency, duration)
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
def doPause(value, bpm):
|
|
||||||
time.sleep(60 * 4 / value / bpm)
|
|
||||||
|
|
||||||
def pause(args, env):
|
|
||||||
bpm = env.findVariable("bpm")
|
|
||||||
value = args[0]
|
|
||||||
doPause(value, bpm)
|
|
||||||
@@ -1,251 +0,0 @@
|
|||||||
import sys
|
|
||||||
from parser.Environment import objectString
|
|
||||||
from Note import *
|
|
||||||
import random
|
|
||||||
import Synth
|
|
||||||
import time
|
|
||||||
from Error import RuntimeException
|
|
||||||
from NoiseDetector import waitForSound
|
|
||||||
from Parser import parseNote
|
|
||||||
from Tokenizer import Token, TokenType, tokenizeNote
|
|
||||||
from functools import reduce
|
|
||||||
|
|
||||||
types = {
|
|
||||||
int: 'integer',
|
|
||||||
str: 'string',
|
|
||||||
list: 'list',
|
|
||||||
float: 'percent',
|
|
||||||
Note: 'note',
|
|
||||||
type(None): 'void'
|
|
||||||
}
|
|
||||||
|
|
||||||
class Environment():
|
|
||||||
def __init__(self, scopes, functions, methods):
|
|
||||||
self.scopes = scopes
|
|
||||||
self.functions = functions
|
|
||||||
self.methods = methods
|
|
||||||
self.customFunctions = {}
|
|
||||||
self.callStack = []
|
|
||||||
|
|
||||||
def findVariable(self, name, type=None):
|
|
||||||
for scope in reversed(self.scopes):
|
|
||||||
if name in scope:
|
|
||||||
value = scope[name]
|
|
||||||
if type is not None:
|
|
||||||
if isinstance(value, type):
|
|
||||||
return value
|
|
||||||
else:
|
|
||||||
return value
|
|
||||||
raise RuntimeException(None, f"Variable '{name}' is not declared" + ("" if type is None else f" (expected type: {types[type]})"))
|
|
||||||
|
|
||||||
def findVariableScope(self, name, type=None):
|
|
||||||
for scope in reversed(self.scopes):
|
|
||||||
if name in scope:
|
|
||||||
if type is not None:
|
|
||||||
if isinstance(scope[name], type):
|
|
||||||
return scope
|
|
||||||
else:
|
|
||||||
return scope
|
|
||||||
|
|
||||||
def doPrint(args, env):
|
|
||||||
print("".join([objectString(arg) for arg in args]))
|
|
||||||
|
|
||||||
def semitonesList(list):
|
|
||||||
for x in list:
|
|
||||||
if not isinstance(x, Note) and not isistance(x, int):
|
|
||||||
pass # invalid arguments
|
|
||||||
withoutPauses = tuple(filter(lambda x: isinstance(x, Note), list))
|
|
||||||
r = [Note.checkInterval(withoutPauses[i-1], withoutPauses[i]) for i, _ in enumerate(withoutPauses) if i != 0]
|
|
||||||
return r
|
|
||||||
|
|
||||||
def returnElementOrList(list):
|
|
||||||
return list[0] if len(list) == 1 else list
|
|
||||||
|
|
||||||
def semitones(args, env):
|
|
||||||
if len(args) > 0 and isinstance(args[0], list):
|
|
||||||
return returnElementOrList(semitonesList(args[0]))
|
|
||||||
return returnElementOrList(semitonesList(args))
|
|
||||||
|
|
||||||
def intervalList(list):
|
|
||||||
r = [intervalToString(x) for x in semitonesList(list)]
|
|
||||||
return returnElementOrList(r)
|
|
||||||
|
|
||||||
def interval(args, env):
|
|
||||||
if len(args) > 0 and isinstance(args[0], list):
|
|
||||||
return intervalList(args[0])
|
|
||||||
return intervalList(args)
|
|
||||||
|
|
||||||
def transposeTo(args, env):
|
|
||||||
if len(args) > 1 and isinstance(args[0], Note) and all(isinstance(x, list) for i, x in enumerate(args) if i != 0):
|
|
||||||
target = args[0]
|
|
||||||
result = []
|
|
||||||
for i, notes in enumerate(args):
|
|
||||||
if i == 0:
|
|
||||||
continue
|
|
||||||
if len(notes) > 0:
|
|
||||||
first = notes[0]
|
|
||||||
semitones = semitonesList([first, target])[0]
|
|
||||||
result.append([note.transpose(semitones) for note in notes if isinstance(note, Note)])
|
|
||||||
else:
|
|
||||||
result.append([])
|
|
||||||
return returnElementOrList(result)
|
|
||||||
else:
|
|
||||||
pass # not valid signature
|
|
||||||
|
|
||||||
|
|
||||||
def transpose(args, env):
|
|
||||||
if len(args) > 1 and isinstance(args[0], int) and all(isinstance(arg, list) for i, arg in enumerate(args) if i != 0):
|
|
||||||
value = args[0]
|
|
||||||
transposed = []
|
|
||||||
for i, arg in enumerate(args):
|
|
||||||
if i == 0:
|
|
||||||
continue
|
|
||||||
if not isinstance(arg, list):
|
|
||||||
return # is not list
|
|
||||||
transposed.append([note.transpose(value) for note in arg if isinstance(note, Note)])
|
|
||||||
return returnElementOrList(transposed)
|
|
||||||
if len(args) > 1 and all(isinstance(arg, Note) for i, arg in enumerate(args) if i != 0):
|
|
||||||
value = args[0]
|
|
||||||
transposed = [note.transpose(value) for i, note in enumerate(args) if i != 0]
|
|
||||||
return returnElementOrList(transposed)
|
|
||||||
else:
|
|
||||||
return # not valid signature
|
|
||||||
|
|
||||||
def objectType(args, env):
|
|
||||||
if len(args) == 1:
|
|
||||||
return types[type(args[0])]
|
|
||||||
else:
|
|
||||||
pass # not valid signature
|
|
||||||
|
|
||||||
def exit(args, env):
|
|
||||||
if len(args) == 1 and isinstance(args[0], int):
|
|
||||||
sys.exit(args[0])
|
|
||||||
else:
|
|
||||||
pass # not valid signature
|
|
||||||
|
|
||||||
def sleep(args, env):
|
|
||||||
if len(args) == 1 and isinstance(args[0], int):
|
|
||||||
time.sleep(args[0])
|
|
||||||
else:
|
|
||||||
pass # not valid signature
|
|
||||||
|
|
||||||
def rand(args, env):
|
|
||||||
if not all(isinstance(x, list) and len(x) == 2 and isinstance(x[0], float) for x in args):
|
|
||||||
return # not valid signature
|
|
||||||
if sum([x[0] for x in args]) != 1.0:
|
|
||||||
return # not sums to 100%
|
|
||||||
choice = random.random()
|
|
||||||
acc = 0
|
|
||||||
for e in args:
|
|
||||||
acc += e[0]
|
|
||||||
if choice <= acc:
|
|
||||||
return e[1]
|
|
||||||
|
|
||||||
def read(args, env):
|
|
||||||
if len(args) == 2 and isinstance(args[0], str) and isinstance(args[1], str):
|
|
||||||
print(args[0], end="")
|
|
||||||
value = input()
|
|
||||||
if args[1] == "integer":
|
|
||||||
try:
|
|
||||||
return int(value)
|
|
||||||
except ValueError as v:
|
|
||||||
pass # not int
|
|
||||||
elif args[1] == "string":
|
|
||||||
return value
|
|
||||||
elif args[1] == "note":
|
|
||||||
chars, token = tokenizeNote(value, 0, 0)
|
|
||||||
if chars == 0:
|
|
||||||
return # not note
|
|
||||||
return parseNote([token], None).value
|
|
||||||
else:
|
|
||||||
pass # invalid type
|
|
||||||
elif len(args) == 1 and isinstance(args[0], str):
|
|
||||||
print(args[0], end="")
|
|
||||||
return input()
|
|
||||||
elif len(args) == 0:
|
|
||||||
return input()
|
|
||||||
else:
|
|
||||||
pass # not valid signature
|
|
||||||
|
|
||||||
def changeDuration(args, env):
|
|
||||||
if len(args) == 2 and isinstance(args[0], Note) and isinstance(args[1], int):
|
|
||||||
return args[0].withDuration(args[1])
|
|
||||||
return # invalid signature
|
|
||||||
|
|
||||||
def changeOctave(args, env):
|
|
||||||
if len(args) == 2 and isinstance(args[0], Note) and isinstance(args[1], int):
|
|
||||||
return args[0].withOctave(args[1])
|
|
||||||
return # invalid signature
|
|
||||||
|
|
||||||
def tupletList(n, m, list):
|
|
||||||
return [note.withDuration(note.duration * n / m) for note in list]
|
|
||||||
|
|
||||||
def tuplet(args, env):
|
|
||||||
if len(args) > 2 and type(args[0]) == int and type(args[1]) == int and all(type(x) == Note for x in args[2:]):
|
|
||||||
n = args[0] # how many notes
|
|
||||||
m = args[1] # instead of number of notes (3-tuplet: 3 instead 2; 5-tuplet: 5 instead 4 etc.)
|
|
||||||
return returnElementOrList(tupletList(n, m, args[2:]))
|
|
||||||
elif len(args) == 3 and type(args[0]) == int and type(args[1]) == int and type(args[2]) == list and all(type(x) == Note for x in args[2]):
|
|
||||||
n = args[0]
|
|
||||||
m = args[1]
|
|
||||||
l = args[2]
|
|
||||||
return returnElementOrList(tupletList(n, m, l))
|
|
||||||
else:
|
|
||||||
pass # not valid signature
|
|
||||||
|
|
||||||
def combine(args, env):
|
|
||||||
if all(type(x) == list for x in args):
|
|
||||||
return reduce((lambda x, y: x + y), args)
|
|
||||||
|
|
||||||
def flat(args, env):
|
|
||||||
return _flat(args, [])
|
|
||||||
|
|
||||||
def _flat(input, output = []):
|
|
||||||
for item in input:
|
|
||||||
if type(item) == list:
|
|
||||||
_flat(item, output)
|
|
||||||
else:
|
|
||||||
output.append(item)
|
|
||||||
return output
|
|
||||||
|
|
||||||
def createEnvironment():
|
|
||||||
functions = {
|
|
||||||
'print': doPrint,
|
|
||||||
'synth': Synth.play,
|
|
||||||
'pause': Synth.pause,
|
|
||||||
'type': objectType,
|
|
||||||
'sample': sample,
|
|
||||||
'semitones': semitones,
|
|
||||||
'interval': interval,
|
|
||||||
'transpose': transpose,
|
|
||||||
'transposeTo': transposeTo,
|
|
||||||
'sleep': sleep,
|
|
||||||
'random': rand,
|
|
||||||
'changeDuration': changeDuration,
|
|
||||||
'changeOctave': changeOctave,
|
|
||||||
'wait': waitForSound,
|
|
||||||
'read': read,
|
|
||||||
'debug': lambda args, env: print(args),
|
|
||||||
'tuplet': tuplet,
|
|
||||||
'combine': combine,
|
|
||||||
'flat': flat,
|
|
||||||
'exit': exit
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
methods = {
|
|
||||||
str: {},
|
|
||||||
list: {},
|
|
||||||
float: {},
|
|
||||||
Note: {
|
|
||||||
'synth': Synth.play
|
|
||||||
},
|
|
||||||
type(None): {},
|
|
||||||
}
|
|
||||||
|
|
||||||
variables = {
|
|
||||||
"bpm": 120
|
|
||||||
}
|
|
||||||
|
|
||||||
return Environment([ variables ], functions, methods)
|
|
||||||
|
|
||||||
0
smnp/environment/__init__.py
Normal file
0
smnp/environment/__init__.py
Normal file
31
smnp/environment/environment.py
Normal file
31
smnp/environment/environment.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
from smnp.error.runtime import RuntimeException
|
||||||
|
|
||||||
|
|
||||||
|
class Environment():
|
||||||
|
def __init__(self, scopes, functions, methods):
|
||||||
|
self.scopes = scopes
|
||||||
|
self.functions = functions
|
||||||
|
self.methods = methods
|
||||||
|
self.customFunctions = {}
|
||||||
|
self.callStack = [] #TODO remove
|
||||||
|
|
||||||
|
def findVariable(self, name, type=None, pos=None):
|
||||||
|
for scope in reversed(self.scopes):
|
||||||
|
if name in scope:
|
||||||
|
value = scope[name]
|
||||||
|
if type is not None:
|
||||||
|
if isinstance(value, type):
|
||||||
|
return value
|
||||||
|
else:
|
||||||
|
return value
|
||||||
|
raise RuntimeException(pos, f"Variable '{name}' is not declared" + (
|
||||||
|
"" if type is None else f" (expected type: {type})"))
|
||||||
|
|
||||||
|
def findVariableScope(self, name, type=None):
|
||||||
|
for scope in reversed(self.scopes):
|
||||||
|
if name in scope:
|
||||||
|
if type is not None:
|
||||||
|
if isinstance(scope[name], type):
|
||||||
|
return scope
|
||||||
|
else:
|
||||||
|
return scope
|
||||||
48
smnp/environment/factory.py
Normal file
48
smnp/environment/factory.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import smnp.environment.function.list as l
|
||||||
|
from smnp.environment.environment import Environment
|
||||||
|
from smnp.environment.function import synth, base, interval, note, transposer, rand, mic
|
||||||
|
from smnp.environment.function.model import Function, ONLY_FUNCTION
|
||||||
|
from smnp.note.model import Note
|
||||||
|
|
||||||
|
|
||||||
|
def createEnvironment():
|
||||||
|
functions = {
|
||||||
|
'exit': Function(base.exit, ONLY_FUNCTION),
|
||||||
|
'print': Function(base.display, ONLY_FUNCTION),
|
||||||
|
'read': Function(base.read, ONLY_FUNCTION),
|
||||||
|
'type': Function(base.objectType, ONLY_FUNCTION),
|
||||||
|
'sleep': Function(base.sleep, ONLY_FUNCTION),
|
||||||
|
'synth': Function(synth.synth, ONLY_FUNCTION),
|
||||||
|
'pause': Function(synth.pause, ONLY_FUNCTION),
|
||||||
|
'changeDuration': Function(note.changeDuration, ONLY_FUNCTION),
|
||||||
|
'changeOctave': Function(note.changeOctave, ONLY_FUNCTION),
|
||||||
|
'semitones': Function(interval.semitones, ONLY_FUNCTION),
|
||||||
|
'interval': Function(interval.interval, ONLY_FUNCTION),
|
||||||
|
'transpose': Function(transposer.transpose, ONLY_FUNCTION),
|
||||||
|
'transposeTo': Function(transposer.transposeTo, ONLY_FUNCTION),
|
||||||
|
'random': Function(rand.random, ONLY_FUNCTION),
|
||||||
|
# 'sample': sample,
|
||||||
|
'wait': Function(mic.wait, ONLY_FUNCTION),
|
||||||
|
'tuplet': Function(note.tuplet, ONLY_FUNCTION),
|
||||||
|
'combine': Function(l.combine, ONLY_FUNCTION),
|
||||||
|
'flat': Function(l.flat, ONLY_FUNCTION),
|
||||||
|
'debug': Function(lambda args, env: print(args), ONLY_FUNCTION),
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
methods = {
|
||||||
|
str: {},
|
||||||
|
list: {},
|
||||||
|
float: {},
|
||||||
|
Note: {
|
||||||
|
'synth': synth.synth
|
||||||
|
},
|
||||||
|
type(None): {},
|
||||||
|
}
|
||||||
|
|
||||||
|
variables = {
|
||||||
|
"bpm": 120
|
||||||
|
}
|
||||||
|
|
||||||
|
return Environment([ variables ], functions, methods)
|
||||||
|
|
||||||
0
smnp/environment/function/__init__.py
Normal file
0
smnp/environment/function/__init__.py
Normal file
55
smnp/environment/function/base.py
Normal file
55
smnp/environment/function/base.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
def display(args, env):
|
||||||
|
print("".join([arg.stringify() for arg in args]))
|
||||||
|
|
||||||
|
|
||||||
|
def objectType(args, env):
|
||||||
|
if len(args) == 1:
|
||||||
|
return args[0].stringify()
|
||||||
|
else:
|
||||||
|
pass # not valid signature
|
||||||
|
|
||||||
|
|
||||||
|
def exit(args, env):
|
||||||
|
if len(args) == 1 and isinstance(args[0], int):
|
||||||
|
sys.exit(args[0])
|
||||||
|
else:
|
||||||
|
pass # not valid signature
|
||||||
|
|
||||||
|
|
||||||
|
def sleep(args, env):
|
||||||
|
if len(args) == 1 and isinstance(args[0], int):
|
||||||
|
time.sleep(args[0])
|
||||||
|
else:
|
||||||
|
pass # not valid signature
|
||||||
|
|
||||||
|
|
||||||
|
def read(args, env):
|
||||||
|
if len(args) == 2 and isinstance(args[0], str) and isinstance(args[1], str):
|
||||||
|
print(args[0], end="")
|
||||||
|
value = input()
|
||||||
|
if args[1] == "integer":
|
||||||
|
try:
|
||||||
|
return int(value)
|
||||||
|
except ValueError as v:
|
||||||
|
pass # not int
|
||||||
|
elif args[1] == "string":
|
||||||
|
return value
|
||||||
|
# TODO: note - wydzielić parsowanie nut do osobnej funkcji w pakiecie smnp.note
|
||||||
|
# elif args[1] == "note":
|
||||||
|
# chars, token = tokenizeNote(value, 0, 0)
|
||||||
|
# if chars == 0:
|
||||||
|
# return # not note
|
||||||
|
# return parseNote([token], None).value
|
||||||
|
else:
|
||||||
|
pass # invalid type
|
||||||
|
elif len(args) == 1 and isinstance(args[0], str):
|
||||||
|
print(args[0], end="")
|
||||||
|
return input()
|
||||||
|
elif len(args) == 0:
|
||||||
|
return input()
|
||||||
|
else:
|
||||||
|
pass # not valid signature
|
||||||
27
smnp/environment/function/interval.py
Normal file
27
smnp/environment/function/interval.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
from smnp.environment.function.tools import returnElementOrList
|
||||||
|
from smnp.note.interval import intervalToString
|
||||||
|
from smnp.note.model import Note
|
||||||
|
|
||||||
|
|
||||||
|
def interval(args, env):
|
||||||
|
if len(args) > 0 and isinstance(args[0], list):
|
||||||
|
return intervalList(args[0])
|
||||||
|
return intervalList(args)
|
||||||
|
|
||||||
|
|
||||||
|
def intervalList(list):
|
||||||
|
r = [intervalToString(x) for x in semitonesList(list)]
|
||||||
|
return returnElementOrList(r)
|
||||||
|
|
||||||
|
def semitonesList(list):
|
||||||
|
for x in list:
|
||||||
|
if not isinstance(x, Note) and not isinstance(x, int):
|
||||||
|
pass # invalid arguments
|
||||||
|
withoutPauses = tuple(filter(lambda x: isinstance(x, Note), list))
|
||||||
|
r = [Note.checkInterval(withoutPauses[i-1], withoutPauses[i]) for i, _ in enumerate(withoutPauses) if i != 0]
|
||||||
|
return r
|
||||||
|
|
||||||
|
def semitones(args, env):
|
||||||
|
if len(args) > 0 and isinstance(args[0], list):
|
||||||
|
return returnElementOrList(semitonesList(args[0]))
|
||||||
|
return returnElementOrList(semitonesList(args))
|
||||||
19
smnp/environment/function/list.py
Normal file
19
smnp/environment/function/list.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from functools import reduce
|
||||||
|
|
||||||
|
|
||||||
|
def combine(args, env):
|
||||||
|
if all(type(x) == list for x in args):
|
||||||
|
return reduce((lambda x, y: x + y), args)
|
||||||
|
|
||||||
|
|
||||||
|
def flat(args, env):
|
||||||
|
return _flat(args, [])
|
||||||
|
|
||||||
|
|
||||||
|
def _flat(input, output = []):
|
||||||
|
for item in input:
|
||||||
|
if type(item) == list:
|
||||||
|
_flat(item, output)
|
||||||
|
else:
|
||||||
|
output.append(item)
|
||||||
|
return output
|
||||||
3
smnp/environment/function/mic.py
Normal file
3
smnp/environment/function/mic.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
def wait(args, env):
|
||||||
|
pass
|
||||||
18
smnp/environment/function/model.py
Normal file
18
smnp/environment/function/model.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from enum import Enum, auto
|
||||||
|
|
||||||
|
|
||||||
|
class FunctionType(Enum):
|
||||||
|
FUNCTION = auto()
|
||||||
|
METHOD = auto()
|
||||||
|
|
||||||
|
|
||||||
|
class Function:
|
||||||
|
def __init__(self, signature, function):
|
||||||
|
self.signature = signature
|
||||||
|
self.function = function
|
||||||
|
|
||||||
|
def call(self, env, args):
|
||||||
|
result = self.signature(args)
|
||||||
|
if result[0]:
|
||||||
|
return self.function(env, *result[1:])
|
||||||
|
# todo: raise illegal signature exception or something
|
||||||
31
smnp/environment/function/note.py
Normal file
31
smnp/environment/function/note.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
from smnp.environment.function.tools import returnElementOrList
|
||||||
|
|
||||||
|
|
||||||
|
def changeDuration(args, env):
|
||||||
|
if len(args) == 2 and isinstance(args[0], Note) and isinstance(args[1], int):
|
||||||
|
return args[0].withDuration(args[1])
|
||||||
|
return # invalid signature
|
||||||
|
|
||||||
|
|
||||||
|
def changeOctave(args, env):
|
||||||
|
if len(args) == 2 and isinstance(args[0], Note) and isinstance(args[1], int):
|
||||||
|
return args[0].withOctave(args[1])
|
||||||
|
return # invalid signature
|
||||||
|
|
||||||
|
|
||||||
|
def tupletList(n, m, list):
|
||||||
|
return [note.withDuration(note.duration * n / m) for note in list]
|
||||||
|
|
||||||
|
|
||||||
|
def tuplet(args, env):
|
||||||
|
if len(args) > 2 and type(args[0]) == int and type(args[1]) == int and all(type(x) == Note for x in args[2:]):
|
||||||
|
n = args[0] # how many notes
|
||||||
|
m = args[1] # instead of number of notes (3-tuplet: 3 instead 2; 5-tuplet: 5 instead 4 etc.)
|
||||||
|
return returnElementOrList(tupletList(n, m, args[2:]))
|
||||||
|
elif len(args) == 3 and type(args[0]) == int and type(args[1]) == int and type(args[2]) == list and all(type(x) == Note for x in args[2]):
|
||||||
|
n = args[0]
|
||||||
|
m = args[1]
|
||||||
|
l = args[2]
|
||||||
|
return returnElementOrList(tupletList(n, m, l))
|
||||||
|
else:
|
||||||
|
pass # not valid signature
|
||||||
17
smnp/environment/function/rand.py
Normal file
17
smnp/environment/function/rand.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import random as r
|
||||||
|
|
||||||
|
|
||||||
|
def random(args, env):
|
||||||
|
if not all(isinstance(x, list) and len(x) == 2 and isinstance(x[0], float) for x in args):
|
||||||
|
return # not valid signature
|
||||||
|
if sum([x[0] for x in args]) != 1.0:
|
||||||
|
return # not sums to 100%
|
||||||
|
choice = r.random()
|
||||||
|
acc = 0
|
||||||
|
for e in args:
|
||||||
|
acc += e[0]
|
||||||
|
if choice <= acc:
|
||||||
|
return e[1]
|
||||||
|
|
||||||
|
|
||||||
|
#TODO: sample
|
||||||
93
smnp/environment/function/signature.py
Normal file
93
smnp/environment/function/signature.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# from smnp.type.model import Type
|
||||||
|
#
|
||||||
|
from smnp.type.model import Type
|
||||||
|
|
||||||
|
|
||||||
|
class Matcher:
|
||||||
|
def __init__(self, objectType, matcher):
|
||||||
|
self.type = objectType
|
||||||
|
self.matcher = matcher
|
||||||
|
|
||||||
|
def match(self, value):
|
||||||
|
if self.type is not None and self.type != value.type:
|
||||||
|
return False
|
||||||
|
return self.matcher(value)
|
||||||
|
|
||||||
|
def andWith(self, matcher):
|
||||||
|
if self.type != matcher.type:
|
||||||
|
raise RuntimeError("Support types of matches are not the same")
|
||||||
|
return Matcher(self.type, lambda x: self.matcher(x) and matcher.matcher(x))
|
||||||
|
|
||||||
|
|
||||||
|
def varargSignature(varargMatcher, *basicSignature):
|
||||||
|
def check(args):
|
||||||
|
if len(basicSignature) > len(args):
|
||||||
|
return doesNotMatchVararg(basicSignature)
|
||||||
|
|
||||||
|
for i in range(len(basicSignature)):
|
||||||
|
if not basicSignature[i].match(args[i]):
|
||||||
|
return doesNotMatchVararg(basicSignature)
|
||||||
|
|
||||||
|
for i in range(len(basicSignature), len(args)):
|
||||||
|
if not varargMatcher.match(args[i]):
|
||||||
|
return doesNotMatchVararg(basicSignature)
|
||||||
|
|
||||||
|
return True, (*args[:len(basicSignature)]), args[len(basicSignature):]
|
||||||
|
return check
|
||||||
|
|
||||||
|
|
||||||
|
def doesNotMatchVararg(basicSignature):
|
||||||
|
return (False, *[None for n in basicSignature], None)
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
def signature(*signature):
|
||||||
|
def check(args):
|
||||||
|
if len(signature) != len(args):
|
||||||
|
return doesNotMatch(signature)
|
||||||
|
|
||||||
|
for s, a in zip(signature, args):
|
||||||
|
if not s.match(a):
|
||||||
|
return doesNotMatch(signature)
|
||||||
|
|
||||||
|
return (True, *args)
|
||||||
|
|
||||||
|
return check
|
||||||
|
|
||||||
|
|
||||||
|
def doesNotMatch(sign):
|
||||||
|
return (False, *[None for n in sign])
|
||||||
|
|
||||||
|
|
||||||
|
def ofTypes(*types):
|
||||||
|
def check(value):
|
||||||
|
return value.type in types
|
||||||
|
return Matcher(None, check)
|
||||||
|
|
||||||
|
|
||||||
|
def listOf(*types):
|
||||||
|
def check(value):
|
||||||
|
return len([item for item in value.value if not item.type in types]) == 0
|
||||||
|
|
||||||
|
return Matcher(Type.LIST, check)
|
||||||
|
|
||||||
|
|
||||||
|
def listMatches(*pattern):
|
||||||
|
def check(value):
|
||||||
|
return signature(pattern)(value.value)[0]
|
||||||
|
|
||||||
|
return Matcher(Type.LIST, check)
|
||||||
|
|
||||||
|
|
||||||
|
def recursiveListMatcher(matcher):
|
||||||
|
if matcher.type == Type.LIST:
|
||||||
|
raise RuntimeError(f"Passed matcher will be handling non-list types, so it cannot have type set to {Type.LIST}")
|
||||||
|
|
||||||
|
def check(value):
|
||||||
|
if value.type != Type.LIST:
|
||||||
|
return matcher.match(value)
|
||||||
|
for item in value.value:
|
||||||
|
return check(item)
|
||||||
|
|
||||||
|
return Matcher(Type.LIST, check)
|
||||||
|
|
||||||
6
smnp/environment/function/synth.py
Normal file
6
smnp/environment/function/synth.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
def synth(args, env):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def pause(args, env):
|
||||||
|
pass
|
||||||
38
smnp/environment/function/tools.py
Normal file
38
smnp/environment/function/tools.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
from smnp.environment.function.model import Function
|
||||||
|
|
||||||
|
|
||||||
|
def returnElementOrList(list):
|
||||||
|
return list[0] if len(list) == 1 else list
|
||||||
|
|
||||||
|
def combineFunctions(*functions):
|
||||||
|
if len(functions) == 0:
|
||||||
|
raise RuntimeError("Must be passed one function at least")
|
||||||
|
|
||||||
|
def signature(args):
|
||||||
|
ret = None
|
||||||
|
for fun in functions:
|
||||||
|
ret = fun.signature(args)
|
||||||
|
if ret[0] == True:
|
||||||
|
return ret
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def function(env, *args):
|
||||||
|
originalArgs = removeFirstLevelNesting(args)
|
||||||
|
for fun in functions:
|
||||||
|
if fun.signature(originalArgs)[0]:
|
||||||
|
return fun.function(env, *args)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
return Function(signature, function)
|
||||||
|
|
||||||
|
def removeFirstLevelNesting(l):
|
||||||
|
flat = []
|
||||||
|
for item in l:
|
||||||
|
if type(item) == list:
|
||||||
|
for i in item:
|
||||||
|
flat.append(i)
|
||||||
|
else:
|
||||||
|
flat.append(item)
|
||||||
|
|
||||||
|
return flat
|
||||||
41
smnp/environment/function/transposer.py
Normal file
41
smnp/environment/function/transposer.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
from smnp.environment.function.interval import semitonesList
|
||||||
|
from smnp.environment.function.tools import returnElementOrList
|
||||||
|
from smnp.note.model import Note
|
||||||
|
|
||||||
|
|
||||||
|
def transposeTo(args, env):
|
||||||
|
if len(args) > 1 and isinstance(args[0], Note) and all(isinstance(x, list) for i, x in enumerate(args) if i != 0):
|
||||||
|
target = args[0]
|
||||||
|
result = []
|
||||||
|
for i, notes in enumerate(args):
|
||||||
|
if i == 0:
|
||||||
|
continue
|
||||||
|
if len(notes) > 0:
|
||||||
|
first = notes[0]
|
||||||
|
semitones = semitonesList([first, target])[0]
|
||||||
|
result.append([note.transpose(semitones) for note in notes if isinstance(note, Note)])
|
||||||
|
else:
|
||||||
|
result.append([])
|
||||||
|
return returnElementOrList(result)
|
||||||
|
else:
|
||||||
|
pass # not valid signature
|
||||||
|
|
||||||
|
|
||||||
|
def transpose(args, env):
|
||||||
|
if len(args) > 1 and isinstance(args[0], int) and all(
|
||||||
|
isinstance(arg, list) for i, arg in enumerate(args) if i != 0):
|
||||||
|
value = args[0]
|
||||||
|
transposed = []
|
||||||
|
for i, arg in enumerate(args):
|
||||||
|
if i == 0:
|
||||||
|
continue
|
||||||
|
if not isinstance(arg, list):
|
||||||
|
return # is not list
|
||||||
|
transposed.append([note.transpose(value) for note in arg if isinstance(note, Note)])
|
||||||
|
return returnElementOrList(transposed)
|
||||||
|
if len(args) > 1 and all(isinstance(arg, Note) for i, arg in enumerate(args) if i != 0):
|
||||||
|
value = args[0]
|
||||||
|
transposed = [note.transpose(value) for i, note in enumerate(args) if i != 0]
|
||||||
|
return returnElementOrList(transposed)
|
||||||
|
else:
|
||||||
|
return # not valid signature
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
class RuntimeException(Exception):
|
class RuntimeException(Exception):
|
||||||
def __init__(self, pos, msg):
|
def __init__(self, pos, msg):
|
||||||
posStr = "" if pos is None else f"[line {pos[0]+1}, col {pos[1]+1}]"
|
posStr = "" if pos is None else f" [line {pos[0]+1}, col {pos[1]+1}]"
|
||||||
self.msg = f"Syntax error {posStr}:\n{msg}"
|
self.msg = f"Runtime error{posStr}:\n{msg}"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
class SyntaxException(Exception):
|
class SyntaxException(Exception):
|
||||||
def __init__(self, pos, msg):
|
def __init__(self, pos, msg):
|
||||||
posStr = "" if pos is None else f"[line {pos[0]+1}, col {pos[1]+1}]"
|
posStr = "" if pos is None else f" [line {pos[0]+1}, col {pos[1]+1}]"
|
||||||
self.msg = f"Syntax error {posStr}:\n{msg}"
|
self.msg = f"Syntax error{posStr}:\n{msg}"
|
||||||
|
|||||||
0
smnp/mic/__init__.py
Normal file
0
smnp/mic/__init__.py
Normal file
0
smnp/mic/detector/__init__.py
Normal file
0
smnp/mic/detector/__init__.py
Normal file
@@ -1,8 +1,9 @@
|
|||||||
|
from collections import deque
|
||||||
|
from statistics import mean
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import sounddevice as sd
|
import sounddevice as sd
|
||||||
import os
|
|
||||||
from collections import deque
|
|
||||||
from statistics import mean
|
|
||||||
|
|
||||||
class NoiseDetector:
|
class NoiseDetector:
|
||||||
def __init__(self, noiseTreshold=300, silenceTreshold=10, bufferSize=20):
|
def __init__(self, noiseTreshold=300, silenceTreshold=10, bufferSize=20):
|
||||||
@@ -42,7 +43,8 @@ class NoiseDetector:
|
|||||||
print(f"Mic level: {self.mean}", end="\r")
|
print(f"Mic level: {self.mean}", end="\r")
|
||||||
sd.sleep(200)
|
sd.sleep(200)
|
||||||
|
|
||||||
|
|
||||||
|
#TODO: do environment
|
||||||
def waitForSound(args, env):
|
def waitForSound(args, env):
|
||||||
noiseTreshold = 300
|
noiseTreshold = 300
|
||||||
silenceTreshold = 10
|
silenceTreshold = 10
|
||||||
0
smnp/runtime/__init__.py
Normal file
0
smnp/runtime/__init__.py
Normal file
0
smnp/synth/__init__.py
Normal file
0
smnp/synth/__init__.py
Normal file
24
smnp/synth/player.py
Normal file
24
smnp/synth/player.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import time
|
||||||
|
|
||||||
|
from smnp.note.model import Note
|
||||||
|
from smnp.synth.wave import sine
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
12
smnp/synth/wave.py
Normal file
12
smnp/synth/wave.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import time
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import sounddevice as sd
|
||||||
|
|
||||||
|
FS = 44100
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
0
smnp/type/__init__.py
Normal file
0
smnp/type/__init__.py
Normal file
23
smnp/type/model.py
Normal file
23
smnp/type/model.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
from smnp.error.runtime import RuntimeException
|
||||||
|
from smnp.note.model import Note
|
||||||
|
|
||||||
|
|
||||||
|
class Type(Enum):
|
||||||
|
INTEGER = (int, lambda x: str(x))
|
||||||
|
STRING = (str, lambda x: x)
|
||||||
|
LIST = (list, lambda x: f"({', '.join([e.stringify() for e in x])})")
|
||||||
|
PERCENT = (float, lambda x: f"{int(x * 100)}%")
|
||||||
|
NOTE = (Note, lambda x: x.note.name)
|
||||||
|
VOID = (None, lambda x: _failStringify(x))
|
||||||
|
|
||||||
|
def stringify(self, element):
|
||||||
|
return self.value[1](element)
|
||||||
|
|
||||||
|
|
||||||
|
def _failStringify(obj):
|
||||||
|
raise RuntimeException(None, f"Not able to interpret '{obj.type.name()}'")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
17
smnp/type/value.py
Normal file
17
smnp/type/value.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
class Value:
|
||||||
|
def __init__(self, objectType, value):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
if type(value) == objectType.value[0]:
|
||||||
|
self.type = objectType
|
||||||
|
else:
|
||||||
|
raise RuntimeError(f"Invalid type '{objectType.name}' for value '{value}'")
|
||||||
|
|
||||||
|
def stringify(self):
|
||||||
|
return self.type.stringify(self.value)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.type.name}({self.stringify()})"
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.__str__()
|
||||||
Reference in New Issue
Block a user