musa-dsl 0.40.0 → 0.41.0

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/Gemfile +0 -1
  4. data/docs/subsystems/music.md +326 -15
  5. data/lib/musa-dsl/generative/darwin.rb +36 -1
  6. data/lib/musa-dsl/generative/generative-grammar.rb +28 -0
  7. data/lib/musa-dsl/generative/markov.rb +2 -0
  8. data/lib/musa-dsl/generative/rules.rb +54 -0
  9. data/lib/musa-dsl/generative/variatio.rb +69 -0
  10. data/lib/musa-dsl/midi/midi-recorder.rb +4 -0
  11. data/lib/musa-dsl/midi/midi-voices.rb +10 -0
  12. data/lib/musa-dsl/music/chords.rb +54 -9
  13. data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +70 -521
  14. data/lib/musa-dsl/music/scale_kinds/bebop/bebop_dominant_scale_kind.rb +110 -0
  15. data/lib/musa-dsl/music/scale_kinds/bebop/bebop_major_scale_kind.rb +110 -0
  16. data/lib/musa-dsl/music/scale_kinds/bebop/bebop_minor_scale_kind.rb +110 -0
  17. data/lib/musa-dsl/music/scale_kinds/blues/blues_major_scale_kind.rb +100 -0
  18. data/lib/musa-dsl/music/scale_kinds/blues/blues_scale_kind.rb +99 -0
  19. data/lib/musa-dsl/music/scale_kinds/chromatic_scale_kind.rb +79 -0
  20. data/lib/musa-dsl/music/scale_kinds/ethnic/double_harmonic_scale_kind.rb +102 -0
  21. data/lib/musa-dsl/music/scale_kinds/ethnic/hungarian_minor_scale_kind.rb +102 -0
  22. data/lib/musa-dsl/music/scale_kinds/ethnic/neapolitan_major_scale_kind.rb +102 -0
  23. data/lib/musa-dsl/music/scale_kinds/ethnic/neapolitan_minor_scale_kind.rb +101 -0
  24. data/lib/musa-dsl/music/scale_kinds/ethnic/phrygian_dominant_scale_kind.rb +103 -0
  25. data/lib/musa-dsl/music/scale_kinds/harmonic_major/harmonic_major_scale_kind.rb +104 -0
  26. data/lib/musa-dsl/music/scale_kinds/major_scale_kind.rb +110 -0
  27. data/lib/musa-dsl/music/scale_kinds/melodic_minor/altered_scale_kind.rb +106 -0
  28. data/lib/musa-dsl/music/scale_kinds/melodic_minor/dorian_b2_scale_kind.rb +104 -0
  29. data/lib/musa-dsl/music/scale_kinds/melodic_minor/locrian_sharp2_scale_kind.rb +103 -0
  30. data/lib/musa-dsl/music/scale_kinds/melodic_minor/lydian_augmented_scale_kind.rb +103 -0
  31. data/lib/musa-dsl/music/scale_kinds/melodic_minor/lydian_dominant_scale_kind.rb +106 -0
  32. data/lib/musa-dsl/music/scale_kinds/melodic_minor/melodic_minor_scale_kind.rb +104 -0
  33. data/lib/musa-dsl/music/scale_kinds/melodic_minor/mixolydian_b6_scale_kind.rb +103 -0
  34. data/lib/musa-dsl/music/scale_kinds/minor_harmonic_scale_kind.rb +125 -0
  35. data/lib/musa-dsl/music/scale_kinds/minor_natural_scale_kind.rb +123 -0
  36. data/lib/musa-dsl/music/scale_kinds/modes/dorian_scale_kind.rb +111 -0
  37. data/lib/musa-dsl/music/scale_kinds/modes/locrian_scale_kind.rb +114 -0
  38. data/lib/musa-dsl/music/scale_kinds/modes/lydian_scale_kind.rb +111 -0
  39. data/lib/musa-dsl/music/scale_kinds/modes/mixolydian_scale_kind.rb +111 -0
  40. data/lib/musa-dsl/music/scale_kinds/modes/phrygian_scale_kind.rb +111 -0
  41. data/lib/musa-dsl/music/scale_kinds/pentatonic/pentatonic_major_scale_kind.rb +93 -0
  42. data/lib/musa-dsl/music/scale_kinds/pentatonic/pentatonic_minor_scale_kind.rb +99 -0
  43. data/lib/musa-dsl/music/scale_kinds/symmetric/diminished_hw_scale_kind.rb +110 -0
  44. data/lib/musa-dsl/music/scale_kinds/symmetric/diminished_wh_scale_kind.rb +110 -0
  45. data/lib/musa-dsl/music/scale_kinds/symmetric/whole_tone_scale_kind.rb +99 -0
  46. data/lib/musa-dsl/music/scale_systems/equally_tempered_12_tone_scale_system.rb +80 -0
  47. data/lib/musa-dsl/music/scale_systems/twelve_semitones_scale_system.rb +60 -0
  48. data/lib/musa-dsl/music/scales.rb +427 -0
  49. data/lib/musa-dsl/series/buffer-serie.rb +6 -0
  50. data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +23 -0
  51. data/lib/musa-dsl/series/quantizer-serie.rb +12 -0
  52. data/lib/musa-dsl/series/queue-serie.rb +13 -0
  53. data/lib/musa-dsl/version.rb +2 -1
  54. data/musa-dsl.gemspec +20 -15
  55. metadata +85 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 603ecc3ddd2b6bcead498e08a747035df5ff2c2e87e4f527c66c48d5631ab435
