musa-dsl 0.14.16
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/Gemfile +20 -0
- data/LICENSE.md +157 -0
- data/README.md +8 -0
- data/lib/musa-dsl/core-ext/array-apply-get.rb +18 -0
- data/lib/musa-dsl/core-ext/array-explode-ranges.rb +29 -0
- data/lib/musa-dsl/core-ext/array-to-neumas.rb +28 -0
- data/lib/musa-dsl/core-ext/array-to-serie.rb +20 -0
- data/lib/musa-dsl/core-ext/arrayfy.rb +15 -0
- data/lib/musa-dsl/core-ext/as-context-run.rb +44 -0
- data/lib/musa-dsl/core-ext/duplicate.rb +134 -0
- data/lib/musa-dsl/core-ext/dynamic-proxy.rb +55 -0
- data/lib/musa-dsl/core-ext/inspect-nice.rb +28 -0
- data/lib/musa-dsl/core-ext/key-parameters-procedure-binder.rb +85 -0
- data/lib/musa-dsl/core-ext/proc-nice.rb +13 -0
- data/lib/musa-dsl/core-ext/send-nice.rb +21 -0
- data/lib/musa-dsl/core-ext/string-to-neumas.rb +27 -0
- data/lib/musa-dsl/core-ext.rb +13 -0
- data/lib/musa-dsl/datasets/gdv-decorators.rb +221 -0
- data/lib/musa-dsl/datasets/gdv.rb +499 -0
- data/lib/musa-dsl/datasets/pdv.rb +44 -0
- data/lib/musa-dsl/datasets.rb +5 -0
- data/lib/musa-dsl/generative/darwin.rb +145 -0
- data/lib/musa-dsl/generative/generative-grammar.rb +294 -0
- data/lib/musa-dsl/generative/markov.rb +78 -0
- data/lib/musa-dsl/generative/rules.rb +282 -0
- data/lib/musa-dsl/generative/variatio.rb +331 -0
- data/lib/musa-dsl/generative.rb +5 -0
- data/lib/musa-dsl/midi/midi-recorder.rb +83 -0
- data/lib/musa-dsl/midi/midi-voices.rb +274 -0
- data/lib/musa-dsl/midi.rb +2 -0
- data/lib/musa-dsl/music/chord-definition.rb +99 -0
- data/lib/musa-dsl/music/chord-definitions.rb +13 -0
- data/lib/musa-dsl/music/chords.rb +326 -0
- data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +204 -0
- data/lib/musa-dsl/music/scales.rb +584 -0
- data/lib/musa-dsl/music.rb +6 -0
- data/lib/musa-dsl/neuma/neuma.rb +181 -0
- data/lib/musa-dsl/neuma.rb +1 -0
- data/lib/musa-dsl/neumalang/neumalang.citrus +294 -0
- data/lib/musa-dsl/neumalang/neumalang.rb +179 -0
- data/lib/musa-dsl/neumalang.rb +3 -0
- data/lib/musa-dsl/repl/repl.rb +143 -0
- data/lib/musa-dsl/repl.rb +1 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-control.rb +189 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +354 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +382 -0
- data/lib/musa-dsl/sequencer/base-sequencer-public.rb +261 -0
- data/lib/musa-dsl/sequencer/sequencer-dsl.rb +94 -0
- data/lib/musa-dsl/sequencer/sequencer.rb +3 -0
- data/lib/musa-dsl/sequencer.rb +1 -0
- data/lib/musa-dsl/series/base-series.rb +245 -0
- data/lib/musa-dsl/series/hash-serie-splitter.rb +194 -0
- data/lib/musa-dsl/series/holder-serie.rb +87 -0
- data/lib/musa-dsl/series/main-serie-constructors.rb +726 -0
- data/lib/musa-dsl/series/main-serie-operations.rb +1151 -0
- data/lib/musa-dsl/series/proxy-serie.rb +69 -0
- data/lib/musa-dsl/series/queue-serie.rb +94 -0
- data/lib/musa-dsl/series/series.rb +8 -0
- data/lib/musa-dsl/series.rb +1 -0
- data/lib/musa-dsl/transport/clock.rb +36 -0
- data/lib/musa-dsl/transport/dummy-clock.rb +47 -0
- data/lib/musa-dsl/transport/external-tick-clock.rb +31 -0
- data/lib/musa-dsl/transport/input-midi-clock.rb +124 -0
- data/lib/musa-dsl/transport/timer-clock.rb +102 -0
- data/lib/musa-dsl/transport/timer.rb +40 -0
- data/lib/musa-dsl/transport/transport.rb +137 -0
- data/lib/musa-dsl/transport.rb +9 -0
- data/lib/musa-dsl.rb +17 -0
- data/musa-dsl.gemspec +17 -0
- metadata +174 -0
@@ -0,0 +1,499 @@
|
|
1
|
+
require 'musa-dsl/neuma'
|
2
|
+
|
3
|
+
module Musa::Datasets
|
4
|
+
module GDVd
|
5
|
+
include Musa::Neumalang::Dataset
|
6
|
+
|
7
|
+
NaturalKeys = [:abs_grade, :abs_sharps, :abs_octave,
|
8
|
+
:delta_grade, :delta_sharps, :delta_interval_sign, :delta_interval, :delta_octave,
|
9
|
+
:abs_duration, :delta_duration, :factor_duration,
|
10
|
+
:abs_velocity, :delta_velocity].freeze
|
11
|
+
|
12
|
+
attr_accessor :base_duration
|
13
|
+
|
14
|
+
def to_gdv(scale, previous:)
|
15
|
+
r = previous.clone.delete_if {|k,_| !GDV::NaturalKeys.include?(k)}.extend GDV
|
16
|
+
|
17
|
+
r.base_duration = @base_duration
|
18
|
+
|
19
|
+
if include? :abs_grade
|
20
|
+
if self[:abs_grade] == :silence
|
21
|
+
r[:silence] = true
|
22
|
+
else
|
23
|
+
r.delete :silence
|
24
|
+
r.delete :sharps
|
25
|
+
|
26
|
+
r[:grade] = scale[self[:abs_grade]].wide_grade
|
27
|
+
r[:sharps] = self[:abs_sharps] if include?(:abs_sharps)
|
28
|
+
end
|
29
|
+
|
30
|
+
elsif include?(:delta_grade)
|
31
|
+
r.delete :silence
|
32
|
+
|
33
|
+
r[:grade], r[:sharps] =
|
34
|
+
normalize_to_scale(scale,
|
35
|
+
scale[r[:grade]].wide_grade + self[:delta_grade],
|
36
|
+
(r[:sharps] || 0) + (self[:delta_sharps] || 0))
|
37
|
+
|
38
|
+
r.delete :sharps if r[:sharps].zero?
|
39
|
+
|
40
|
+
elsif include?(:delta_interval)
|
41
|
+
r.delete :silence
|
42
|
+
|
43
|
+
sign = self[:delta_interval_sign] || 1
|
44
|
+
|
45
|
+
r[:grade], r[:sharps] =
|
46
|
+
normalize_to_scale scale,
|
47
|
+
scale[r[:grade]].wide_grade,
|
48
|
+
sign * scale.kind.tuning.scale_system.intervals[self[:delta_interval]]
|
49
|
+
|
50
|
+
r.delete :sharps if r[:sharps].zero?
|
51
|
+
|
52
|
+
elsif include?(:delta_sharps)
|
53
|
+
r.delete :silence
|
54
|
+
|
55
|
+
r[:grade], r[:sharps] =
|
56
|
+
normalize_to_scale scale,
|
57
|
+
scale[r[:grade]].wide_grade,
|
58
|
+
(r[:sharps] || 0) + self[:delta_sharps]
|
59
|
+
|
60
|
+
r.delete :sharps if r[:sharps].zero?
|
61
|
+
end
|
62
|
+
|
63
|
+
if include?(:abs_octave)
|
64
|
+
r[:octave] = self[:abs_octave]
|
65
|
+
elsif include?(:delta_octave)
|
66
|
+
r[:octave] += self[:delta_octave]
|
67
|
+
end
|
68
|
+
|
69
|
+
if include?(:abs_duration)
|
70
|
+
r[:duration] = self[:abs_duration]
|
71
|
+
elsif include?(:delta_duration)
|
72
|
+
r[:duration] += self[:delta_duration]
|
73
|
+
elsif include?(:factor_duration)
|
74
|
+
r[:duration] *= self[:factor_duration]
|
75
|
+
end
|
76
|
+
|
77
|
+
if include?(:abs_velocity)
|
78
|
+
r[:velocity] = self[:abs_velocity]
|
79
|
+
elsif include?(:delta_velocity)
|
80
|
+
r[:velocity] += self[:delta_velocity]
|
81
|
+
end
|
82
|
+
|
83
|
+
(keys - NaturalKeys).each { |k| r[k] = self[k] }
|
84
|
+
|
85
|
+
r
|
86
|
+
end
|
87
|
+
|
88
|
+
def normalize_to_scale(scale, grade, sharps)
|
89
|
+
note = scale[grade].sharp(sharps)
|
90
|
+
background = note.background_note
|
91
|
+
|
92
|
+
if background
|
93
|
+
return background.wide_grade, note.background_sharps
|
94
|
+
else
|
95
|
+
return note.wide_grade, 0
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def to_neuma(mode = nil)
|
100
|
+
mode ||= :dots # :parenthesis
|
101
|
+
|
102
|
+
@base_duration ||= Rational(1,4)
|
103
|
+
|
104
|
+
attributes = []
|
105
|
+
|
106
|
+
c = 0
|
107
|
+
|
108
|
+
if include?(:abs_grade)
|
109
|
+
attributes[c] = self[:abs_grade].to_s
|
110
|
+
|
111
|
+
elsif include?(:delta_grade)
|
112
|
+
attributes[c] = positive_sign_of(self[:delta_grade]) + self[:delta_grade].to_s unless self[:delta_grade].zero?
|
113
|
+
|
114
|
+
elsif include?(:delta_interval)
|
115
|
+
|
116
|
+
attributes[c] = self[:delta_interval_sign] if include?(:delta_interval_sign)
|
117
|
+
attributes[c] ||= ''
|
118
|
+
attributes[c] += self[:delta_interval].to_s
|
119
|
+
end
|
120
|
+
|
121
|
+
if include?(:delta_sharps) && !self[:delta_sharps].zero?
|
122
|
+
char = self[:delta_sharps] > 0 ? '#' : '_'
|
123
|
+
sign = attributes[c].nil? ? positive_sign_of(self[:delta_sharps]) : ''
|
124
|
+
|
125
|
+
attributes[c] ||= ''
|
126
|
+
attributes[c] += sign + char * self[:delta_sharps].abs
|
127
|
+
end
|
128
|
+
|
129
|
+
if include?(:abs_octave)
|
130
|
+
attributes[c += 1] = 'o' + positive_sign_of(self[:abs_octave]) + self[:abs_octave].to_s
|
131
|
+
elsif include?(:delta_octave)
|
132
|
+
attributes[c += 1] = sign_of(self[:delta_octave]) + 'o' + self[:delta_octave].abs.to_s if self[:delta_octave] != 0
|
133
|
+
end
|
134
|
+
|
135
|
+
if include?(:abs_duration)
|
136
|
+
attributes[c += 1] = (self[:abs_duration] / @base_duration).to_s
|
137
|
+
elsif include?(:delta_duration)
|
138
|
+
attributes[c += 1] = positive_sign_of(self[:delta_duration]) + (self[:delta_duration] / @base_duration).to_s
|
139
|
+
elsif include?(:factor_duration)
|
140
|
+
attributes[c += 1] = '*' + self[:factor_duration].to_s
|
141
|
+
end
|
142
|
+
|
143
|
+
if include?(:abs_velocity)
|
144
|
+
attributes[c += 1] = velocity_of(self[:abs_velocity])
|
145
|
+
elsif include?(:delta_velocity)
|
146
|
+
attributes[c += 1] = sign_of(self[:delta_velocity]) + 'f' * self[:delta_velocity].abs
|
147
|
+
end
|
148
|
+
|
149
|
+
(keys - NaturalKeys).each do |k|
|
150
|
+
attributes[c += 1] = modificator_string(k, self[k])
|
151
|
+
end
|
152
|
+
|
153
|
+
if mode == :dots
|
154
|
+
if !attributes.empty?
|
155
|
+
attributes.join '.'
|
156
|
+
else
|
157
|
+
'.'
|
158
|
+
end
|
159
|
+
|
160
|
+
elsif mode == :parenthesis
|
161
|
+
'<' + attributes.join(', ') + '>'
|
162
|
+
else
|
163
|
+
attributes
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
module Parser
|
168
|
+
class << self
|
169
|
+
def parse(expression, base_duration: nil)
|
170
|
+
base_duration ||= Rational(1,4)
|
171
|
+
|
172
|
+
neuma = expression.clone
|
173
|
+
|
174
|
+
command = {}.extend GDVd
|
175
|
+
command.base_duration = base_duration
|
176
|
+
|
177
|
+
grade = neuma.shift
|
178
|
+
|
179
|
+
if grade && !grade.empty?
|
180
|
+
if '+-#_'.include?(grade[0])
|
181
|
+
sign, interval, number, sharps = parse_grade(grade)
|
182
|
+
|
183
|
+
sign ||= 1
|
184
|
+
|
185
|
+
command[:delta_grade] = number * sign if number
|
186
|
+
command[:delta_sharps] = sharps * sign unless sharps.zero?
|
187
|
+
|
188
|
+
command[:delta_interval] = interval if interval
|
189
|
+
command[:delta_interval_sign] = sign if interval && sign && interval
|
190
|
+
else
|
191
|
+
_, name, number, sharps = parse_grade(grade)
|
192
|
+
|
193
|
+
command[:abs_grade] = name || number
|
194
|
+
command[:abs_sharps] = sharps unless sharps.zero?
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
octave = neuma.reject {|a| a.is_a?(Hash)}.find { |a| /\A[+-]?o[+-]?[0-9]+\Z/x.match a }
|
199
|
+
|
200
|
+
if octave
|
201
|
+
if (octave[0] == '+' || octave[0] == '-') && octave[1] == 'o'
|
202
|
+
command[:delta_octave] = (octave[0] + octave[2..-1]).to_i
|
203
|
+
elsif octave[0] == 'o'
|
204
|
+
command[:abs_octave] = octave[1..-1].to_i
|
205
|
+
end
|
206
|
+
|
207
|
+
neuma.delete octave
|
208
|
+
end
|
209
|
+
|
210
|
+
to_delete = velocity = neuma.select {|a| a.is_a?(Hash)}.find { |a| /\A(mp | mf | (\+|\-)?(p+|f+))\Z/x.match a[:modifier] }
|
211
|
+
velocity = velocity[:modifier].to_s if velocity
|
212
|
+
|
213
|
+
velocity ||= to_delete = neuma.reject {|a| a.is_a?(Hash)}.find { |a| /\A(mp | mf | (\+|\-)?(p+|f+))\Z/x.match a }
|
214
|
+
|
215
|
+
if velocity
|
216
|
+
if velocity[0] == '+' || velocity[0] == '-'
|
217
|
+
command[:delta_velocity] = (velocity[1] == 'f' ? 1 : -1) * (velocity.length - 1) * (velocity[0] + '1').to_i
|
218
|
+
elsif velocity[0] == 'm'
|
219
|
+
command[:abs_velocity] = velocity[1] == 'f' ? 1 : 0
|
220
|
+
else
|
221
|
+
command[:abs_velocity] = velocity.length * (velocity[0] == 'f' ? 1 : -1) + (velocity[0] == 'f' ? 1 : 0)
|
222
|
+
end
|
223
|
+
|
224
|
+
neuma.delete to_delete
|
225
|
+
end
|
226
|
+
|
227
|
+
duration = neuma.reject {|a| a.is_a?(Hash)}.first
|
228
|
+
|
229
|
+
if duration && !duration.empty?
|
230
|
+
if duration[0] == '+' || duration[0] == '-'
|
231
|
+
command[:delta_duration] = (duration[0] == '-' ? -1 : 1) * eval_duration(duration[1..-1]) * base_duration
|
232
|
+
|
233
|
+
elsif /\A\/+·*\Z/x.match(duration)
|
234
|
+
command[:abs_duration] = eval_duration(duration) * base_duration
|
235
|
+
|
236
|
+
elsif duration[0] == '*'
|
237
|
+
command[:factor_duration] = eval_duration(duration[1..-1])
|
238
|
+
|
239
|
+
elsif duration[0] == '/'
|
240
|
+
command[:factor_duration] = Rational(1, eval_duration(duration[1..-1]))
|
241
|
+
|
242
|
+
else
|
243
|
+
command[:abs_duration] = eval_duration(duration) * base_duration
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
neuma.delete duration if duration
|
248
|
+
|
249
|
+
neuma.select {|a| a.is_a?(Hash)}.each do |a|
|
250
|
+
command[a[:modifier]] = a[:parameters] || true
|
251
|
+
end
|
252
|
+
|
253
|
+
raise EncodingError, "Neuma #{neuma} cannot be decoded" unless neuma.reject {|a| a.is_a?(Hash)}.size.zero?
|
254
|
+
|
255
|
+
command
|
256
|
+
end
|
257
|
+
|
258
|
+
def parse_grade(neuma_grade)
|
259
|
+
sign = name = wide_grade = nil
|
260
|
+
accidentals = 0
|
261
|
+
|
262
|
+
case neuma_grade
|
263
|
+
when Symbol, String
|
264
|
+
match = /\A(?<sign>[+|-]?)(?<name>[^[#|_]]*)(?<accidental_sharps>#*)(?<accidental_flats>_*)\Z/.match neuma_grade.to_s
|
265
|
+
|
266
|
+
if match
|
267
|
+
sign = (match[:sign] == '-' ? -1 : 1) unless match[:sign].empty?
|
268
|
+
|
269
|
+
if match[:name] == match[:name].to_i.to_s
|
270
|
+
wide_grade = match[:name].to_i
|
271
|
+
else
|
272
|
+
name = match[:name].to_sym unless match[:name].empty?
|
273
|
+
end
|
274
|
+
accidentals = match[:accidental_sharps].length - match[:accidental_flats].length
|
275
|
+
else
|
276
|
+
name = neuma_grade.to_sym unless (neuma_grade.nil? || neuma_grade.empty?)
|
277
|
+
end
|
278
|
+
when Numeric
|
279
|
+
wide_grade = neuma_grade.to_i
|
280
|
+
|
281
|
+
else
|
282
|
+
raise ArgumentError, "Cannot eval #{neuma_grade} as name or grade position."
|
283
|
+
end
|
284
|
+
|
285
|
+
return sign, name, wide_grade, accidentals
|
286
|
+
end
|
287
|
+
|
288
|
+
def eval_duration(string)
|
289
|
+
# format: ///···
|
290
|
+
#
|
291
|
+
if match = /\A(?<slashes>\/+)(?<dots>\·*)\Z/x.match(string)
|
292
|
+
base = Rational(1, 2**match[:slashes].length.to_r)
|
293
|
+
dots_extension = 0
|
294
|
+
match[:dots].length.times do |i|
|
295
|
+
dots_extension += Rational(base, 2**(i+1))
|
296
|
+
end
|
297
|
+
|
298
|
+
base + dots_extension
|
299
|
+
|
300
|
+
# format: 1··
|
301
|
+
#
|
302
|
+
elsif match = /\A(?<number>\d*\/?\d+?)(?<dots>\·*)\Z/x.match(string)
|
303
|
+
base = match[:number].to_r
|
304
|
+
dots_extension = 0
|
305
|
+
match[:dots].length.times do |i|
|
306
|
+
dots_extension += Rational(base, 2**(i+1))
|
307
|
+
end
|
308
|
+
|
309
|
+
base + dots_extension
|
310
|
+
|
311
|
+
else
|
312
|
+
string.to_r
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
class NeumaDifferentialDecoder < Musa::Neumalang::DifferentialDecoder # to get a GDVd
|
319
|
+
def initialize(base_duration: nil)
|
320
|
+
@base_duration = base_duration || Rational(1,4)
|
321
|
+
end
|
322
|
+
|
323
|
+
def parse(expression)
|
324
|
+
Parser.parse(expression, base_duration: @base_duration)
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
module GDV
|
330
|
+
include Musa::Neumalang::Dataset
|
331
|
+
|
332
|
+
NaturalKeys = [:grade, :sharps, :octave, :duration, :velocity, :silence].freeze
|
333
|
+
|
334
|
+
attr_accessor :base_duration
|
335
|
+
|
336
|
+
def to_pdv(scale)
|
337
|
+
r = {}.extend Musa::Datasets::PDV
|
338
|
+
r.base_duration = @base_duration
|
339
|
+
|
340
|
+
if self[:grade]
|
341
|
+
r[:pitch] = if self[:silence]
|
342
|
+
:silence
|
343
|
+
else
|
344
|
+
scale[self[:grade]].sharp(self[:sharps] || 0).octave(self[:octave] || 0).pitch
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
if self[:duration]
|
349
|
+
r[:duration] = self[:duration]
|
350
|
+
end
|
351
|
+
|
352
|
+
if self[:velocity]
|
353
|
+
# ppp = 16 ... fff = 127
|
354
|
+
r[:velocity] = [16, 32, 48, 64, 80, 96, 112, 127][self[:velocity] + 3]
|
355
|
+
end
|
356
|
+
|
357
|
+
(keys - NaturalKeys).each { |k| r[k] = self[k] }
|
358
|
+
|
359
|
+
r
|
360
|
+
end
|
361
|
+
|
362
|
+
def to_neuma(mode = nil)
|
363
|
+
mode ||= :dotted # :parenthesis
|
364
|
+
|
365
|
+
@base_duration ||= Rational(1,4)
|
366
|
+
|
367
|
+
attributes = []
|
368
|
+
|
369
|
+
c = 0
|
370
|
+
|
371
|
+
if include?(:silence)
|
372
|
+
attributes[c] = :silence
|
373
|
+
elsif include?(:grade)
|
374
|
+
attributes[c] = self[:grade].to_s
|
375
|
+
if include?(:sharps)
|
376
|
+
if self[:sharps] > 0
|
377
|
+
attributes[c] += '#' * self[:sharps]
|
378
|
+
elsif self[:sharps] < 0
|
379
|
+
attributes[c] += '_' * self[:sharps]
|
380
|
+
end
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
attributes[c += 1] = 'o' + positive_sign_of(self[:octave]) + self[:octave].to_s if self[:octave]
|
385
|
+
attributes[c += 1] = (self[:duration] / @base_duration).to_s if self[:duration]
|
386
|
+
attributes[c += 1] = velocity_of(self[:velocity]) if self[:velocity]
|
387
|
+
|
388
|
+
(keys - NaturalKeys).each do |k|
|
389
|
+
attributes[c += 1] = modificator_string(k, self[k])
|
390
|
+
end
|
391
|
+
|
392
|
+
if mode == :dotted
|
393
|
+
attributes.join '.'
|
394
|
+
|
395
|
+
elsif mode == :parenthesis
|
396
|
+
'(' + attributes.join(', ') + ')'
|
397
|
+
else
|
398
|
+
attributes
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
def velocity_of(x)
|
403
|
+
%w[ppp pp p mp mf f ff fff][x + 3]
|
404
|
+
end
|
405
|
+
|
406
|
+
private :velocity_of
|
407
|
+
|
408
|
+
def to_gdvd(scale, previous: nil)
|
409
|
+
r = {}.extend Musa::Datasets::GDVd
|
410
|
+
r.base_duration = @base_duration
|
411
|
+
|
412
|
+
if previous
|
413
|
+
|
414
|
+
if include?(:silence)
|
415
|
+
r[:abs_grade] = :silence
|
416
|
+
|
417
|
+
elsif include?(:grade) && !previous.include?(:grade)
|
418
|
+
r[:abs_grade] = self[:grade]
|
419
|
+
r[:abs_sharps] = self[:sharps]
|
420
|
+
|
421
|
+
elsif include?(:grade) && previous.include?(:grade)
|
422
|
+
if self[:grade] != previous[:grade] ||
|
423
|
+
(self[:sharps] || 0) != (previous[:sharps] || 0)
|
424
|
+
|
425
|
+
r[:delta_grade] = scale[self[:grade]].octave(self[:octave]).wide_grade - scale[previous[:grade]].octave(previous[:octave]).wide_grade
|
426
|
+
r[:delta_sharps] = (self[:sharps] || 0) - (previous[:sharps] || 0)
|
427
|
+
end
|
428
|
+
elsif include?(:sharps)
|
429
|
+
r[:delta_sharps] = self[:sharps] - (previous[:sharps] || 0)
|
430
|
+
end
|
431
|
+
|
432
|
+
if self[:duration] && previous[:duration] && (self[:duration] != previous[:duration])
|
433
|
+
r[:delta_duration] = (self[:duration] - previous[:duration])
|
434
|
+
end
|
435
|
+
|
436
|
+
if self[:velocity] && previous[:velocity] && (self[:velocity] != previous[:velocity])
|
437
|
+
r[:delta_velocity] = self[:velocity] - previous[:velocity]
|
438
|
+
end
|
439
|
+
else
|
440
|
+
r[:abs_grade] = self[:grade] if self[:grade]
|
441
|
+
r[:abs_duration] = self[:duration] if self[:duration]
|
442
|
+
r[:abs_velocity] = self[:velocity] if self[:velocity]
|
443
|
+
end
|
444
|
+
|
445
|
+
(keys - NaturalKeys).each { |k| r[k] = self[k] }
|
446
|
+
|
447
|
+
r
|
448
|
+
end
|
449
|
+
|
450
|
+
class NeumaDecoder < Musa::Neumalang::Decoder # to get a GDV
|
451
|
+
def initialize(scale, base_duration: nil, processor: nil, **base)
|
452
|
+
@base_duration = base_duration || Rational(1,4)
|
453
|
+
|
454
|
+
base = { grade: 0, octave: 0, duration: @base_duration, velocity: 1 } if base.empty?
|
455
|
+
|
456
|
+
@scale = scale
|
457
|
+
|
458
|
+
super base, processor: processor
|
459
|
+
end
|
460
|
+
|
461
|
+
attr_accessor :scale, :base_duration
|
462
|
+
|
463
|
+
def parse(expression)
|
464
|
+
expression = expression.clone
|
465
|
+
|
466
|
+
appogiatura_neuma = expression.find { |_| _.is_a?(Hash) && _[:appogiatura] }
|
467
|
+
expression.delete appogiatura_neuma if appogiatura_neuma
|
468
|
+
|
469
|
+
parsed = GDVd::Parser.parse(expression, base_duration: @base_duration)
|
470
|
+
|
471
|
+
if appogiatura_neuma
|
472
|
+
appogiatura = GDVd::Parser.parse(appogiatura_neuma[:appogiatura], base_duration: @base_duration)
|
473
|
+
parsed[:appogiatura] = appogiatura
|
474
|
+
end
|
475
|
+
|
476
|
+
parsed
|
477
|
+
end
|
478
|
+
|
479
|
+
def subcontext
|
480
|
+
NeumaDecoder.new @scale, base_duration: @base_duration, processor: @processor, **@last
|
481
|
+
end
|
482
|
+
|
483
|
+
def apply(action, on:)
|
484
|
+
gdv = action.to_gdv @scale, previous: on
|
485
|
+
|
486
|
+
appogiatura_action = action[:appogiatura]
|
487
|
+
gdv[:appogiatura] = appogiatura_action.to_gdv @scale, previous: on if appogiatura_action
|
488
|
+
|
489
|
+
gdv
|
490
|
+
end
|
491
|
+
|
492
|
+
def inspect
|
493
|
+
"GDV NeumaDecoder: @last = #{@last}"
|
494
|
+
end
|
495
|
+
|
496
|
+
alias to_s inspect
|
497
|
+
end
|
498
|
+
end
|
499
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'musa-dsl/neuma'
|
2
|
+
|
3
|
+
module Musa::Datasets
|
4
|
+
module PDV
|
5
|
+
include Musa::Neumalang::Dataset
|
6
|
+
|
7
|
+
NaturalKeys = [:pitch, :duration, :velocity].freeze
|
8
|
+
|
9
|
+
attr_accessor :base_duration
|
10
|
+
|
11
|
+
def to_gdv(scale)
|
12
|
+
r = {}.extend Musa::Datasets::GDV
|
13
|
+
r.base_duration = @base_duration
|
14
|
+
|
15
|
+
if self[:pitch]
|
16
|
+
if self[:pitch] == :silence
|
17
|
+
r[:grade] = :silence
|
18
|
+
else
|
19
|
+
note = scale.note_of_pitch(self[:pitch], allow_chromatic: true)
|
20
|
+
|
21
|
+
if background_note = note.background_note
|
22
|
+
r[:grade] = background_note.grade
|
23
|
+
r[:octave] = background_note.octave
|
24
|
+
r[:sharps] = note.background_sharps
|
25
|
+
else
|
26
|
+
r[:grade] = note.grade
|
27
|
+
r[:octave] = note.octave
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
r[:duration] = self[:duration] if self[:duration]
|
33
|
+
|
34
|
+
if self[:velocity]
|
35
|
+
# ppp = 16 ... fff = 127
|
36
|
+
r[:velocity] = [0..16, 17..32, 33..48, 49..64, 65..80, 81..96, 97..112, 113..127].index { |r| r.cover? self[:velocity] } - 3
|
37
|
+
end
|
38
|
+
|
39
|
+
(keys - NaturalKeys).each { |k| r[k] = self[k] }
|
40
|
+
|
41
|
+
r
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'musa-dsl/core-ext/as-context-run'
|
2
|
+
|
3
|
+
module Musa
|
4
|
+
class Darwin
|
5
|
+
def initialize(&block)
|
6
|
+
raise ArgumentError, 'block is needed' unless block
|
7
|
+
|
8
|
+
main_context = MainContext.new block
|
9
|
+
|
10
|
+
@measures = main_context._measures
|
11
|
+
@weights = main_context._weights
|
12
|
+
end
|
13
|
+
|
14
|
+
def select(population)
|
15
|
+
measured_objects = []
|
16
|
+
|
17
|
+
population.each do |object|
|
18
|
+
context = MeasuresEvalContext.new
|
19
|
+
|
20
|
+
context.instance_exec object, &@measures
|
21
|
+
measure = context._measure
|
22
|
+
|
23
|
+
measured_objects << { object: object, measure: context._measure } unless measure.died?
|
24
|
+
end
|
25
|
+
|
26
|
+
limits = {}
|
27
|
+
|
28
|
+
measured_objects.each do |measured_object|
|
29
|
+
measure = measured_object[:measure]
|
30
|
+
|
31
|
+
measure.dimensions.each do |measure_name, value|
|
32
|
+
limit = limits[measure_name] ||= { min: nil, max: nil }
|
33
|
+
|
34
|
+
limit[:min] = value.to_f if limit[:min].nil? || limit[:min] > value
|
35
|
+
limit[:max] = value.to_f if limit[:max].nil? || limit[:max] < value
|
36
|
+
|
37
|
+
limit[:range] = limit[:max] - limit[:min]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# warn "Darwin.select: weights #{@weights}"
|
42
|
+
|
43
|
+
measured_objects.each do |measured_object|
|
44
|
+
measure = measured_object[:measure]
|
45
|
+
|
46
|
+
measure.dimensions.each do |dimension_name, value|
|
47
|
+
limit = limits[dimension_name]
|
48
|
+
measure.normalized_dimensions[dimension_name] = (value - limit[:min]) / limit[:range]
|
49
|
+
end
|
50
|
+
|
51
|
+
# warn "Darwin.select: #{measured_object[:object]} #{measured_object[:measure]} weight=#{measured_object[:measure].evaluate_weight(@weights).round(2)}"
|
52
|
+
end
|
53
|
+
|
54
|
+
measured_objects.sort! { |a, b| evaluate_weights a[:measure], b[:measure] }
|
55
|
+
|
56
|
+
measured_objects.collect { |measured_object| measured_object[:object] }
|
57
|
+
end
|
58
|
+
|
59
|
+
def evaluate_weights(measure_a, measure_b)
|
60
|
+
measure_b.evaluate_weight(@weights) <=> measure_a.evaluate_weight(@weights)
|
61
|
+
end
|
62
|
+
|
63
|
+
class MainContext
|
64
|
+
attr_reader :_measures, :_weights
|
65
|
+
|
66
|
+
def initialize(block)
|
67
|
+
@_weights = {}
|
68
|
+
_as_context_run block
|
69
|
+
end
|
70
|
+
|
71
|
+
def measures(&block)
|
72
|
+
@_measures = block
|
73
|
+
end
|
74
|
+
|
75
|
+
def weight(**feature_or_dimension_weights)
|
76
|
+
feature_or_dimension_weights.each do |name, value|
|
77
|
+
@_weights[name] = value
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class MeasuresEvalContext
|
83
|
+
def initialize
|
84
|
+
@_features = {}
|
85
|
+
@_dimensions = {}
|
86
|
+
@_died = false
|
87
|
+
end
|
88
|
+
|
89
|
+
def _measure
|
90
|
+
Measure.new @_features, @_dimensions, @_died
|
91
|
+
end
|
92
|
+
|
93
|
+
def feature(feature_name)
|
94
|
+
@_features[feature_name] = true
|
95
|
+
end
|
96
|
+
|
97
|
+
def dimension(dimension_name, value)
|
98
|
+
@_dimensions[dimension_name] = value
|
99
|
+
end
|
100
|
+
|
101
|
+
def die
|
102
|
+
@_died = true
|
103
|
+
end
|
104
|
+
|
105
|
+
def died?
|
106
|
+
@_died
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class Measure
|
111
|
+
attr_reader :features, :dimensions, :normalized_dimensions
|
112
|
+
|
113
|
+
def initialize(features, dimensions, died)
|
114
|
+
@features = features
|
115
|
+
@dimensions = dimensions
|
116
|
+
@died = died
|
117
|
+
|
118
|
+
@normalized_dimensions = {}
|
119
|
+
end
|
120
|
+
|
121
|
+
def died?
|
122
|
+
@died
|
123
|
+
end
|
124
|
+
|
125
|
+
def evaluate_weight(weights)
|
126
|
+
total = 0.0
|
127
|
+
|
128
|
+
unless @died
|
129
|
+
weights.each do |name, weight|
|
130
|
+
total += @normalized_dimensions[name] * weight if @normalized_dimensions.key? name
|
131
|
+
total += weight if @features[name]
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
total
|
136
|
+
end
|
137
|
+
|
138
|
+
def inspect
|
139
|
+
"Measure features=#{@features.collect { |k, _v| k }} dimensions=#{@normalized_dimensions.collect { |k, v| [k, [@dimensions[k].round(5), v.round(2)]] }.to_h}"
|
140
|
+
end
|
141
|
+
|
142
|
+
alias to_s inspect
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|