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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/Gemfile +0 -1
- data/docs/subsystems/music.md +326 -15
- data/lib/musa-dsl/generative/darwin.rb +36 -1
- data/lib/musa-dsl/generative/generative-grammar.rb +28 -0
- data/lib/musa-dsl/generative/markov.rb +2 -0
- 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 +10 -0
- data/lib/musa-dsl/music/chords.rb +54 -9
- 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 +427 -0
- data/lib/musa-dsl/series/buffer-serie.rb +6 -0
- data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +23 -0
- data/lib/musa-dsl/series/quantizer-serie.rb +12 -0
- data/lib/musa-dsl/series/queue-serie.rb +13 -0
- data/lib/musa-dsl/version.rb +2 -1
- data/musa-dsl.gemspec +20 -15
- metadata +85 -22
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2a2ee1f86c7d0b4a6c6ecb95b92ea4b15ead3a305c9c6252ea74eb1d437b16fb
|
|
4
|
+
data.tar.gz: 07fea02dd0fe457b16c79e18eb82fedb0236c929fa3d1b1e3b32b621d419e6f7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0f186d4ac7ee38fb1984a7737e220c25a12411b104728181854898580a7059f87efc6828a769bee268695cb80d3d2818b6dadc0e21a4a1a3a57c853c7502817c
|
|
7
|
+
data.tar.gz: 84bf07953180497c334da1d471067a4561957da58a9aac4fe454bbfac364f4c44295e1e12e7ad935ca04f3e304e248fe9d89f2d866aaed9c62d87634d2fcbe15
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
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
|
|
|
@@ -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
|
|
177
|
-
class
|
|
487
|
+
# Example 1: Define a custom Hirajoshi scale (Japanese pentatonic)
|
|
488
|
+
class HirajoshiScaleKind < ScaleKind
|
|
178
489
|
class << self
|
|
179
490
|
def id
|
|
180
|
-
:
|
|
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:
|
|
187
|
-
{ functions: [:V, :
|
|
188
|
-
{ functions: [:VI, :
|
|
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(
|
|
509
|
+
Scales.et12.register(HirajoshiScaleKind)
|
|
199
510
|
|
|
200
511
|
# Use the new scale kind
|
|
201
512
|
tuning = Scales.default_system.default_tuning
|
|
202
|
-
|
|
203
|
-
puts
|
|
204
|
-
puts
|
|
205
|
-
puts
|
|
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
|