musa-dsl 0.14.16

Sign up to get free protection for your applications and to get access to all the features.
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