musa-dsl 0.40.0 → 0.42.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.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/Gemfile +0 -1
- data/README.md +15 -1
- data/docs/README.md +1 -0
- data/docs/subsystems/datasets.md +75 -0
- data/docs/subsystems/generative.md +92 -6
- data/docs/subsystems/music.md +349 -19
- data/docs/subsystems/transport.md +26 -0
- data/lib/musa-dsl/datasets/dataset.rb +2 -0
- data/lib/musa-dsl/datasets/gdv.rb +3 -3
- data/lib/musa-dsl/datasets/p.rb +1 -1
- data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +4 -2
- data/lib/musa-dsl/datasets/score.rb +3 -1
- data/lib/musa-dsl/generative/darwin.rb +36 -1
- data/lib/musa-dsl/generative/generative-grammar.rb +31 -1
- data/lib/musa-dsl/generative/markov.rb +3 -1
- data/lib/musa-dsl/generative/rules.rb +54 -0
- data/lib/musa-dsl/generative/variatio.rb +69 -0
- data/lib/musa-dsl/midi/midi-recorder.rb +4 -0
- data/lib/musa-dsl/midi/midi-voices.rb +13 -1
- data/lib/musa-dsl/music/chord-definition.rb +7 -5
- data/lib/musa-dsl/music/chord-definitions.rb +37 -0
- data/lib/musa-dsl/music/chords.rb +88 -21
- data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +70 -521
- data/lib/musa-dsl/music/scale_kinds/bebop/bebop_dominant_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/bebop/bebop_major_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/bebop/bebop_minor_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/blues/blues_major_scale_kind.rb +100 -0
- data/lib/musa-dsl/music/scale_kinds/blues/blues_scale_kind.rb +99 -0
- data/lib/musa-dsl/music/scale_kinds/chromatic_scale_kind.rb +79 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/double_harmonic_scale_kind.rb +102 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/hungarian_minor_scale_kind.rb +102 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/neapolitan_major_scale_kind.rb +102 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/neapolitan_minor_scale_kind.rb +101 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/phrygian_dominant_scale_kind.rb +103 -0
- data/lib/musa-dsl/music/scale_kinds/harmonic_major/harmonic_major_scale_kind.rb +104 -0
- data/lib/musa-dsl/music/scale_kinds/major_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/altered_scale_kind.rb +106 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/dorian_b2_scale_kind.rb +104 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/locrian_sharp2_scale_kind.rb +103 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/lydian_augmented_scale_kind.rb +103 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/lydian_dominant_scale_kind.rb +106 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/melodic_minor_scale_kind.rb +104 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/mixolydian_b6_scale_kind.rb +103 -0
- data/lib/musa-dsl/music/scale_kinds/minor_harmonic_scale_kind.rb +125 -0
- data/lib/musa-dsl/music/scale_kinds/minor_natural_scale_kind.rb +123 -0
- data/lib/musa-dsl/music/scale_kinds/modes/dorian_scale_kind.rb +111 -0
- data/lib/musa-dsl/music/scale_kinds/modes/locrian_scale_kind.rb +114 -0
- data/lib/musa-dsl/music/scale_kinds/modes/lydian_scale_kind.rb +111 -0
- data/lib/musa-dsl/music/scale_kinds/modes/mixolydian_scale_kind.rb +111 -0
- data/lib/musa-dsl/music/scale_kinds/modes/phrygian_scale_kind.rb +111 -0
- data/lib/musa-dsl/music/scale_kinds/pentatonic/pentatonic_major_scale_kind.rb +93 -0
- data/lib/musa-dsl/music/scale_kinds/pentatonic/pentatonic_minor_scale_kind.rb +99 -0
- data/lib/musa-dsl/music/scale_kinds/symmetric/diminished_hw_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/symmetric/diminished_wh_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/symmetric/whole_tone_scale_kind.rb +99 -0
- data/lib/musa-dsl/music/scale_systems/equally_tempered_12_tone_scale_system.rb +80 -0
- data/lib/musa-dsl/music/scale_systems/twelve_semitones_scale_system.rb +60 -0
- data/lib/musa-dsl/music/scales.rb +606 -67
- data/lib/musa-dsl/musicxml/builder/note.rb +31 -92
- data/lib/musa-dsl/musicxml/builder/pitched-note.rb +33 -94
- data/lib/musa-dsl/musicxml/builder/rest.rb +30 -91
- data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +31 -91
- data/lib/musa-dsl/neumas/array-to-neumas.rb +1 -1
- data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +2 -2
- data/lib/musa-dsl/sequencer/sequencer-dsl.rb +367 -3
- data/lib/musa-dsl/series/base-series.rb +250 -240
- data/lib/musa-dsl/series/buffer-serie.rb +16 -5
- data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +29 -3
- data/lib/musa-dsl/series/main-serie-constructors.rb +19 -15
- data/lib/musa-dsl/series/main-serie-operations.rb +74 -29
- data/lib/musa-dsl/series/proxy-serie.rb +5 -1
- data/lib/musa-dsl/series/quantizer-serie.rb +16 -2
- data/lib/musa-dsl/series/queue-serie.rb +15 -1
- data/lib/musa-dsl/series/series-composer.rb +5 -2
- data/lib/musa-dsl/series/timed-serie.rb +8 -4
- data/lib/musa-dsl/transport/timer-clock.rb +4 -2
- data/lib/musa-dsl/transport/timer.rb +27 -4
- data/lib/musa-dsl/version.rb +1 -1
- data/musa-dsl.gemspec +18 -15
- metadata +85 -22
data/docs/subsystems/music.md
CHANGED
|
@@ -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
|
-
|
|
24
|
-
|
|
25
|
-
- `:
|
|
26
|
-
- `:
|
|
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
|
|
|
@@ -79,8 +123,8 @@ c_major[:V] # => Dominant (G)
|
|
|
79
123
|
pitch = c_major.tonic.pitch # => 60
|
|
80
124
|
|
|
81
125
|
# Navigate with octaves
|
|
82
|
-
note = c_major[2].
|
|
83
|
-
pitch_with_octave = note.pitch
|
|
126
|
+
note = c_major[2].at_octave(1) # E transposed up 1 octave
|
|
127
|
+
pitch_with_octave = note.pitch # => 76
|
|
84
128
|
|
|
85
129
|
# Chromatic operations - sharp and flat
|
|
86
130
|
c_sharp = c_major.tonic.sharp # => C# (chromatic, +1 semitone)
|
|
@@ -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]
|
|
@@ -124,15 +211,258 @@ seventh_chord = i_chord.with_size(:seventh) # C major 7th
|
|
|
124
211
|
ninth_chord = i_chord.with_size(:ninth) # C major 9th
|
|
125
212
|
|
|
126
213
|
# Voicing modifications - move specific tones to different octaves
|
|
127
|
-
voiced = i_chord.
|
|
214
|
+
voiced = i_chord.with_move(root: -1, fifth: 1) # Root down, fifth up
|
|
215
|
+
i_chord.move # => { root: -1, fifth: 1 } (current settings)
|
|
128
216
|
|
|
129
217
|
# Duplicate tones in other octaves
|
|
130
|
-
doubled = i_chord.
|
|
218
|
+
doubled = i_chord.with_duplicate(root: -2, third: [-1, 1]) # Root 2 down, third 1 down and 1 up
|
|
219
|
+
i_chord.duplicate # => { root: -2, third: [-1, 1] } (current settings)
|
|
131
220
|
|
|
132
221
|
# Transpose entire chord
|
|
133
222
|
lower = i_chord.octave(-1) # Move chord down one octave
|
|
134
223
|
```
|
|
135
224
|
|
|
225
|
+
### Chord-Scale Navigation
|
|
226
|
+
|
|
227
|
+
MusaDSL provides methods to explore the relationship between chords and scales,
|
|
228
|
+
enabling harmonic analysis and discovery of functional contexts.
|
|
229
|
+
|
|
230
|
+
#### Checking if a Scale Contains a Chord
|
|
231
|
+
|
|
232
|
+
```ruby
|
|
233
|
+
c_major = Scales.et12[440.0].major[60]
|
|
234
|
+
g7 = c_major.dominant.chord :seventh
|
|
235
|
+
|
|
236
|
+
c_major.contains_chord?(g7) # => true
|
|
237
|
+
c_major.degree_of_chord(g7) # => 4 (V degree, 0-based)
|
|
238
|
+
|
|
239
|
+
# Non-diatonic chords return false/nil
|
|
240
|
+
cm = c_major.tonic.chord.with_quality(:minor) # C minor (Eb not in C major)
|
|
241
|
+
c_major.contains_chord?(cm) # => false
|
|
242
|
+
c_major.degree_of_chord(cm) # => nil
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### Creating a Chord in a Different Scale Context
|
|
246
|
+
|
|
247
|
+
```ruby
|
|
248
|
+
# Get the same chord but with a different scale as context
|
|
249
|
+
g_mixolydian = Scales.et12[440.0].mixolydian[67]
|
|
250
|
+
g7_in_mixolydian = g7.as_chord_in_scale(g_mixolydian)
|
|
251
|
+
g7_in_mixolydian.scale # => G Mixolydian scale
|
|
252
|
+
g_mixolydian.degree_of_chord(g7_in_mixolydian) # => 0 (I degree)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
#### Creating Chords from Scale Degrees
|
|
256
|
+
|
|
257
|
+
```ruby
|
|
258
|
+
# Create chords directly from scale degrees using Scale#chord_on
|
|
259
|
+
i_chord = scale.chord_on(0) # Tonic triad
|
|
260
|
+
v7 = scale.chord_on(:dominant, :seventh) # V7
|
|
261
|
+
iv_maj7 = scale.chord_on(:IV, :seventh, :major) # IVmaj7
|
|
262
|
+
|
|
263
|
+
# Equivalent to using the note method then chord:
|
|
264
|
+
scale[0].chord # Same as scale.chord_on(0)
|
|
265
|
+
scale[:dominant].chord(:seventh) # Same as scale.chord_on(:dominant, :seventh)
|
|
266
|
+
|
|
267
|
+
# With voicing parameters
|
|
268
|
+
scale.chord_on(:I, :seventh, move: {root: -1})
|
|
269
|
+
scale.chord_on(0, :triad, duplicate: {root: 1})
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
#### Finding Scales That Contain a Chord
|
|
273
|
+
|
|
274
|
+
```ruby
|
|
275
|
+
g_triad = c_major.dominant.chord # G-B-D
|
|
276
|
+
|
|
277
|
+
# Search in diatonic scales
|
|
278
|
+
g_triad.search_in_scales(family: :diatonic)
|
|
279
|
+
# => [Chord in C major (V), Chord in G major (I), Chord in D major (IV), ...]
|
|
280
|
+
|
|
281
|
+
# Search using metadata filters
|
|
282
|
+
g_triad.search_in_scales(family: :greek_modes, brightness: -1..1)
|
|
283
|
+
|
|
284
|
+
# Search in all scale types
|
|
285
|
+
g_triad.search_in_scales
|
|
286
|
+
|
|
287
|
+
# Each result has its scale context
|
|
288
|
+
g_triad.search_in_scales(family: :diatonic).each do |chord|
|
|
289
|
+
scale = chord.scale
|
|
290
|
+
degree = scale.degree_of_chord(chord)
|
|
291
|
+
puts "#{scale.kind.class.id} rooted on #{scale.root_pitch}: degree #{degree}"
|
|
292
|
+
end
|
|
293
|
+
# Output:
|
|
294
|
+
# major rooted on 60: degree 4
|
|
295
|
+
# major rooted on 67: degree 0
|
|
296
|
+
# major rooted on 62: degree 3
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
#### Low-Level Navigation Methods
|
|
300
|
+
|
|
301
|
+
```ruby
|
|
302
|
+
tuning = Scales.et12[440.0]
|
|
303
|
+
|
|
304
|
+
# Search at ScaleKind level
|
|
305
|
+
tuning.major.find_chord_in_scales(g_triad)
|
|
306
|
+
|
|
307
|
+
# Search at ScaleSystemTuning level with metadata filters
|
|
308
|
+
tuning.search_chord_in_scales(g7, family: :diatonic, roots: 60..71)
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Scale Kind Metadata
|
|
312
|
+
|
|
313
|
+
Scale kinds in MusaDSL have a three-layer metadata system that provides
|
|
314
|
+
both automatic structural information and extensible custom properties.
|
|
315
|
+
|
|
316
|
+
#### Metadata Layers
|
|
317
|
+
|
|
318
|
+
1. **Intrinsic metadata**: Automatically derived from scale structure
|
|
319
|
+
- `id`: Scale kind identifier
|
|
320
|
+
- `grades`: Number of scale degrees
|
|
321
|
+
- `pitches`: Array of pitch offsets from root
|
|
322
|
+
- `intervals`: Intervals between consecutive degrees
|
|
323
|
+
- `has_leading_tone`: Whether scale has pitch 11 (semitone below octave)
|
|
324
|
+
- `has_tritone`: Whether scale contains tritone interval (pitch 6)
|
|
325
|
+
- `symmetric`: Type of symmetry if any (`:equal`, `:palindrome`, `:repeating`)
|
|
326
|
+
|
|
327
|
+
2. **Base metadata**: Defined by musa-dsl library
|
|
328
|
+
- `family`: Scale family (`:diatonic`, `:greek_modes`, `:pentatonic`, etc.)
|
|
329
|
+
- `brightness`: Relative brightness (-3 to +3, major = 0)
|
|
330
|
+
- `character`: Array of descriptive tags
|
|
331
|
+
- `parent`: Parent scale and degree for modes (e.g., `{ scale: :major, degree: 2 }`)
|
|
332
|
+
|
|
333
|
+
3. **Custom metadata**: Added by users at runtime
|
|
334
|
+
|
|
335
|
+
#### Accessing Metadata
|
|
336
|
+
|
|
337
|
+
```ruby
|
|
338
|
+
tuning = Scales.et12[440.0]
|
|
339
|
+
major_class = tuning.major.class
|
|
340
|
+
|
|
341
|
+
# Get combined metadata (intrinsic < base < custom)
|
|
342
|
+
major_class.metadata
|
|
343
|
+
# => { id: :major, grades: 7, pitches: [0,2,4,5,7,9,11], family: :diatonic, ... }
|
|
344
|
+
|
|
345
|
+
# Get specific layer
|
|
346
|
+
major_class.intrinsic_metadata # Structure-derived only
|
|
347
|
+
major_class.base_metadata # Library-defined only
|
|
348
|
+
major_class.custom_metadata # User-added only
|
|
349
|
+
|
|
350
|
+
# Query helpers
|
|
351
|
+
major_class.has_metadata?(:family) # => true
|
|
352
|
+
major_class.has_metadata?(:family, :diatonic) # => true
|
|
353
|
+
major_class.has_metadata?(:character, :bright) # => true (array inclusion)
|
|
354
|
+
major_class.metadata_value(:brightness) # => 0
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
#### Extending Metadata
|
|
358
|
+
|
|
359
|
+
Users can add custom metadata to any scale kind:
|
|
360
|
+
|
|
361
|
+
```ruby
|
|
362
|
+
# Extend a specific scale kind class
|
|
363
|
+
tuning.dorian.class.extend_metadata(
|
|
364
|
+
my_mood: :nostalgic,
|
|
365
|
+
suitable_for: [:jazz, :fusion],
|
|
366
|
+
personal_rating: 5
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
# Or use the convenience method with scale ID
|
|
370
|
+
Scales.extend_metadata(:dorian, my_mood: :nostalgic)
|
|
371
|
+
|
|
372
|
+
# Multiple calls merge metadata
|
|
373
|
+
Scales.extend_metadata(:phrygian, mood: :dark)
|
|
374
|
+
Scales.extend_metadata(:phrygian, origin: :spanish) # Merges with previous
|
|
375
|
+
|
|
376
|
+
# Reset custom metadata if needed
|
|
377
|
+
tuning.dorian.class.reset_custom_metadata
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
#### Custom Metadata Overrides
|
|
381
|
+
|
|
382
|
+
Custom metadata takes precedence over base metadata:
|
|
383
|
+
|
|
384
|
+
```ruby
|
|
385
|
+
# Override library-defined family
|
|
386
|
+
Scales.extend_metadata(:dorian, family: :my_custom_category)
|
|
387
|
+
tuning.dorian.class.metadata[:family] # => :my_custom_category
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
#### Brightness Scale Reference
|
|
391
|
+
|
|
392
|
+
| Value | Meaning | Examples |
|
|
393
|
+
|-------|---------|----------|
|
|
394
|
+
| +3 | Very bright | Lydian augmented |
|
|
395
|
+
| +2 | Bright | Lydian |
|
|
396
|
+
| +1 | Slightly bright | Mixolydian, Lydian dominant |
|
|
397
|
+
| 0 | Neutral (reference) | Major (Ionian) |
|
|
398
|
+
| -1 | Slightly dark | Dorian |
|
|
399
|
+
| -2 | Dark | Minor harmonic, Phrygian |
|
|
400
|
+
| -3 | Very dark | Locrian, Natural minor |
|
|
401
|
+
|
|
402
|
+
#### Scale Families
|
|
403
|
+
|
|
404
|
+
- `:diatonic` - Major, minor natural, minor harmonic
|
|
405
|
+
- `:greek_modes` - Dorian, Phrygian, Lydian, Mixolydian, Locrian
|
|
406
|
+
- `:melodic_minor_modes` - Melodic minor and its modes
|
|
407
|
+
- `:pentatonic` - Pentatonic major/minor
|
|
408
|
+
- `:blues` - Blues scales
|
|
409
|
+
- `:bebop` - Bebop scales
|
|
410
|
+
- `:symmetric` - Whole tone, diminished
|
|
411
|
+
- `:ethnic` - Hungarian, Spanish, Neapolitan, etc.
|
|
412
|
+
- `:chromatic` - Chromatic scale
|
|
413
|
+
|
|
414
|
+
### Searching Scale Kinds
|
|
415
|
+
|
|
416
|
+
The `scale_kinds` method on ScaleSystemTuning allows searching and filtering
|
|
417
|
+
scale kinds by their metadata properties.
|
|
418
|
+
|
|
419
|
+
#### Basic Usage
|
|
420
|
+
|
|
421
|
+
```ruby
|
|
422
|
+
tuning = Scales.et12[440.0]
|
|
423
|
+
|
|
424
|
+
# Get all scale kinds
|
|
425
|
+
tuning.scale_kinds
|
|
426
|
+
# => [major_kind, minor_kind, dorian_kind, ...]
|
|
427
|
+
|
|
428
|
+
# Filter by family
|
|
429
|
+
tuning.scale_kinds(family: :diatonic)
|
|
430
|
+
# => [major_kind, minor_kind, minor_harmonic_kind, harmonic_major_kind]
|
|
431
|
+
|
|
432
|
+
# Filter by brightness range
|
|
433
|
+
tuning.scale_kinds(brightness: -1..1)
|
|
434
|
+
|
|
435
|
+
# Filter by character
|
|
436
|
+
tuning.scale_kinds(character: :jazz)
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
#### Custom Filtering with Blocks
|
|
440
|
+
|
|
441
|
+
Use a block for complex filtering based on any metadata:
|
|
442
|
+
|
|
443
|
+
```ruby
|
|
444
|
+
# Scales with leading tone
|
|
445
|
+
tuning.scale_kinds { |klass| klass.intrinsic_metadata[:has_leading_tone] }
|
|
446
|
+
|
|
447
|
+
# Combine criteria and block
|
|
448
|
+
tuning.scale_kinds(family: :greek_modes) { |klass| klass.metadata[:brightness]&.negative? }
|
|
449
|
+
# => [dorian_kind, phrygian_kind, locrian_kind]
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
#### Integration with Chord Search
|
|
453
|
+
|
|
454
|
+
The `search_chord_in_scales` method uses the same metadata filtering:
|
|
455
|
+
|
|
456
|
+
```ruby
|
|
457
|
+
g7 = tuning.major[60].dominant.chord(:seventh)
|
|
458
|
+
|
|
459
|
+
# Find G7 in diatonic scales
|
|
460
|
+
tuning.search_chord_in_scales(g7, family: :diatonic)
|
|
461
|
+
|
|
462
|
+
# Find G7 in scales with specific brightness
|
|
463
|
+
tuning.search_chord_in_scales(g7, brightness: -1..1)
|
|
464
|
+
```
|
|
465
|
+
|
|
136
466
|
## Defining Custom Scale Systems, Scale Kinds, and Chord Definitions
|
|
137
467
|
|
|
138
468
|
The framework is extensible, allowing users to define custom tuning systems, scale types, and chord structures:
|
|
@@ -173,19 +503,19 @@ require 'musa-dsl'
|
|
|
173
503
|
include Musa::Scales
|
|
174
504
|
include Musa::Chords
|
|
175
505
|
|
|
176
|
-
# Example 1: Define a custom
|
|
177
|
-
class
|
|
506
|
+
# Example 1: Define a custom Hirajoshi scale (Japanese pentatonic)
|
|
507
|
+
class HirajoshiScaleKind < ScaleKind
|
|
178
508
|
class << self
|
|
179
509
|
def id
|
|
180
|
-
:
|
|
510
|
+
:hirajoshi
|
|
181
511
|
end
|
|
182
512
|
|
|
183
513
|
def pitches
|
|
184
514
|
[{ functions: [:I, :_1, :tonic], pitch: 0 },
|
|
185
515
|
{ functions: [:II, :_2], pitch: 2 },
|
|
186
|
-
{ functions: [:III, :_3], pitch:
|
|
187
|
-
{ functions: [:V, :
|
|
188
|
-
{ functions: [:VI, :
|
|
516
|
+
{ functions: [:III, :_3], pitch: 3 },
|
|
517
|
+
{ functions: [:V, :_4], pitch: 7 },
|
|
518
|
+
{ functions: [:VI, :_5], pitch: 8 }]
|
|
189
519
|
end
|
|
190
520
|
|
|
191
521
|
def grades
|
|
@@ -195,14 +525,14 @@ class PentatonicMajorScaleKind < ScaleKind
|
|
|
195
525
|
end
|
|
196
526
|
|
|
197
527
|
# Register the new scale kind with the 12-tone system
|
|
198
|
-
Scales.et12.register(
|
|
528
|
+
Scales.et12.register(HirajoshiScaleKind)
|
|
199
529
|
|
|
200
530
|
# Use the new scale kind
|
|
201
531
|
tuning = Scales.default_system.default_tuning
|
|
202
|
-
|
|
203
|
-
puts
|
|
204
|
-
puts
|
|
205
|
-
puts
|
|
532
|
+
c_hirajoshi = tuning[:hirajoshi][60] # C Hirajoshi
|
|
533
|
+
puts c_hirajoshi[0].pitch # => 60 (C)
|
|
534
|
+
puts c_hirajoshi[1].pitch # => 62 (D)
|
|
535
|
+
puts c_hirajoshi[2].pitch # => 63 (Eb)
|
|
206
536
|
|
|
207
537
|
# Example 2: Register a custom chord definition (sus4)
|
|
208
538
|
Musa::Chords::ChordDefinition.register :sus4,
|
|
@@ -87,6 +87,32 @@ dummy_clock = Musa::Clock::DummyClock.new(100)
|
|
|
87
87
|
4. **on_change_position** - Run when position jumps/seeks
|
|
88
88
|
5. **after_stop** - Run when transport stops
|
|
89
89
|
|
|
90
|
+
### Clean Shutdown
|
|
91
|
+
|
|
92
|
+
To cleanly terminate a transport using TimerClock:
|
|
93
|
+
|
|
94
|
+
1. Call `transport.stop` from within a scheduled event
|
|
95
|
+
2. This calls `clock.terminate` internally
|
|
96
|
+
3. The clock's run loop exits
|
|
97
|
+
4. `transport.start` returns
|
|
98
|
+
5. Your code continues after `transport.start` for cleanup
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
# Example: Self-terminating composition
|
|
102
|
+
transport.sequencer.at 10 do
|
|
103
|
+
puts "Composition finished"
|
|
104
|
+
transport.stop # This will cause transport.start to return
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
transport.start # Blocks until transport.stop is called
|
|
108
|
+
puts "Cleanup..." # Executes after stop
|
|
109
|
+
output.close
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Note:** For `InputMidiClock`, stop/start cycles are controlled by the DAW
|
|
113
|
+
and don't terminate the process. The `terminate` method can be used explicitly
|
|
114
|
+
if you need the run loop to exit.
|
|
115
|
+
|
|
90
116
|
**Key methods:**
|
|
91
117
|
- `start` - Start playback (blocks while running)
|
|
92
118
|
- `stop` - Stop playback
|
|
@@ -204,7 +204,7 @@ module Musa::Datasets
|
|
|
204
204
|
pdv[:pitch] = if self[:silence]
|
|
205
205
|
:silence
|
|
206
206
|
else
|
|
207
|
-
scale[self[:grade]].sharp(self[:sharps] || 0).
|
|
207
|
+
scale[self[:grade]].sharp(self[:sharps] || 0).at_octave(self[:octave] || 0).pitch
|
|
208
208
|
end
|
|
209
209
|
end
|
|
210
210
|
|
|
@@ -399,8 +399,8 @@ module Musa::Datasets
|
|
|
399
399
|
(self[:sharps] || 0) != (previous[:sharps] || 0)
|
|
400
400
|
|
|
401
401
|
gdvd[:delta_grade] =
|
|
402
|
-
scale[self[:grade]].
|
|
403
|
-
scale[previous[:grade]].
|
|
402
|
+
scale[self[:grade]].at_octave(self[:octave]).wide_grade -
|
|
403
|
+
scale[previous[:grade]].at_octave(previous[:octave]).wide_grade
|
|
404
404
|
|
|
405
405
|
gdvd[:delta_sharps] = (self[:sharps] || 0) - (previous[:sharps] || 0)
|
|
406
406
|
end
|
data/lib/musa-dsl/datasets/p.rb
CHANGED
|
@@ -42,7 +42,8 @@ module Musa::Datasets::Score::ToMXML
|
|
|
42
42
|
# @note This method is experimental and currently unused. See TODO comment.
|
|
43
43
|
#
|
|
44
44
|
# @api private
|
|
45
|
-
def initialize(element, bar, bar_size =
|
|
45
|
+
def initialize(element, bar, bar_size = Rational(1))
|
|
46
|
+
# TODO remove (unused because of bad strategy to time groups)
|
|
46
47
|
@continue_from_previous_bar = element[:start] < bar
|
|
47
48
|
@continue_to_next_bar = element[:finish] >= bar + bar_size
|
|
48
49
|
|
|
@@ -99,7 +100,8 @@ module Musa::Datasets::Score::ToMXML
|
|
|
99
100
|
#
|
|
100
101
|
# @api private
|
|
101
102
|
# @todo Complete or remove this experimental method
|
|
102
|
-
def time_and_tuplet_optimize(elements, bar, bar_size =
|
|
103
|
+
def time_and_tuplet_optimize(elements, bar, bar_size = Rational(1))
|
|
104
|
+
# TODO remove (unused because of bad strategy to time groups)
|
|
103
105
|
decompositions = elements.collect { |pdv| ElementDurationDecomposition.new(pdv, bar, bar_size) }
|
|
104
106
|
|
|
105
107
|
denominators = decompositions.collect { |g| g.duration_decomposition.collect { |d| d.to_r.denominator } }.flatten.uniq
|
|
@@ -143,12 +143,14 @@ module Musa::Datasets
|
|
|
143
143
|
# @return [Object, nil] attribute value
|
|
144
144
|
#
|
|
145
145
|
# @api private
|
|
146
|
-
def
|
|
146
|
+
def get(key)
|
|
147
147
|
if NaturalKeys.include?(key) && self.respond_to?(key)
|
|
148
148
|
self.send(key)
|
|
149
149
|
end
|
|
150
150
|
end
|
|
151
151
|
|
|
152
|
+
alias_method :[], :get
|
|
153
|
+
|
|
152
154
|
# Returns latest finish time of all events.
|
|
153
155
|
#
|
|
154
156
|
# @return [Rational, nil] latest finish time, or nil if score is empty
|
|
@@ -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
|