22
22
"b" : 493.883301256
23
23
}
24
24
25
+
26
+ DEFAULT_VIBRATO_FREQ_HZ = 7.0
27
+ DEFAULT_VIBRATO_VAR_HZ = 20.0
28
+ DEFAULT_OCTAVE = 4
29
+ DEFAULT_DURATION = 8
30
+ DEFAULT_BPM = 123
31
+
32
+
25
33
class PTTTLSyntaxError (Exception ):
26
34
"""
27
35
Raised by PTTTLParser when ptttl data is malformed and cannot be parsed
@@ -58,14 +66,29 @@ def _invalid_note(note):
58
66
def _invalid_octave (note ):
59
67
raise PTTTLValueError ("invalid octave in note '%s'" % note )
60
68
69
+ def _invalid_vibrato (vdata ):
70
+ raise PTTTLValueError ("invalid vibrato settings: '%s'" % note )
71
+
72
+
61
73
def _int_setting (key , val ):
62
74
ret = None
63
75
64
76
try :
65
77
ret = int (val )
66
78
except ValueError :
67
79
raise PTTTLValueError ("expecting an integer for '%s' setting in "
68
- "PTTTL script" % key )
80
+ "PTTTL script" % key )
81
+
82
+ return ret
83
+
84
+ def _float_setting (key , val ):
85
+ ret = None
86
+
87
+ try :
88
+ ret = float (val )
89
+ except ValueError :
90
+ raise PTTTLValueError ("expecting a floating point value for '%s' setting in "
91
+ "PTTTL script" % key )
69
92
70
93
return ret
71
94
@@ -79,15 +102,38 @@ class PTTTLNote(object):
79
102
80
103
:ivar float pitch: Note pitch in Hz
81
104
:ivar float duration: Note duration in seconds
105
+ :ivar float vfreq: Vibrato frequency in Hz
106
+ :ivar float vvar: Vibrato variance from main pitch in Hz
82
107
"""
83
- def __init__ (self , pitch , duration ):
108
+ def __init__ (self , pitch , duration , vfreq = None , vvar = None ):
84
109
self .pitch = pitch
85
110
self .duration = duration
111
+ self .vibrato_frequency = vfreq
112
+ self .vibrato_variance = vvar
113
+
114
+ def has_vibrato (self ):
115
+ """
116
+ Returns True if vibrato frequency and variance are non-zero
117
+
118
+ :return: True if vibrato is non-zero
119
+ :rtype: bool
120
+ """
121
+ if None in [self .vibrato_frequency , self .vibrato_variance ]:
122
+ return False
123
+
124
+ if 0.0 in [self .vibrato_frequency , self .vibrato_variance ]:
125
+ return False
126
+
127
+ return (self .vibrato_frequency > 0.0 ) and (self .vibrato_variance > 0.0 )
86
128
87
129
def __str__ (self ):
88
- return "%s(pitch=%.4f, duration=%.4f)" % (self .__class__ .__name__ ,
89
- self .pitch ,
90
- self .duration )
130
+ ret = "%s(pitch=%.4f, duration=%.4f" % (self .__class__ .__name__ ,
131
+ self .pitch ,
132
+ self .duration )
133
+ if self .has_vibrato ():
134
+ ret += ", vibrato=%.1f:%.1f" % (self .vibrato_frequency , self .vibrato_variance )
135
+
136
+ return ret + ")"
91
137
92
138
def __repr__ (self ):
93
139
return self .__str__ ()
@@ -99,10 +145,22 @@ class PTTTLData(object):
99
145
May contain multiple tracks, where each track is a list of PTTTLNote objects.
100
146
101
147
:ivar [[PTTTLNote]] tracks: List of tracks. Each track is a list of PTTTLNote objects.
148
+ :ivar float bpm: playback speed in BPM (beats per minute).
149
+ :ivar int default_octave: Default octave to use when none is specified
150
+ :ivar int default_duration: Default note duration to use when none is specified
151
+ :ivar float default_vibrato_freq: Default vibrato frequency when none is specified, in Hz
152
+ :ivar float default_vibrato_var: Default vibrato variance when none is specified, in Hz
102
153
"""
103
- def __init__ (self ):
154
+ def __init__ (self , bpm = DEFAULT_BPM , default_octave = DEFAULT_OCTAVE ,
155
+ default_duration = DEFAULT_DURATION , default_vibrato_freq = DEFAULT_VIBRATO_FREQ_HZ ,
156
+ default_vibrato_var = DEFAULT_VIBRATO_VAR_HZ ):
157
+ self .bpm = bpm
158
+ self .default_octave = default_octave
159
+ self .default_duration = default_duration
160
+ self .default_vibrato_freq = default_vibrato_freq
161
+ self .default_vibrato_var = default_vibrato_var
104
162
self .tracks = []
105
-
163
+
106
164
def add_track (self , notes ):
107
165
self .tracks .append (notes )
108
166
@@ -149,6 +207,12 @@ def _parse_config_line(self, conf):
149
207
if not values :
150
208
raise PTTTLSyntaxError ("no valid configuration found in PTTTL script" )
151
209
210
+ bpm = None
211
+ default = None
212
+ octave = None
213
+ vfreq = None
214
+ vvar = None
215
+
152
216
for value in values :
153
217
fields = value .split ('=' )
154
218
if len (fields ) != 2 :
@@ -170,6 +234,10 @@ def _parse_config_line(self, conf):
170
234
octave = _int_setting (key , val )
171
235
if not self ._is_valid_octave (octave ):
172
236
_invalid_value (key , val )
237
+ elif key == 'f' :
238
+ vfreq = _float_setting (key , val )
239
+ elif key == 'v' :
240
+ vvar = _float_setting (key , val )
173
241
else :
174
242
_unrecognised_setting (key )
175
243
@@ -182,23 +250,32 @@ def _parse_config_line(self, conf):
182
250
if not default :
183
251
_missing_setting ('d' )
184
252
185
- return bpm , default , octave
253
+ return bpm , default , octave , vfreq , vvar
186
254
187
255
def _note_time_to_secs (self , note_time , bpm ):
188
256
# Time in seconds for a whole note (4 beats) given current BPM.
189
257
whole = (60.0 / float (bpm )) * 4.0
190
258
191
259
return whole / float (note_time )
192
260
193
- def _parse_note (self , string , bpm , default , octave ):
261
+ def _parse_note (self , string , bpm , default , octave , vfreq , vvar ):
194
262
i = 0
195
263
orig = string
196
264
sawdot = False
197
265
dur = default
266
+ vibrato_freq = None
267
+ vibrato_var = None
268
+ vdata = None
269
+
270
+ fields = string .split ('v' )
271
+ if len (fields ) == 2 :
272
+ string = fields [0 ]
273
+ vdata = fields [1 ]
198
274
199
275
if len (string ) == 0 :
200
276
raise PTTTLSyntaxError ("Missing notes after comma" )
201
277
278
+ # Get the note duration; if there is one, it should be the first thing
202
279
while i < len (string ) and string [i ].isdigit ():
203
280
if i > 1 :
204
281
_invalid_note_duration (orig )
@@ -214,8 +291,10 @@ def _parse_note(self, string, bpm, default, octave):
214
291
if not string [0 ].isalpha ():
215
292
_invalid_note (orig )
216
293
294
+ # Calculate note duration in real seconds
217
295
duration = self ._note_time_to_secs (dur , bpm )
218
296
297
+ # Now, look for the musical note value, which should be next
219
298
string = string [i :]
220
299
221
300
i = 0
@@ -234,8 +313,8 @@ def _parse_note(self, string, bpm, default, octave):
234
313
i = 0
235
314
236
315
if note == 'p' :
316
+ # This note is a rest
237
317
pitch = - 1
238
-
239
318
else :
240
319
if note not in NOTES :
241
320
_invalid_note (orig )
@@ -261,13 +340,37 @@ def _parse_note(self, string, bpm, default, octave):
261
340
else :
262
341
pitch = raw_pitch
263
342
343
+ string = string [i :].strip ()
344
+ i = 0
345
+
264
346
if sawdot or ((i < len (string )) and string [- 1 ] == '.' ):
265
347
duration += (duration / 2.0 )
266
348
267
- return PTTTLNote (pitch , duration )
349
+ if vdata is not None :
350
+ if vdata .strip () == '' :
351
+ vibrato_freq = vfreq
352
+ vibrato_var = vvar
353
+ else :
354
+ fields = vdata .split ('-' )
355
+ if len (fields ) == 2 :
356
+ try :
357
+ vibrato_freq = float (fields [0 ])
358
+ vibrato_var = float (fields [1 ])
359
+ except :
360
+ _invalid_vibrato (vdata )
361
+
362
+ elif len (fields ) == 1 :
363
+ try :
364
+ vibrato_freq = float (vdata )
365
+ except :
366
+ _invalid_vibrato (vdata )
268
367
269
- def _parse_notes (self , track_list , bpm , default , octave ):
270
- ret = PTTTLData ()
368
+ vibrato_var = vvar
369
+
370
+ return PTTTLNote (pitch , duration , vibrato_freq , vibrato_var )
371
+
372
+ def _parse_notes (self , track_list , bpm , default , octave , vfreq , vvar ):
373
+ ret = PTTTLData (bpm , octave , default , vfreq , vvar )
271
374
272
375
for track in track_list :
273
376
if track .strip () == "" :
@@ -276,7 +379,7 @@ def _parse_notes(self, track_list, bpm, default, octave):
276
379
buf = []
277
380
fields = track .split (',' )
278
381
for note in fields :
279
- note = self ._parse_note (note .strip (), bpm , default , octave )
382
+ note = self ._parse_note (note .strip (), bpm , default , octave , vfreq , vvar )
280
383
buf .append (note )
281
384
282
385
ret .add_track (buf )
@@ -299,7 +402,7 @@ def parse(self, ptttl_string):
299
402
raise PTTTLSyntaxError ('expecting 3 colon-seperated fields' )
300
403
301
404
self .name = fields [0 ].strip ()
302
- bpm , default , octave = self ._parse_config_line (fields [1 ])
405
+ bpm , default , octave , vfreq , vvar = self ._parse_config_line (fields [1 ])
303
406
304
407
numtracks = - 1
305
408
blocks = fields [2 ].split (';' )
@@ -322,4 +425,9 @@ def parse(self, ptttl_string):
322
425
if i < (len (trackdata ) - 1 ):
323
426
tracks [j ] += ","
324
427
325
- return self ._parse_notes (tracks , bpm , default , octave )
428
+ if vfreq is None :
429
+ vfreq = DEFAULT_VIBRATO_FREQ_HZ
430
+ if vvar is None :
431
+ vvar = DEFAULT_VIBRATO_VAR_HZ
432
+
433
+ return self ._parse_notes (tracks , bpm , default , octave , vfreq , vvar )
0 commit comments