4
- data.tar.gz: 37dde89a2c5ff9dda9691ff245fa06b4e870475fb7320ef10bca5ef0a7a84ed2
3
+ metadata.gz: 2a2ee1f86c7d0b4a6c6ecb95b92ea4b15ead3a305c9c6252ea74eb1d437b16fb
4
+ data.tar.gz: 07fea02dd0fe457b16c79e18eb82fedb0236c929fa3d1b1e3b32b621d419e6f7
5
5
  SHA512:
6
- metadata.gz: 8b1bd0544fd6b6fe611718809e50422e3accf376e88da0832e7b22b7f5b279eb52d9cab2fe3c3f75d2e996e17f0c1a604a2e56676249c946dae533988db8a54c
7
- data.tar.gz: 4c4dbcbfb7243f751205f275eaba483e8d29ce3ca5f59e600a1b8be9d1687c0815572916415b4dfbb0ac14ae7f2bbaec3ee610af2922fe8ab9de42e4ab3a04b2
6
+ metadata.gz: 0f186d4ac7ee38fb1984a7737e220c25a12411b104728181854898580a7059f87efc6828a769bee268695cb80d3d2818b6dadc0e21a4a1a3a57c853c7502817c
7
+ data.tar.gz: 84bf07953180497c334da1d471067a4561957da58a9aac4fe454bbfac364f4c44295e1e12e7ad935ca04f3e304e248fe9d89f2d866aaed9c62d87634d2fcbe15
data/.gitignore CHANGED
@@ -12,3 +12,5 @@ test.musicxml
12
12
  doc
13
13
  .yardoc
14
14
  *.backup
15
+ .vscode
16
+ .ruby-lsp
data/Gemfile CHANGED
@@ -1,4 +1,3 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
- gem 'rubocop', group: 'development'
@@ -20,10 +20,54 @@ The **Scales** module provides hierarchical access to musical scales with multip
20
20
  - `:et12` (EquallyTempered12ToneScaleSystem): 12-tone equal temperament (default)
21
21
 
22
22
  **Available scale kinds in et12:**
