Create some fundamental functions
This commit is contained in:
13
Audio.py
Normal file
13
Audio.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import pysine
|
||||||
|
from Note import Note, NotePitch
|
||||||
|
import time
|
||||||
|
|
||||||
|
BREAK = 0
|
||||||
|
|
||||||
|
def playNote(note, bpm):
|
||||||
|
frequency = note.toFrequency()
|
||||||
|
duration = 60 * 4 / note.duration / bpm
|
||||||
|
pysine.sine(frequency, duration)
|
||||||
|
#TODO: duration powinno byc w prawdziwych nutach: 4 = cwierc, 2 = pol etc.
|
||||||
|
|
||||||
|
|
||||||
54
Evaulator.py
54
Evaulator.py
@@ -1,9 +1,11 @@
|
|||||||
|
import os
|
||||||
from Tokenizer import tokenize, TokenType
|
from Tokenizer import tokenize, TokenType
|
||||||
from Parser import parse
|
from Parser import parse
|
||||||
from AST import *
|
from AST import *
|
||||||
import sys
|
import sys
|
||||||
from Note import *
|
from Note import *
|
||||||
import random
|
import random
|
||||||
|
import Midi
|
||||||
|
|
||||||
class RuntimeException(Exception):
|
class RuntimeException(Exception):
|
||||||
pass
|
pass
|
||||||
@@ -48,7 +50,7 @@ def evaluateFunctionCall(functionCall, environment):
|
|||||||
arguments = evaluateList(functionCall.arguments, environment)
|
arguments = evaluateList(functionCall.arguments, environment)
|
||||||
for name, definition in environment.functions.items():
|
for name, definition in environment.functions.items():
|
||||||
if name == function:
|
if name == function:
|
||||||
return definition(arguments)
|
return definition(arguments, environment)
|
||||||
raise RuntimeException(f"Function '{function}' does not exist")
|
raise RuntimeException(f"Function '{function}' does not exist")
|
||||||
|
|
||||||
|
|
||||||
@@ -129,7 +131,7 @@ def evaluate(input, environment):
|
|||||||
if isinstance(input, IdentifierNode):
|
if isinstance(input, IdentifierNode):
|
||||||
return evaluateIdentifier(input, environment)
|
return evaluateIdentifier(input, environment)
|
||||||
|
|
||||||
def rand(args):
|
def rand(args, env):
|
||||||
if len(args) == 1 and isinstance(args[0], list):
|
if len(args) == 1 and isinstance(args[0], list):
|
||||||
return args[0][int(random.uniform(0, len(args[0])))]
|
return args[0][int(random.uniform(0, len(args[0])))]
|
||||||
|
|
||||||
@@ -144,16 +146,52 @@ def objectString(obj):
|
|||||||
return "(" + ", ".join([objectString(v) for v in obj]) + ")"
|
return "(" + ", ".join([objectString(v) for v in obj]) + ")"
|
||||||
raise RuntimeException(f"Don't know how to interpret {str(obj)}")
|
raise RuntimeException(f"Don't know how to interpret {str(obj)}")
|
||||||
|
|
||||||
def prt(args):
|
def prt(args, env):
|
||||||
print("".join([objectString(arg) for arg in args]))
|
print("".join([objectString(arg) for arg in args]))
|
||||||
|
|
||||||
if __name__ == "__main__":
|
def semitonesList(list):
|
||||||
|
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[0] if len(r) == 1 else r
|
||||||
|
|
||||||
|
def semitones(args, env):
|
||||||
|
if len(args) > 0 and isinstance(args[0], list):
|
||||||
|
return semitonesList(args[0])
|
||||||
|
return semitonesList(args)
|
||||||
|
|
||||||
|
def intervalList(list):
|
||||||
|
r = [intervalToString(x) for x in list]
|
||||||
|
return r[0] if len(r) == 1 else r
|
||||||
|
|
||||||
|
def interval(args, env):
|
||||||
|
if len(args) > 0 and isinstance(args[0], list):
|
||||||
|
return intervalList(args[0])
|
||||||
|
return intervalList(args)
|
||||||
|
|
||||||
|
def transpose(args, env):
|
||||||
|
if len(args) > 1 and isinstance(args[0], int):
|
||||||
|
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 transposed
|
||||||
|
else:
|
||||||
|
return # not valid signature
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
functions = {
|
functions = {
|
||||||
'print': prt,
|
'print': prt,
|
||||||
'midi': lambda args: print(":".join([str(type(arg)) for arg in args])),
|
'midi': Midi.play,
|
||||||
'type': lambda args: print(type(args[0])),
|
'pause': Midi.pause,
|
||||||
'random': rand
|
'type': lambda args, env: print(type(args[0])),
|
||||||
|
'random': rand,
|
||||||
|
'semitones': semitones,
|
||||||
|
'interval': interval,
|
||||||
|
'transpose': transpose
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,6 +201,6 @@ if __name__ == "__main__":
|
|||||||
tokens = [token for token in tokenize(lines) if token.type != TokenType.COMMENT]
|
tokens = [token for token in tokenize(lines) if token.type != TokenType.COMMENT]
|
||||||
|
|
||||||
ast = parse(tokens)
|
ast = parse(tokens)
|
||||||
environment = Environment([{}], functions)
|
environment = Environment([{ "bpm": 120 }], functions)
|
||||||
evaluate(ast, environment)
|
evaluate(ast, environment)
|
||||||
|
|
||||||
|
|||||||
44
Midi.py
Normal file
44
Midi.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
from Note import *
|
||||||
|
import Audio
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
def parseNotes(notes):
|
||||||
|
map = { NotePitch.C: 'c', NotePitch.CIS: 'c#', NotePitch.D: 'd', NotePitch.DIS: 'd#',
|
||||||
|
NotePitch.E: 'e', NotePitch.F: 'f', NotePitch.FIS: 'f#', NotePitch.G: 'g',
|
||||||
|
NotePitch.GIS: 'g#', NotePitch.A: 'a', NotePitch.AIS: 'a#', NotePitch.H: 'b' }
|
||||||
|
parsed = [(f"{map[note.note]}{note.octave}", note.duration) for note in notes]
|
||||||
|
print(parsed)
|
||||||
|
|
||||||
|
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 = findVariable("bpm", env)
|
||||||
|
if all(isinstance(x, Note) or isinstance(x, int) for x in notes):
|
||||||
|
for x in notes:
|
||||||
|
if isinstance(x, Note):
|
||||||
|
Audio.playNote(x, bpm)
|
||||||
|
if isinstance(x, int):
|
||||||
|
doPause(x, bpm)
|
||||||
|
#sys.stdout = open(os.devnull, 'w')
|
||||||
|
#sys.stderr = open(os.devnull, 'w')
|
||||||
|
#sys.stdout = sys.__stdout__
|
||||||
|
#sys.stderr = sys.__stderr__
|
||||||
|
|
||||||
|
def findVariable(name, environment):
|
||||||
|
for scope in reversed(environment.scopes):
|
||||||
|
if name in scope:
|
||||||
|
return scope[name]
|
||||||
|
|
||||||
|
def pause(args, env):
|
||||||
|
bpm = findVariable("bpm", env)
|
||||||
|
value = args[0]
|
||||||
|
doPause(value, bpm)
|
||||||
|
|
||||||
|
def doPause(value, bpm):
|
||||||
|
time.sleep(60 * 4 / value / bpm)
|
||||||
69
Note.py
69
Note.py
@@ -1,5 +1,6 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from Error import ParseError
|
from Error import ParseError
|
||||||
|
import math
|
||||||
|
|
||||||
class NotePitch(Enum):
|
class NotePitch(Enum):
|
||||||
C = 1
|
C = 1
|
||||||
@@ -21,6 +22,26 @@ class NotePitch(Enum):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.__str__()
|
return self.__str__()
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def checkInterval(a, b):
|
||||||
|
return a.value - b.value
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def toPitch(string):
|
def toPitch(string):
|
||||||
try:
|
try:
|
||||||
@@ -41,7 +62,7 @@ class NotePitch(Enum):
|
|||||||
return [note[1] for note in NotePitch.__members__.items() if note[1].value >= aValue and note[1].value <= bValue]
|
return [note[1] for note in NotePitch.__members__.items() if note[1].value >= aValue and note[1].value <= bValue]
|
||||||
|
|
||||||
class Note:
|
class Note:
|
||||||
def __init__(self, note, octave, duration):
|
def __init__(self, note, octave = 4, duration = 4):
|
||||||
if type(note) == str:
|
if type(note) == str:
|
||||||
self.note = NotePitch.toPitch(note)
|
self.note = NotePitch.toPitch(note)
|
||||||
else:
|
else:
|
||||||
@@ -49,6 +70,52 @@ class Note:
|
|||||||
self.octave = octave
|
self.octave = octave
|
||||||
self.duration = duration
|
self.duration = duration
|
||||||
|
|
||||||
|
def hash(self):
|
||||||
|
return f"{self.note.value}{self.octave}{self.duration}"
|
||||||
|
|
||||||
|
def toFrequency(self):
|
||||||
|
return self.note.toFrequency() * 2 ** self.octave
|
||||||
|
|
||||||
|
def transpose(self, interval):
|
||||||
|
origIntRepr = self._intRepr()
|
||||||
|
transposedIntRepr = origIntRepr + interval
|
||||||
|
pitch = transposedIntRepr % len(NotePitch)
|
||||||
|
note = NotePitch(pitch if pitch != 0 else self.note.value)
|
||||||
|
octave = int(transposedIntRepr / len(NotePitch))
|
||||||
|
return Note(note, octave, self.duration)
|
||||||
|
|
||||||
|
def _intRepr(self):
|
||||||
|
return self.octave * len(NotePitch) + self.note.value
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.note}({self.octave}')[{self.duration}]"
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.__str__()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def checkInterval(a, b):
|
||||||
|
return a._intRepr() - b._intRepr()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def range(a, b):
|
def range(a, b):
|
||||||
return [Note(note, 1, 1) for note in NotePitch.range(a.note, b.note)]
|
return [Note(note, 1, 1) for note in NotePitch.range(a.note, b.note)]
|
||||||
|
|
||||||
|
def intervalToString(interval):
|
||||||
|
octaveInterval = int(abs(interval) / len(NotePitch))
|
||||||
|
pitchInterval = abs(interval) % len(NotePitch)
|
||||||
|
pitchIntervalName = {
|
||||||
|
0: "1",
|
||||||
|
1: "1>/2<",
|
||||||
|
2: "2",
|
||||||
|
3: "3<",
|
||||||
|
4: "3>",
|
||||||
|
5: "4",
|
||||||
|
6: "4>/5<",
|
||||||
|
7: "5",
|
||||||
|
8: "6<",
|
||||||
|
9: "6>",
|
||||||
|
10: "7<",
|
||||||
|
11: "7>"
|
||||||
|
}
|
||||||
|
return (str(pitchIntervalName[pitchInterval]) + (f"(+{octaveInterval}')" if octaveInterval > 0 else ""))
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ def parseNote(input, parent):
|
|||||||
consumedChars = 1
|
consumedChars = 1
|
||||||
notePitch = value[consumedChars]
|
notePitch = value[consumedChars]
|
||||||
consumedChars += 1
|
consumedChars += 1
|
||||||
octave = 1
|
octave = 4
|
||||||
duration = 1
|
duration = 4
|
||||||
if consumedChars < len(value) and value[consumedChars] in ('b', '#'):
|
if consumedChars < len(value) and value[consumedChars] in ('b', '#'):
|
||||||
notePitch += value[consumedChars]
|
notePitch += value[consumedChars]
|
||||||
consumedChars += 1
|
consumedChars += 1
|
||||||
|
|||||||
Reference in New Issue
Block a user