-
Notifications
You must be signed in to change notification settings - Fork 2
/
SFM.py
203 lines (166 loc) · 5.67 KB
/
SFM.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#########################################################
#
# File: SFM.py
# SFM = solfa machine
#
# Authors: James Carlson and Hakon Hallgrimur
# Date: March 27, 2011
#
# Purpose: transform a string of augmented solfa symbols
# into a string of tuple, where a tuple is
# (frequency, duration, amplitude, decay)
#
# Example: from SFM import SFM
# S = SFM()
# S.input = "q do re mi"
# print S.tuples()
# print "Total duration:", S.totalDuration
# print "Total beats:", S.currentBeat
# print "Maximum amplitude:", S.maximumAmplitude
#
##########################################################
ON = 1
OFF = 0
DEBUG = OFF
import math
from parse import splitToken
from note import Note
from rhythm import Rhythm
from dynamics import Dynamics
from stringUtil import * # catList, catList2
from listUtil import interval, mapInterval
class SFM(object):
# pitch registers
transpositionSemitones = 0
octaveNumber = 3
# tempo registers
tempo = 60
currentBeatValue = 60.0/tempo
currentBeat = 0
totalDuration = 0.0
# dynamics registers
crescendoSpeed = 1.1
currentCrescendoSpeed = crescendoSpeed
crescendoBeatsRemaining = 0.0
maximumAmplitude = 0.0
# tuple registers
duration = 60.0/tempo
amplitude = 1.0
decay = 1.0
# I/O
input = ""
#########################################################
# initialize Note, Rhythm, and Dynamics objects
def __init__(self, SCALE):
NOTES, FREQ_DICT = SCALE
self.note = Note(NOTES, FREQ_DICT)
self.rhythm = Rhythm()
self.dynamics = Dynamics()
self.equalizerFactor = { }
self.equalizerFactor[-0.1] = 1.0
self.equalizerFactor[220] = 1.0
self.equalizerFactor[440] = 0.7
self.equalizerFactor[880] = 0.35
self.equalizerFactor[1760] = 0.15
self.equalizerFactor[3520] = 0.15
self.equalizerFactor[7040] = 0.15
self.equalizerFactor[14080] = 0.15
self.equalizerFactor[28160] = 0.15
self.equalizerFactor[56320] = 0.15
self.equalizerBreakPoints = [-0.1, 220, 440, 880, 1760, 3520, 7040, 14080, 28160, 56320]
# self.tempo = 60
# self.currentBeatValue = 60.0/self.tempo
# self.octaveNumber = 3
def equalize(self, freq):
a, b = interval(freq, self.equalizerBreakPoints)
f = mapInterval(freq, a,b, self.equalizerFactor[a], self.equalizerFactor[b])
# print freq, a, b, f
return f
# return tuple as string given frequency,
# duration, decay, and amplitude
def _tuple(self, freq, duration):
output = `freq`
output += " "+`duration`
a = self.amplitude
a = a*self.equalize(freq)
output += " "+`a`
output += " "+`self.decay`
return output
# return tuple as string from frequency of token
# and root and suffix of token
def tuple(self, freq, root, suffix):
if suffix.find(",") > -1:
thisDuration = self.duration*(1 - self.rhythm.breath)
output = self._tuple(freq, thisDuration)
output += "\n"
output += self._tuple(0, self.duration - thisDuration)
else:
output = self._tuple(freq, self.duration)
return output
def updateRhythm(self, cmd):
self.currentBeatValue, self.duration = self.rhythm.value(cmd, self)
def emitNote(self, token):
if self.crescendoBeatsRemaining > 0:
self.amplitude = self.amplitude*self.currentCrescendoSpeed
self.crescendoBeatsRemaining -= self.currentBeatValue
freq, root, suffix = self.note.freq(token, self.transpositionSemitones, self.octaveNumber)
self.output += self.tuple(freq, root, suffix) + "\n"
# summary data
self.totalDuration += self.duration
self.currentBeat += self.currentBeatValue
if self.amplitude > self.maximumAmplitude:
self.maximumAmplitude = self.amplitude
def executeCommand(self, ops):
cmd = ops[0]
# if cmd is a rhythm symbol, change value of duration register
if self.rhythm.isRhythmOp(cmd):
self.updateRhythm(cmd)
# if cmd is a tempo command, change value of the tempo register
if self.rhythm.isTempoOp(cmd):
self.tempo = self.rhythm.tempo[cmd]
self.updateRhythm(cmd)
if cmd == "tempo":
self.tempo = float(ops[1])
self.updateRhythm(cmd)
# if cmd is an articulation command, change value of the decay register
if self.rhythm.isArticulationOp(cmd):
self.decay = self.rhythm.decay[cmd]
# if cmd is a dynamics command, change value of the amplitude register
if self.dynamics.isDynamicsConstant(cmd):
self.amplitude = self.dynamics.value[cmd]
# crescendo and decrescendo
if cmd == "crescendo" or cmd == "cresc":
self.crescendoBeatsRemaining = float(ops[1])
self.currentCrescendoSpeed = self.crescendoSpeed
if cmd == "decrescendo" or cmd == "decresc":
self.crescendoBeatsRemaining = float(ops[1])
self.currentCrescendoSpeed = 1.0/self.crescendoSpeed
# pitch transposition
if cmd == "octave":
self.octaveNumber = int(ops[1])
if cmd == "transpose":
self.transpositionSemitones = int(ops[1])
# pass special commands through
if cmd[0] == '@':
CMD = catList2(ops)
CMD = CMD[:len(CMD)]+"\n"
self.output += CMD
# tuples: returns a string of tuples from input = solfa text
def tuples(self):
# split intput into list of tokens
self.input = self.input.replace("\n", " ")
tokens = self.input.split(" ")
# make sure there are not empty list elements
tokens = filter( lambda x: len(x), tokens)
# initialize output
self.output = ""
for token in tokens:
if self.note.isNote(token):
self.emitNote(token)
else:
ops = token.split(":")
ops = filter(lambda x: len(x) > 0, ops)
if DEBUG == ON:
self.output += "cmd: "+ `ops`+"\n"
self.executeCommand(ops)
return self.output