23
- - `:major` - Major scale (Ionian mode)
24
- - `:minor` - Natural minor scale (Aeolian mode)
25
- - `:minor_harmonic` - Harmonic minor scale (raised 7th degree)
26
- - `:chromatic` - Chromatic scale (all 12 semitones)
23
+
24
+ *Core scales:*
25
+ - `:major` - Major scale (Ionian mode) - 7 notes
26
+ - `:minor` - Natural minor scale (Aeolian mode) - 7 notes
27
+ - `:minor_harmonic` - Harmonic minor scale (raised 7th) - 7 notes
28
+ - `:major_harmonic` - Harmonic major scale (lowered 6th) - 7 notes
29
+ - `:chromatic` - Chromatic scale (all 12 semitones) - 12 notes
30
+
31
+ *Greek/church modes:*
32
+ - `:dorian` - Dorian mode (minor with major 6th) - 7 notes
33
+ - `:phrygian` - Phrygian mode (minor with minor 2nd) - 7 notes
34
+ - `:lydian` - Lydian mode (major with augmented 4th) - 7 notes
35
+ - `:mixolydian` - Mixolydian mode (major with minor 7th) - 7 notes
36
+ - `:locrian` - Locrian mode (diminished 5th and minor 2nd) - 7 notes
37
+
38
+ *Pentatonic scales:*
39
+ - `:pentatonic_major` - Major pentatonic (no 4th or 7th) - 5 notes
40
+ - `:pentatonic_minor` - Minor pentatonic (no 2nd or 6th) - 5 notes
41
+
42
+ *Blues scales:*
43
+ - `:blues` - Blues scale (minor pentatonic + b5 blue note) - 6 notes
44
+ - `:blues_major` - Major blues scale (major pentatonic + b3 blue note) - 6 notes
45
+
46
+ *Symmetric scales:*
47
+ - `:whole_tone` - Whole tone scale (all whole steps) - 6 notes
48
+ - `:diminished_hw` - Diminished half-whole (octatonic) - 8 notes
49
+ - `:diminished_wh` - Diminished whole-half (dominant diminished) - 8 notes
50
+
51
+ *Melodic minor modes:*
52
+ - `:minor_melodic` - Melodic minor (jazz minor) - 7 notes
53
+ - `:dorian_b2` - Dorian b2 / Phrygian #6 - 7 notes
54
+ - `:lydian_augmented` - Lydian augmented (#4, #5) - 7 notes
55
+ - `:lydian_dominant` - Lydian dominant / Bartók scale (#4, b7) - 7 notes
56
+ - `:mixolydian_b6` - Mixolydian b6 / Hindu scale - 7 notes
57
+ - `:locrian_sharp2` - Locrian #2 / Half-diminished scale - 7 notes
58
+ - `:altered` - Altered / Super Locrian (all tensions altered) - 7 notes
59
+
60
+ *Ethnic/exotic scales:*
61
+ - `:double_harmonic` - Double harmonic / Byzantine (two augmented 2nds) - 7 notes
62
+ - `:hungarian_minor` - Hungarian minor / Gypsy minor (#4, raised 7th) - 7 notes
63
+ - `:phrygian_dominant` - Phrygian dominant / Spanish Phrygian (b2, major 3rd) - 7 notes
64
+ - `:neapolitan_minor` - Neapolitan minor (harmonic minor with b2) - 7 notes
65
+ - `:neapolitan_major` - Neapolitan major (melodic minor with b2) - 7 notes
66
+
67
+ *Bebop scales:*
68
+ - `:bebop_dominant` - Bebop dominant (Mixolydian + major 7th passing) - 8 notes
69
+ - `:bebop_major` - Bebop major (major + #5 passing) - 8 notes
70
+ - `:bebop_minor` - Bebop minor (Dorian + major 7th passing) - 8 notes
27
71
 
28
72
  ## Chords System
29
73
 
@@ -93,6 +137,49 @@ third_up = c_major.tonic.sharp(4) # => E (+4 semitones = major third)
93
137
  # Frequency calculation
94
138
  frequency = c_major.tonic.frequency # => 261.63 Hz (middle C at A=440)
95
139
 
140
+ # Greek modes (church modes)
141
+ d_dorian = tuning.dorian[62] # D Dorian (minor with major 6th)
142
+ e_phrygian = tuning.phrygian[64] # E Phrygian (minor with minor 2nd)
143
+ f_lydian = tuning.lydian[65] # F Lydian (major with augmented 4th)
144
+ g_mixolydian = tuning.mixolydian[67] # G Mixolydian (major with minor 7th)
145
+ b_locrian = tuning.locrian[71] # B Locrian (diminished 5th)
146
+
147
+ # Access notes in Greek modes by function
148
+ d_dorian.tonic.pitch # => 62 (D)
149
+ d_dorian[:vi].pitch # => 71 (B - the major 6th characteristic of Dorian)
150
+
151
+ e_phrygian[:ii].pitch # => 65 (F - the minor 2nd characteristic of Phrygian)
152
+
153
+ f_lydian[:IV].pitch # => 71 (B - the augmented 4th characteristic of Lydian)
154
+
155
+ g_mixolydian[:VII].pitch # => 77 (F - the minor 7th characteristic of Mixolydian)
156
+
157
+ b_locrian[:v].pitch # => 77 (F - the diminished 5th characteristic of Locrian)
158
+
159
+ # Pentatonic and blues scales
160
+ c_pent_maj = tuning.pentatonic_major[60] # C major pentatonic
161
+ a_pent_min = tuning.pentatonic_minor[69] # A minor pentatonic
162
+ a_blues = tuning.blues[69] # A blues scale
163
+ a_blues[:blue].pitch # => 75 (Eb - the blue note)
164
+
165
+ # Symmetric scales
166
+ c_whole = tuning.whole_tone[60] # C whole tone
167
+ c_dim_hw = tuning.diminished_hw[60] # C diminished (half-whole)
168
+ c_dim_wh = tuning.diminished_wh[60] # C diminished (whole-half)
169
+
170
+ # Melodic minor modes
171
+ c_mel_min = tuning.minor_melodic[60] # C melodic minor
172
+ g_altered = tuning.altered[67] # G altered (for G7alt chords)
173
+ f_lyd_dom = tuning.lydian_dominant[65] # F lydian dominant (F7#11)
174
+
175
+ # Ethnic scales
176
+ e_phry_dom = tuning.phrygian_dominant[64] # E Phrygian dominant (flamenco)
177
+ a_hung_min = tuning.hungarian_minor[69] # A Hungarian minor
178
+
179
+ # Bebop scales (8 notes for smooth eighth-note lines)
180
+ g_bebop = tuning.bebop_dominant[67] # G bebop dominant
181
+ g_bebop[7].pitch # => 78 (F# - the chromatic passing tone)
182
+
96
183
  # Create chords from scale degrees
97
184
  i_chord = c_major.tonic.chord # C major triad [C, E, G]
98
185
  ii_chord = c_major.supertonic.chord # D minor triad [D, F, A]
@@ -133,6 +220,230 @@ doubled = i_chord.duplicate(root: -2, third: [-1, 1]) # Root 2 down, third 1 do
133
220
  lower = i_chord.octave(-1) # Move chord down one octave
134
221
  ```
135
222
 
223
+ ### Chord-Scale Navigation
224
+
225
+ MusaDSL provides methods to explore the relationship between chords and scales,
226
+ enabling harmonic analysis and discovery of functional contexts.
227
+
228
+ #### Checking if a Scale Contains a Chord
229
+
230
+ ```ruby
231
+ c_major = Scales.et12[440.0].major[60]
232
+ g7 = c_major.dominant.chord :seventh
233
+
234
+ c_major.contains_chord?(g7) # => true
235
+ c_major.degree_of_chord(g7) # => 4 (V degree, 0-based)
236
+
237
+ # Non-diatonic chords return false/nil
238
+ cm = c_major.tonic.chord.with_quality(:minor) # C minor (Eb not in C major)
239
+ c_major.contains_chord?(cm) # => false
240
+ c_major.degree_of_chord(cm) # => nil
241
+ ```
242
+
243
+ #### Creating a Chord in a Different Scale Context
244
+
245
+ ```ruby
246
+ # Get the same chord but with a different scale as context
247
+ g_mixolydian = Scales.et12[440.0].mixolydian[67]
248
+ g7_in_mixolydian = g_mixolydian.chord_on(g7)
249
+ g7_in_mixolydian.scale # => G Mixolydian scale
250
+ g_mixolydian.degree_of_chord(g7_in_mixolydian) # => 0 (I degree)
251
+ ```
252
+
253
+ #### Finding Scales That Contain a Chord
254
+
255
+ ```ruby
256
+ g_triad = c_major.dominant.chord # G-B-D
257
+
258
+ # Search in diatonic scales
259
+ g_triad.in_scales(family: :diatonic)
260
+ # => [Chord in C major (V), Chord in G major (I), Chord in D major (IV), ...]
261
+
262
+ # Search using metadata filters
263
+ g_triad.in_scales(family: :greek_modes, brightness: -1..1)
264
+
265
+ # Search in all scale types
266
+ g_triad.in_scales
267
+
268
+ # Each result has its scale context
269
+ g_triad.in_scales(family: :diatonic).each do |chord|
270
+ scale = chord.scale
271
+ degree = scale.degree_of_chord(chord)
272
+ puts "#{scale.kind.class.id} rooted on #{scale.root_pitch}: degree #{degree}"
273
+ end
274
+ # Output:
275
+ # major rooted on 60: degree 4
276
+ # major rooted on 67: degree 0
277
+ # major rooted on 62: degree 3
278
+ ```
279
+
280
+ #### Low-Level Navigation Methods
281
+
282
+ ```ruby
283
+ tuning = Scales.et12[440.0]
284
+
285
+ # Search at ScaleKind level
286
+ tuning.major.scales_containing(g_triad)
287
+
288
+ # Search at ScaleSystemTuning level with metadata filters
289
+ tuning.chords_of(g7, family: :diatonic, roots: 60..71)
290
+ ```
291
+
292
+ ### Scale Kind Metadata
293
+
294
+ Scale kinds in MusaDSL have a three-layer metadata system that provides
295
+ both automatic structural information and extensible custom properties.
296
+
297
+ #### Metadata Layers
298
+
299
+ 1. **Intrinsic metadata**: Automatically derived from scale structure
300
+ - `id`: Scale kind identifier
301
+ - `grades`: Number of scale degrees
302
+ - `pitches`: Array of pitch offsets from root
303
+ - `intervals`: Intervals between consecutive degrees
304
+ - `has_leading_tone`: Whether scale has pitch 11 (semitone below octave)
305
+ - `has_tritone`: Whether scale contains tritone interval (pitch 6)
306
+ - `symmetric`: Type of symmetry if any (`:equal`, `:palindrome`, `:repeating`)
307
+
308
+ 2. **Base metadata**: Defined by musa-dsl library
309
+ - `family`: Scale family (`:diatonic`, `:greek_modes`, `:pentatonic`, etc.)
310
+ - `brightness`: Relative brightness (-3 to +3, major = 0)
311
+ - `character`: Array of descriptive tags
312
+ - `parent`: Parent scale and degree for modes (e.g., `{ scale: :major, degree: 2 }`)
313
+
314
+ 3. **Custom metadata**: Added by users at runtime
315
+
316
+ #### Accessing Metadata
317
+
318
+ ```ruby
319
+ tuning = Scales.et12[440.0]
320
+ major_class = tuning.major.class
321
+
322
+ # Get combined metadata (intrinsic < base < custom)
323
+ major_class.metadata
324
+ # => { id: :major, grades: 7, pitches: [0,2,4,5,7,9,11], family: :diatonic, ... }
325
+
326
+ # Get specific layer
327
+ major_class.intrinsic_metadata # Structure-derived only
328
+ major_class.base_metadata # Library-defined only
329
+ major_class.custom_metadata # User-added only
330
+
331
+ # Query helpers
332
+ major_class.has_metadata?(:family) # => true
333
+ major_class.has_metadata?(:family, :diatonic) # => true
334
+ major_class.has_metadata?(:character, :bright) # => true (array inclusion)
335
+ major_class.metadata_value(:brightness) # => 0
336
+ ```
337
+
338
+ #### Extending Metadata
339
+
340
+ Users can add custom metadata to any scale kind:
341
+
342
+ ```ruby
343
+ # Extend a specific scale kind class
344
+ tuning.dorian.class.extend_metadata(
345
+ my_mood: :nostalgic,
346
+ suitable_for: [:jazz, :fusion],
347
+ personal_rating: 5
348
+ )
349
+
350
+ # Or use the convenience method with scale ID
351
+ Scales.extend_metadata(:dorian, my_mood: :nostalgic)
352
+
353
+ # Multiple calls merge metadata
354
+ Scales.extend_metadata(:phrygian, mood: :dark)
355
+ Scales.extend_metadata(:phrygian, origin: :spanish) # Merges with previous
356
+
357
+ # Reset custom metadata if needed
358
+ tuning.dorian.class.reset_custom_metadata
359
+ ```
360
+
361
+ #### Custom Metadata Overrides
362
+
363
+ Custom metadata takes precedence over base metadata:
364
+
365
+ ```ruby
366
+ # Override library-defined family
367
+ Scales.extend_metadata(:dorian, family: :my_custom_category)
368
+ tuning.dorian.class.metadata[:family] # => :my_custom_category
369
+ ```
370
+
371
+ #### Brightness Scale Reference
372
+
373
+ | Value | Meaning | Examples |
374
+ |-------|---------|----------|
375
+ | +3 | Very bright | Lydian augmented |
376
+ | +2 | Bright | Lydian |
377
+ | +1 | Slightly bright | Mixolydian, Lydian dominant |
378
+ | 0 | Neutral (reference) | Major (Ionian) |
379
+ | -1 | Slightly dark | Dorian |
380
+ | -2 | Dark | Minor harmonic, Phrygian |
381
+ | -3 | Very dark | Locrian, Natural minor |
382
+
383
+ #### Scale Families
384
+
385
+ - `:diatonic` - Major, minor natural, minor harmonic
386
+ - `:greek_modes` - Dorian, Phrygian, Lydian, Mixolydian, Locrian
387
+ - `:melodic_minor_modes` - Melodic minor and its modes
388
+ - `:pentatonic` - Pentatonic major/minor
389
+ - `:blues` - Blues scales
390
+ - `:bebop` - Bebop scales
391
+ - `:symmetric` - Whole tone, diminished
392
+ - `:ethnic` - Hungarian, Spanish, Neapolitan, etc.
393
+ - `:chromatic` - Chromatic scale
394
+
395
+ ### Searching Scale Kinds
396
+
397
+ The `scale_kinds` method on ScaleSystemTuning allows searching and filtering
398
+ scale kinds by their metadata properties.
399
+
400
+ #### Basic Usage
401
+
402
+ ```ruby
403
+ tuning = Scales.et12[440.0]
404
+
405
+ # Get all scale kinds
406
+ tuning.scale_kinds
407
+ # => [major_kind, minor_kind, dorian_kind, ...]
408
+
409
+ # Filter by family
410
+ tuning.scale_kinds(family: :diatonic)
411
+ # => [major_kind, minor_kind, minor_harmonic_kind, harmonic_major_kind]
412
+
413
+ # Filter by brightness range
414
+ tuning.scale_kinds(brightness: -1..1)
415
+
416
+ # Filter by character
417
+ tuning.scale_kinds(character: :jazz)
418
+ ```
419
+
420
+ #### Custom Filtering with Blocks
421
+
422
+ Use a block for complex filtering based on any metadata:
423
+
424
+ ```ruby
425
+ # Scales with leading tone
426
+ tuning.scale_kinds { |klass| klass.intrinsic_metadata[:has_leading_tone] }
427
+
428
+ # Combine criteria and block
429
+ tuning.scale_kinds(family: :greek_modes) { |klass| klass.metadata[:brightness]&.negative? }
430
+ # => [dorian_kind, phrygian_kind, locrian_kind]
431
+ ```
432
+
433
+ #### Integration with Chord Search
434
+
435
+ The `chords_of` method uses the same metadata filtering:
436
+
437
+ ```ruby
438
+ g7 = tuning.major[60].dominant.chord(:seventh)
439
+
440
+ # Find G7 in diatonic scales
441
+ tuning.chords_of(g7, family: :diatonic)
442
+
443
+ # Find G7 in scales with specific brightness
444
+ tuning.chords_of(g7, brightness: -1..1)
445
+ ```
446
+
136
447
  ## Defining Custom Scale Systems, Scale Kinds, and Chord Definitions
137
448
 
138
449
  The framework is extensible, allowing users to define custom tuning systems, scale types, and chord structures:
@@ -173,19 +484,19 @@ require 'musa-dsl'
173
484
  include Musa::Scales
174
485
  include Musa::Chords
175
486
 
176
- # Example 1: Define a custom pentatonic scale kind for the 12-tone system
177
- class PentatonicMajorScaleKind < ScaleKind
487
+ # Example 1: Define a custom Hirajoshi scale (Japanese pentatonic)
488
+ class HirajoshiScaleKind < ScaleKind
178
489
  class << self
179
490
  def id
180
- :pentatonic_major
491
+ :hirajoshi
181
492
  end
182
493
 
183
494
  def pitches
184
495
  [{ functions: [:I, :_1, :tonic], pitch: 0 },
185
496
  { functions: [:II, :_2], pitch: 2 },
186
- { functions: [:III, :_3], pitch: 4 },
187
- { functions: [:V, :_5], pitch: 7 },
188
- { functions: [:VI, :_6], pitch: 9 }]
497
+ { functions: [:III, :_3], pitch: 3 },
498
+ { functions: [:V, :_4], pitch: 7 },
499
+ { functions: [:VI, :_5], pitch: 8 }]
189
500
  end
190
501
 
191
502
  def grades
@@ -195,14 +506,14 @@ class PentatonicMajorScaleKind < ScaleKind
195
506
  end
196
507
 
197
508
  # Register the new scale kind with the 12-tone system
198
- Scales.et12.register(PentatonicMajorScaleKind)
509
+ Scales.et12.register(HirajoshiScaleKind)
199
510
 
200
511
  # Use the new scale kind
201
512
  tuning = Scales.default_system.default_tuning
202
- c_pentatonic = tuning[:pentatonic_major][60] # C pentatonic major
203
- puts c_pentatonic[0].pitch # => 60 (C)
204
- puts c_pentatonic[1].pitch # => 62 (D)
205
- puts c_pentatonic[2].pitch # => 64 (E)
513
+ c_hirajoshi = tuning[:hirajoshi][60] # C Hirajoshi
514
+ puts c_hirajoshi[0].pitch # => 60 (C)
515
+ puts c_hirajoshi[1].pitch # => 62 (D)
516
+ puts c_hirajoshi[2].pitch # => 63 (Eb)
206
517
 
207
518
  # Example 2: Register a custom chord definition (sus4)
208
519
  Musa::Chords::ChordDefinition.register :sus4,
@@ -104,6 +104,8 @@ module Musa
104
104
  # measures { |obj| dimension :value, obj[:score] }
105
105
  # weight value: 1.0
106
106
  # end
107
+ #
108
+ # @return [void]
107
109
  def initialize(&block)
108
110
  raise ArgumentError, 'block is needed' unless block
109
111
 
@@ -175,6 +177,14 @@ module Musa
175
177
  measured_objects.collect { |measured_object| measured_object[:object] }
176
178
  end
177
179
 
180
+ # Compares two measures by their weighted fitness.
181
+ #
182
+ # @param measure_a [Measure] first measure to compare
183
+ # @param measure_b [Measure] second measure to compare
184
+ #
185
+ # @return [Integer] comparison result (-1, 0, 1)
186
+ #
187
+ # @api private
178
188
  def evaluate_weights(measure_a, measure_b)
179
189
  measure_b.evaluate_weight(@weights) <=> measure_a.evaluate_weight(@weights)
180
190
  end
@@ -189,6 +199,7 @@ module Musa
189
199
  # @return [Hash] weight assignments
190
200
  attr_reader :_measures, :_weights
191
201
 
202
+ # @return [void]
192
203
  # @api private
193
204
  def initialize(&block)
194
205
  @_weights = {}
@@ -198,6 +209,9 @@ module Musa
198
209
  # Defines measures evaluation block.
199
210
  #
200
211
  # @yield [object] measures DSL block
212
+ #
213
+ # @return [Proc] the measures block
214
+ #
201
215
  # @api private
202
216
  def measures(&block)
203
217
  @_measures = block
@@ -205,7 +219,10 @@ module Musa
205
219
 
206
220
  # Assigns weights to features/dimensions.
207
221
  #
208
- # @param feature_or_dimension_weights [Hash] name => weight pairs
222
+ # @param feature_or_dimension_weights [Hash{Symbol => Numeric}] name => weight pairs
223
+ #
224
+ # @return [void]
225
+ #
209
226
  # @api private
210
227
  def weight(**feature_or_dimension_weights)
211
228
  feature_or_dimension_weights.each do |name, value|
@@ -220,6 +237,7 @@ module Musa
220
237
  class MeasuresEvalContext
221
238
  include Musa::Extension::With
222
239
 
240
+ # @return [void]
223
241
  # @api private
224
242
  def initialize
225
243
  @_features = {}
@@ -238,6 +256,9 @@ module Musa
238
256
  # Marks object as having a feature.
239
257
  #
240
258
  # @param feature_name [Symbol] feature identifier
259
+ #
260
+ # @return [void]
261
+ #
241
262
  # @api private
242
263
  def feature(feature_name)
243
264
  @_features[feature_name] = true
@@ -247,6 +268,9 @@ module Musa
247
268
  #
248
269
  # @param dimension_name [Symbol] dimension identifier
249
270
  # @param value [Numeric] measured value
271
+ #
272
+ # @return [void]
273
+ #
250
274
  # @api private
251
275
  def dimension(dimension_name, value)
252
276
  @_dimensions[dimension_name] = value
@@ -254,6 +278,8 @@ module Musa
254
278
 
255
279
  # Marks object as non-viable (to be excluded).
256
280
  #
281
+ # @return [void]
282
+ #
257
283
  # @api private
258
284
  def die
259
285
  @_died = true
@@ -280,6 +306,12 @@ module Musa
280
306
  class Measure
281
307
  attr_reader :features, :dimensions, :normalized_dimensions
282
308
 
309
+ # @param features [Hash{Symbol => Boolean}] feature flags
310
+ # @param dimensions [Hash{Symbol => Numeric}] dimension values
311
+ # @param died [Boolean] viability status
312
+ #
313
+ # @return [void]
314
+ #
283
315
  # @api private
284
316
  def initialize(features, dimensions, died)
285
317
  @features = features
@@ -319,6 +351,9 @@ module Musa
319
351
  total
320
352
  end
321
353
 
354
+ # Returns string representation of measure.
355
+ #
356
+ # @return [String] formatted measure description
322
357
  def inspect
323
358
  "Measure features=#{@features.collect { |k, _v| k }} dimensions=#{@normalized_dimensions.collect { |k, v| [k, [@dimensions[k].round(5), v.round(2)]] }.to_h}"
324
359
  end
@@ -166,6 +166,8 @@ module Musa
166
166
  #
167
167
  # @param content [Object] element content
168
168
  # @param attributes [Hash, nil] element attributes
169
+ #
170
+ # @return [void]
169
171
  def initialize(content, attributes = nil)
170
172
  @content = content
171
173
  @attributes = attributes || {}
@@ -406,6 +408,14 @@ module Musa
406
408
 
407
409
  protected
408
410
 
411
+ # Generates condition block from simplified arguments.
412
+ #
413
+ # @param attribute [Symbol, nil] attribute to check
414
+ # @param after_collect_operation [Symbol, nil] operation on collected values
415
+ # @param comparison_method [Symbol, nil] comparison method to apply
416
+ # @param comparison_value [Object, nil] value to compare against
417
+ #
418
+ # @return [Proc, nil] condition block or nil if arguments incomplete
409
419
  def generate_simple_condition_block(attribute = nil,
410
420
  after_collect_operation = nil,
411
421
  comparison_method = nil,
@@ -451,6 +461,9 @@ module Musa
451
461
 
452
462
  # @param content [Object] node content
453
463
  # @param attributes [Hash] node attributes
464
+ #
465
+ # @return [void]
466
+ #
454
467
  # @api private
455
468
  def initialize(content, attributes)
456
469
  super()
@@ -482,6 +495,9 @@ module Musa
482
495
  class BlockNode < Node
483
496
  # @param attributes [Hash] node attributes
484
497
  # @yield [parent, attributes] block to generate content
498
+ #
499
+ # @return [void]
500
+ #
485
501
  # @api private
486
502
  def initialize(attributes, &block)
487
503
  @attributes = attributes
@@ -516,6 +532,9 @@ module Musa
516
532
  class ConditionNode < Node
517
533
  # @param node [Node] node to filter
518
534
  # @yield [option] condition block
535
+ #
536
+ # @return [void]
537
+ #
519
538
  # @api private
520
539
  def initialize(node, &block)
521
540
  @node = node
@@ -545,6 +564,9 @@ module Musa
545
564
  class OrNode < Node
546
565
  # @param node1 [Node] first alternative
547
566
  # @param node2 [Node] second alternative
567
+ #
568
+ # @return [void]
569
+ #
548
570
  # @api private
549
571
  def initialize(node1, node2)
550
572
  @node1 = node1
@@ -579,6 +601,9 @@ module Musa
579
601
  class NextNode < Node
580
602
  # @param node [Node] first node in sequence
581
603
  # @param after [Node] node to follow
604
+ #
605
+ # @return [void]
606
+ #
582
607
  # @api private
583
608
  def initialize(node, after)
584
609
  @node = node
@@ -609,6 +634,9 @@ module Musa
609
634
  class RepeatNode < Node
610
635
  # @param node [Node] node to repeat
611
636
  # @param max [Integer, nil] maximum repetitions (nil = infinite)
637
+ #
638
+ # @return [void]
639
+ #
612
640
  # @api private
613
641
  def initialize(node, max = nil)
614
642
  @node = node
@@ -113,6 +113,8 @@ module Musa
113
113
  # start: :a,
114
114
  # finish: :x
115
115
  # )
116
+ #
117
+ # @return [void]
116
118
  def initialize(transitions:, start:, finish: nil, random: nil)
117
119
  @transitions = transitions.clone.freeze
118
120