Create some fundamental functions

This commit is contained in:
Bartłomiej Pluta
2019-06-30 14:22:44 +02:00
parent db3419af01
commit 2b52a5e0d5
5 changed files with 174 additions and 12 deletions

13
Audio.py Normal file
View 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.

View File

@@ -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]))
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)
if __name__ == "__main__": 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
View 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
View File

@@ -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 ""))

View File

@@ -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