musa-dsl 0.14.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/Gemfile +20 -0
  4. data/LICENSE.md +157 -0
  5. data/README.md +8 -0
  6. data/lib/musa-dsl/core-ext/array-apply-get.rb +18 -0
  7. data/lib/musa-dsl/core-ext/array-explode-ranges.rb +29 -0
  8. data/lib/musa-dsl/core-ext/array-to-neumas.rb +28 -0
  9. data/lib/musa-dsl/core-ext/array-to-serie.rb +20 -0
  10. data/lib/musa-dsl/core-ext/arrayfy.rb +15 -0
  11. data/lib/musa-dsl/core-ext/as-context-run.rb +44 -0
  12. data/lib/musa-dsl/core-ext/duplicate.rb +134 -0
  13. data/lib/musa-dsl/core-ext/dynamic-proxy.rb +55 -0
  14. data/lib/musa-dsl/core-ext/inspect-nice.rb +28 -0
  15. data/lib/musa-dsl/core-ext/key-parameters-procedure-binder.rb +85 -0
  16. data/lib/musa-dsl/core-ext/proc-nice.rb +13 -0
  17. data/lib/musa-dsl/core-ext/send-nice.rb +21 -0
  18. data/lib/musa-dsl/core-ext/string-to-neumas.rb +27 -0
  19. data/lib/musa-dsl/core-ext.rb +13 -0
  20. data/lib/musa-dsl/datasets/gdv-decorators.rb +221 -0
  21. data/lib/musa-dsl/datasets/gdv.rb +499 -0
  22. data/lib/musa-dsl/datasets/pdv.rb +44 -0
  23. data/lib/musa-dsl/datasets.rb +5 -0
  24. data/lib/musa-dsl/generative/darwin.rb +145 -0
  25. data/lib/musa-dsl/generative/generative-grammar.rb +294 -0
  26. data/lib/musa-dsl/generative/markov.rb +78 -0
  27. data/lib/musa-dsl/generative/rules.rb +282 -0
  28. data/lib/musa-dsl/generative/variatio.rb +331 -0
  29. data/lib/musa-dsl/generative.rb +5 -0
  30. data/lib/musa-dsl/midi/midi-recorder.rb +83 -0
  31. data/lib/musa-dsl/midi/midi-voices.rb +274 -0
  32. data/lib/musa-dsl/midi.rb +2 -0
  33. data/lib/musa-dsl/music/chord-definition.rb +99 -0
  34. data/lib/musa-dsl/music/chord-definitions.rb +13 -0
  35. data/lib/musa-dsl/music/chords.rb +326 -0
  36. data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +204 -0
  37. data/lib/musa-dsl/music/scales.rb +584 -0
  38. data/lib/musa-dsl/music.rb +6 -0
  39. data/lib/musa-dsl/neuma/neuma.rb +181 -0
  40. data/lib/musa-dsl/neuma.rb +1 -0
  41. data/lib/musa-dsl/neumalang/neumalang.citrus +294 -0
  42. data/lib/musa-dsl/neumalang/neumalang.rb +179 -0
  43. data/lib/musa-dsl/neumalang.rb +3 -0
  44. data/lib/musa-dsl/repl/repl.rb +143 -0
  45. data/lib/musa-dsl/repl.rb +1 -0
  46. data/lib/musa-dsl/sequencer/base-sequencer-implementation-control.rb +189 -0
  47. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +354 -0
  48. data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +382 -0
  49. data/lib/musa-dsl/sequencer/base-sequencer-public.rb +261 -0
  50. data/lib/musa-dsl/sequencer/sequencer-dsl.rb +94 -0
  51. data/lib/musa-dsl/sequencer/sequencer.rb +3 -0
  52. data/lib/musa-dsl/sequencer.rb +1 -0
  53. data/lib/musa-dsl/series/base-series.rb +245 -0
  54. data/lib/musa-dsl/series/hash-serie-splitter.rb +194 -0
  55. data/lib/musa-dsl/series/holder-serie.rb +87 -0
  56. data/lib/musa-dsl/series/main-serie-constructors.rb +726 -0
  57. data/lib/musa-dsl/series/main-serie-operations.rb +1151 -0
  58. data/lib/musa-dsl/series/proxy-serie.rb +69 -0
  59. data/lib/musa-dsl/series/queue-serie.rb +94 -0
  60. data/lib/musa-dsl/series/series.rb +8 -0
  61. data/lib/musa-dsl/series.rb +1 -0
  62. data/lib/musa-dsl/transport/clock.rb +36 -0
  63. data/lib/musa-dsl/transport/dummy-clock.rb +47 -0
  64. data/lib/musa-dsl/transport/external-tick-clock.rb +31 -0
  65. data/lib/musa-dsl/transport/input-midi-clock.rb +124 -0
  66. data/lib/musa-dsl/transport/timer-clock.rb +102 -0
  67. data/lib/musa-dsl/transport/timer.rb +40 -0
  68. data/lib/musa-dsl/transport/transport.rb +137 -0
  69. data/lib/musa-dsl/transport.rb +9 -0
  70. data/lib/musa-dsl.rb +17 -0
  71. data/musa-dsl.gemspec +17 -0
  72. 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,5 @@
1
+ require 'musa-dsl/datasets/gdv'
2
+ require 'musa-dsl/datasets/gdv-decorators'
3
+
4
+ require 'musa-dsl/datasets/pdv'
5
+
@@ -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