-
Notifications
You must be signed in to change notification settings - Fork 0
/
petal_parser.rb
421 lines (376 loc) · 13.8 KB
/
petal_parser.rb
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
require 'singleton'
require_relative './EuclideanRhythm/euclidean_rhythm.rb'
require_relative './petal_data.rb'
require_relative './petal_util.rb'
module PetalLang
module Parser
extend self
include PetalLang::Util
def normalize_sound_string(sound)
sound_string = if sound.is_a?(Symbol)
'[' + ':' + sound.to_s + ']'
else
'[' + sound.to_s + ']'
end
end
def normalize_option_string(option)
# 当面はsoundと同じ実装
normalize_sound_string(option)
end
def find_element(index_array, target_array)
element = nil
index_array.each do |i|
while target_array.is_a?(Array) && target_array.count == 1
# 配列の入れ子(例: "[[[1, 3]]]")の場合の対応
element = target_array[0]
target_array = element
end
next unless target_array.is_a?(Array)
idx = i.modulo(target_array.count)
element = target_array[idx]
target_array = element
end
return element[0] if element.is_a?(Array)
element
end
def dfs_merge_with_n_array(sound_array, n_array)
dfs_each_with_index_map(sound_array) do |sound, index_array|
n_element = find_element(index_array, n_array)
sound.index = n_element.value.to_i unless Option::REST.equal?(n_element)
sound
end
end
def dfs_merge_with_gain_array(sound_array, gain_array)
dfs_each_with_index_map(sound_array) do |sound, index_array|
gain = find_element(index_array, gain_array)
sound.amp = gain.value unless Option::REST.equal?(gain)
sound
end
end
def dfs_merge_with_pan_array(sound_array, pan_array)
dfs_each_with_index_map(sound_array) do |sound, index_array|
pan = find_element(index_array, pan_array)
sound.pan = pan.value unless Option::REST.equal?(pan)
sound
end
end
def dfs_merge_with_speed_array(sound_array, speed_array)
dfs_each_with_index_map(sound_array) do |sound, index_array|
speed = find_element(index_array, speed_array)
sound.rate = sound.rate.to_f * speed.value.to_f unless Option::REST.equal?(speed)
sound
end
end
def dfs_merge_n_array_with_sound_array(n_array, sound_array)
dfs_each_with_index_map(n_array) do |n_element, index_array|
if Option::REST.equal?(n_element)
sound = Sound::REST
else
s = find_element(index_array, sound_array)
sound = Sound.new(s.name, s.index, n_element.divisor)
sound.index = n_element.value.to_i
end
sound
end
end
def dfs_merge_gain_array_with_sound_array(gain_array, sound_array)
dfs_each_with_index_map(gain_array) do |gain, index_array|
if Option::REST.equal?(gain)
sound = Sound::REST
else
s = find_element(index_array, sound_array)
sound = Sound.new(s.name, s.index, gain.divisor)
sound.amp = gain.value
end
sound
end
end
def dfs_merge_pan_array_with_sound_array(pan_array, sound_array)
dfs_each_with_index_map(pan_array) do |pan, index_array|
if Option::REST.equal?(pan)
sound = Sound::REST
else
s = find_element(index_array, sound_array)
sound = Sound.new(s.name, s.index, pan.divisor)
sound.pan = pan.value
end
sound
end
end
def dfs_merge_speed_array_with_sound_array(speed_array, sound_array)
dfs_each_with_index_map(speed_array) do |speed, index_array|
if Option::REST.equal?(speed)
sound = Sound::REST
else
s = find_element(index_array, sound_array)
sound = Sound.new(s.name, s.index, speed.divisor)
sound.rate = sound.rate.to_f * speed.value.to_f
end
sound
end
end
def merge_with_sound_array(option_hash, key)
option = option_hash[key]
random_n = Parser.parse_rand(option)
if random_n.nil?
option_string = Parser.normalize_option_string(option)
option_array = Parser::OptionParser.instance.read(option_string)
dbg "option_array: #{option_array}"
yield(option_array)
# 後ろの処理でparseさせないよう、ここで当該オプションを削除
option_hash.delete(key)
end
end
def dfs_each_with_index_map(arry, index_array = [], &block)
arry.each_with_index.map do |e, i|
is_array = e.is_a?(Array)
push_index = !(is_array && arry.count == 1)
# 配列の入れ子の場合の対応
# "[[[1, 3]]]"の場合に、index_arrayを[0, 0, 0, 0]や[0, 0, 0, 1]とせず、
# [0]や[1]になるようにする。
index_array.push(i) if push_index
dbg "index_array: #{index_array}"
e = if is_array
dfs_each_with_index_map(e, index_array, &block)
else
yield(e, index_array)
end
index_array.pop if push_index
e
end
end
def euclidean_rhythm(num_accents, size, beat_rotations = nil)
res = EuclideanRhythm.euclidean_rhythm(num_accents, size)
if beat_rotations && beat_rotations.is_a?(Numeric)
res.rotate!(beat_rotations)
end
res
end
def parse_rand(s)
min = '0'
max = '1'
return Rand.new(min, max) if s == :rand
return IRand.new(min, max) if s == :irand
return nil unless s.is_a?(String)
m = /((rand)|(irand))\s?\(?\s?(\-?\d+\.?\d*)?\s?,?\s?(\-?\d+\.?\d*)?\)?/.match(s)
return nil unless m
is_rand = m[2] && m[2] != ''
is_irand = m[3] && m[3] != ''
val1 = m[4] if m[4] && m[4] != ''
val2 = m[5] if m[5] && m[5] != ''
if val1 && val2
min = val1
max = val2
elsif val1
max = val1
end
return Rand.new(min, max) if is_rand
return IRand.new(min, max) if is_irand
end
def find_left_most_option(**option_hash)
option_hash.each do |k, v|
dbg "option_hash[#{k}]: #{v}"
case k
when :s, :sound, :n, :gain, :amp, :pan, :speed, :rate
return k
end
end
nil
end
def parse(bpm, sound = nil, **option_hash)
# stop
return Cycle.new(bpm, []) if sound == :silence || (sound.nil? || sound.empty?) && (option_hash.nil? || option_hash.empty?)
if sound && !sound.empty?
# 引数soundありの場合。
sound_string = Parser.normalize_sound_string(sound)
sound_array = Parser::SoundParser.instance.read(sound_string)
# 引数soundありでoptionなしの場合、optionをparseせずここでreturn
return Cycle.new(bpm, sound_array) if option_hash.nil? || option_hash.empty?
else
# 引数soundなしの場合。
# opthon_hashに、`s:`か`sound:`があるかチェックする。
sound = option_hash[:s] || option_hash[:sound]
return Cycle.new(bpm, []) if sound.nil?
sound_string = Parser.normalize_sound_string(sound)
sound_array = Parser::SoundParser.instance.read(sound_string)
dbg "sound_array: #{sound_array}"
left_most = find_left_most_option(**option_hash)
case left_most
# when :s, :sound
when :n
Parser.merge_with_sound_array(option_hash, left_most) do |n_array|
sound_array = Parser.dfs_merge_n_array_with_sound_array(n_array, sound_array)
end
when :gain, :amp
Parser.merge_with_sound_array(option_hash, left_most) do |gain_array|
sound_array = Parser.dfs_merge_gain_array_with_sound_array(gain_array, sound_array)
end
when :pan
Parser.merge_with_sound_array(option_hash, left_most) do |pan_array|
sound_array = Parser.dfs_merge_pan_array_with_sound_array(pan_array, sound_array)
end
when :speed, :rate
dbg "sound_array: #{sound_array}"
Parser.merge_with_sound_array(option_hash, left_most) do |speed_array|
sound_array = Parser.dfs_merge_speed_array_with_sound_array(speed_array, sound_array)
end
end
end
# :n
n_option = option_hash[:n]
unless n_option.nil?
random_n = Parser.parse_rand(n_option)
if random_n.nil?
n_string = Parser.normalize_option_string(n_option)
n_array = Parser::OptionParser.instance.read(n_string)
dbg n_array
sound_array = Parser.dfs_merge_with_n_array sound_array, n_array
end
end
# :gain, :amp
gain = option_hash[:gain] || option_hash[:amp]
unless gain.nil?
random_gain = Parser.parse_rand(gain)
if random_gain.nil?
gain_string = Parser.normalize_option_string(gain)
gain_array = Parser::OptionParser.instance.read(gain_string)
dbg gain_array
sound_array = Parser.dfs_merge_with_gain_array sound_array, gain_array
end
end
# :pan
pan = option_hash[:pan]
unless pan.nil?
random_pan = Parser.parse_rand(pan)
if random_pan.nil?
pan_string = Parser.normalize_option_string(pan)
pan_array = Parser::OptionParser.instance.read(pan_string)
dbg pan_array
sound_array = Parser.dfs_merge_with_pan_array sound_array, pan_array
end
end
# :speed, :rate
speed = option_hash[:speed] || option_hash[:rate]
unless speed.nil?
random_speed = Parser.parse_rand(speed)
if random_speed.nil?
speed_string = Parser.normalize_option_string(speed)
speed_array = Parser::OptionParser.instance.read(speed_string)
dbg speed_array
sound_array = Parser.dfs_merge_with_speed_array sound_array, speed_array
end
end
density = option_hash[:fast] || option_hash[:density]
bpm *= density.to_f unless density.nil?
slow = option_hash[:slow]
bpm /= slow.to_f unless slow.nil?
dbg "bpm: #{bpm}"
stretch = option_hash[:stretch]
Cycle.new(bpm, sound_array, stretch, random_n, random_gain, random_pan, random_speed)
end
class AbstractParser
include Singleton
def read(s)
read_from tokenize(s)
end
def tokenize(s)
s.gsub(/[\[\]]/, ' \0 ').split # []
end
def read_from(tokens)
raise SytaxError, 'unexpected EOF while reading' if tokens.empty?
case token = tokens.shift
when '['
l = [] # 配列の初期化
l.push read_from(tokens) until tokens[0] == ']'
tokens.shift
l
when ']'
raise SyntaxError, 'unexpected ]'
else
process_token(token)
# token
end
end
end
class SoundParser < AbstractParser
def process_token(token)
index = 0
divisor = 1
m = /(:?)([0-9a-zA-Z_~]+):?(\d*)(((\*|\/)?(\d+))|(\(\s?(\d+),\s?(\d+),?\s?(\d*)\s?\)))?/.match(token)
raise SyntaxError, "token: #{token}" unless m
builtin_sample = m[1] && m[1] != ''
name = m[2]
name = name.intern if builtin_sample
index = m[3].to_i if m[3] && m[3] != ''
operator = m[6] if m[6] && m[6] != ''
number = m[7].to_i if m[7] && m[7] != ''
num_accents = m[9].to_i if m[9] && m[9] != ''
size = m[10].to_i if m[10] && m[10] != ''
beat_rotations = m[11].to_i if m[11] && m[11] != ''
dbg "index: #{index}"
dbg "operator: #{operator}"
dbg "number: #{number}"
dbg "num_accents: #{num_accents}"
dbg "size: #{size}"
dbg "beat_rotations: #{beat_rotations}"
if num_accents && num_accents != 0 && size && size != 0
pattern = Parser.euclidean_rhythm(num_accents, size, beat_rotations)
pattern = pattern.map { |e| e ? Sound.new(name, index, divisor) : Sound::REST }
return pattern
elsif operator == '*' && number && number != 0
arry = [] # 配列の初期化
number.times do
s = if name == '~'
Sound::REST
else
Sound.new(name, index, divisor)
end
arry.push s
end
return arry
elsif operator == '/' && number && number != 0
divisor = number
end
node = if name == '~'
Sound::REST
else
Sound.new(name, index, divisor)
end
node
end
end
class OptionParser < AbstractParser
def process_token(token)
index = 0
divisor = 1
m = /((\-?\d+\.?\d*)|~)(((\*|\/)?(\d+))|(\(\s?(\d+),\s?(\d+),?\s?(\d*)\s?\)))?/.match(token)
# m = /((\-?\d+\.?\d*)|~)(((\*|\/)?(\d+))|(\(\s?(\d+),\s?(\d+),?\s?(\d*)\s?\)))?/.match('0.5(5,8,2)')
raise SyntaxError, "token: #{token}" unless m
num_or_tilde = m[1]
val = m[2]
operator = m[5] if m[5] && m[5] != ''
number = m[6].to_i if m[6] && m[6] != ''
num_accents = m[8].to_i if m[8] && m[8] != ''
size = m[9].to_i if m[9] && m[9] != ''
beat_rotations = m[10].to_i if m[10] && m[10] != ''
dbg "operator: #{operator}"
dbg "number: #{number}"
if num_accents && num_accents != 0 && size && size != 0
pattern = Parser.euclidean_rhythm(num_accents, size, beat_rotations)
pattern = pattern.map { |e| e ? Option.new(val, divisor) : Option::REST }
return pattern
elsif operator == '*' && number && number != 0
arry = [] # 配列の初期化
number.times do
arry.push Option.new(val, divisor)
end
return arry
elsif operator == '/' && number && number != 0
divisor = number
end
node = num_or_tilde == '~' ? Option::REST : Option.new(val, divisor)
node
end
end
end
end