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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6f157001c0d45494eda872c34d4d73c161b686da78f0192868637873845ca62e
|
|
4
|
+
data.tar.gz: 2d93034d622985b67ae894d0cf7f4a5acdba787b4453fd6dbad08aa88d096866
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4dc00ab7d48971758d26bf982e8b61771fe81c26bd9e7cdedba97e457d3c97ff8286f7e53689712c97bb10674fe5f59261ee1f1186a1f20dbda87b62a16aa858
|
|
7
|
+
data.tar.gz: 9da464c4a8cfce824392e1c37b4987b84bde21c59924da5daf34b97b46c183d70d06afff212ab0de7d5b45971c7267aab5260730509b21522f12c49ab5474d46
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -58,6 +58,20 @@ Detailed tutorial showing the Neuma notation system for composing melodies with
|
|
|
58
58
|
|
|
59
59
|
**📖 [Complete Tutorial](docs/getting-started/tutorial.md)**
|
|
60
60
|
|
|
61
|
+
## Demo Projects
|
|
62
|
+
|
|
63
|
+
A collection of 22+ working demo projects covering the full spectrum of Musa DSL capabilities:
|
|
64
|
+
|
|
65
|
+
- **Basic concepts**: Setup, series, neumas, canon
|
|
66
|
+
- **Generative tools**: Markov chains, Variatio, Darwin, Grammar, Rules, Matrix
|
|
67
|
+
- **DAW integration**: MIDI sync, live coding, clock modes
|
|
68
|
+
- **External protocols**: OSC with SuperCollider and Max/MSP
|
|
69
|
+
- **Advanced patterns**: Event architecture, parameter automation, multi-phase compositions
|
|
70
|
+
|
|
71
|
+
Each demo is a complete, runnable project with documentation explaining the concepts demonstrated.
|
|
72
|
+
|
|
73
|
+
**📦 [musadsl-demo Repository](https://github.com/javier-sy/musadsl-demo)**
|
|
74
|
+
|
|
61
75
|
## System Architecture
|
|
62
76
|
|
|
63
77
|
MusaDSL is a comprehensive ecosystem consisting of a core framework (musa-dsl) and associated projects for communication, development, and integration.
|
|
@@ -230,4 +244,4 @@ Ruby refinements and metaprogramming utilities: Arrayfy, Hashify, ExplodeRanges,
|
|
|
230
244
|
|
|
231
245
|
## License
|
|
232
246
|
|
|
233
|
-
[Musa-DSL](https://github.com/javier-sy/musa-dsl) Copyright (c) 2016-
|
|
247
|
+
[Musa-DSL](https://github.com/javier-sy/musa-dsl) Copyright (c) 2016-2026 [Javier Sánchez Yeste](https://yeste.studio), licensed under LGPL 3.0 License
|
data/docs/README.md
CHANGED
|
@@ -52,6 +52,7 @@ For users extending the DSL or integrating deeply:
|
|
|
52
52
|
2. Explore [Music](subsystems/music.md) (scales & chords)
|
|
53
53
|
3. Try [Generative](subsystems/generative.md) algorithms
|
|
54
54
|
4. Use [MusicXML Builder](subsystems/musicxml-builder.md) for scores
|
|
55
|
+
5. Explore [Demo Projects](https://github.com/javier-sy/musadsl-demo) - 22+ working examples
|
|
55
56
|
|
|
56
57
|
### Live Coding?
|
|
57
58
|
1. Set up [REPL](subsystems/repl.md)
|
data/docs/subsystems/datasets.md
CHANGED
|
@@ -52,6 +52,51 @@ E (base event)
|
|
|
52
52
|
- Glissandi and parameter sweeps (PS)
|
|
53
53
|
- Dynamics changes and other evolving parameters
|
|
54
54
|
|
|
55
|
+
## Duration Types in AbsD
|
|
56
|
+
|
|
57
|
+
AbsD defines three duration concepts that enable complex rhythmic structures:
|
|
58
|
+
|
|
59
|
+
| Field | Purpose | Default |
|
|
60
|
+
|-------|---------|---------|
|
|
61
|
+
| `:duration` | Total event process time | Required |
|
|
62
|
+
| `:note_duration` | Actual sound length (staccato/legato) | = duration |
|
|
63
|
+
| `:forward_duration` | Time until next event starts | = duration |
|
|
64
|
+
|
|
65
|
+
**Examples:**
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
include Musa::Datasets
|
|
69
|
+
|
|
70
|
+
# Normal note - all durations equal
|
|
71
|
+
{ pitch: 60, duration: 1.0 }.extend(AbsD)
|
|
72
|
+
|
|
73
|
+
# Staccato - sounds shorter than it "lasts"
|
|
74
|
+
{ pitch: 60, duration: 1.0, note_duration: 0.5 }.extend(AbsD)
|
|
75
|
+
|
|
76
|
+
# Chord (simultaneous notes) - next event starts immediately
|
|
77
|
+
{ pitch: 60, duration: 1.0, forward_duration: 0 }.extend(AbsD)
|
|
78
|
+
|
|
79
|
+
# Overlap (legato) - next note starts before this one ends
|
|
80
|
+
{ pitch: 60, duration: 1.0, forward_duration: 0.8 }.extend(AbsD)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Understanding DeltaD:**
|
|
84
|
+
|
|
85
|
+
DeltaD is the delta-encoding counterpart to AbsD. While AbsD stores absolute durations, DeltaD stores changes relative to a previous event. If duration hasn't changed from the previous event, it can be omitted for compression (as shown in GDV → GDVd conversions).
|
|
86
|
+
|
|
87
|
+
## Natural Keys
|
|
88
|
+
|
|
89
|
+
Each dataset type defines "natural keys" - semantically meaningful fields for that type:
|
|
90
|
+
|
|
91
|
+
| Module | Natural Keys |
|
|
92
|
+
|--------|--------------|
|
|
93
|
+
| **E** | `[]` (none) |
|
|
94
|
+
| **AbsD** | `[:duration, :note_duration, :forward_duration]` |
|
|
95
|
+
| **PDV** | AbsD keys + `[:pitch, :velocity]` |
|
|
96
|
+
| **GDV** | AbsD keys + `[:grade, :sharps, :octave, :velocity, :silence]` |
|
|
97
|
+
|
|
98
|
+
Custom keys (any not in NaturalKeys) are preserved through conversions, enabling you to attach composition-specific metadata that travels with your musical data.
|
|
99
|
+
|
|
55
100
|
## Extensibility
|
|
56
101
|
|
|
57
102
|
All datasets support custom parameters beyond their natural keys:
|
|
@@ -102,6 +147,36 @@ gdv.is_a?(Abs) # => true (GDV includes Abs)
|
|
|
102
147
|
gdv.is_a?(AbsD) # => true (GDV includes AbsD for duration)
|
|
103
148
|
```
|
|
104
149
|
|
|
150
|
+
## Using AbsD for Containers
|
|
151
|
+
|
|
152
|
+
AbsD is not just for notes - use it for any time-spanning structure that needs a duration. This is useful for chord progression steps, sections, or any compositional element that occupies time but isn't itself a single note.
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
include Musa::Datasets
|
|
156
|
+
|
|
157
|
+
scale = Musa::Scales::Scales.et12[440.0].major[60]
|
|
158
|
+
|
|
159
|
+
# A chord progression step (not a note, but has duration)
|
|
160
|
+
step = {
|
|
161
|
+
duration: 2, # How long this step lasts
|
|
162
|
+
symbol: :I, # Chord symbol for reference
|
|
163
|
+
bass: { grade: 0, octave: -1, duration: 2, velocity: 0 }.extend(GDV),
|
|
164
|
+
chord: scale[:I].chord, # Chord object
|
|
165
|
+
chord_duration: 7/4r,
|
|
166
|
+
chord_velocity: -1
|
|
167
|
+
}.extend(AbsD)
|
|
168
|
+
|
|
169
|
+
# Now the sequencer can use step.duration for timing
|
|
170
|
+
# while the step contains all the musical information needed to render it
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**When to use AbsD directly:**
|
|
174
|
+
|
|
175
|
+
- Containers that hold multiple notes (chords, arpeggios)
|
|
176
|
+
- Section markers with timing information
|
|
177
|
+
- Harmonic rhythm steps in a progression
|
|
178
|
+
- Any structure where you need `:duration` but PDV/GDV semantics don't apply
|
|
179
|
+
|
|
105
180
|
## Dataset Conversions
|
|
106
181
|
|
|
107
182
|
Datasets provide rich conversion capabilities for transforming between different representations, each optimized for specific compositional tasks:
|
|
@@ -80,18 +80,30 @@ limited_chords = variatio.on(root: [60, 64])
|
|
|
80
80
|
|
|
81
81
|
Production system with growth and pruning rules (similar to L-systems). Generates tree structures by applying sequential growth rules to create branches and validation rules to prune invalid paths. Useful for harmonic progressions with voice leading rules, melodic variations with contour constraints, or rhythmic patterns following metric rules.
|
|
82
82
|
|
|
83
|
+
### Execution Model
|
|
84
|
+
|
|
85
|
+
**Critical concept: Each `grow` rule = 1 level of depth in the tree.**
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
Seed → GrowRule1 → GrowRule2 → GrowRule3 → ... → Result
|
|
89
|
+
↓ ↓ ↓
|
|
90
|
+
branches branches branches
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Rules are processed **sequentially**: the first grow rule applies to the seed, the second grow rule applies to all results from the first, and so on. If you define N grow rules, you get N levels of transformation.
|
|
94
|
+
|
|
83
95
|
**Constructor parameters:**
|
|
84
96
|
- `&block` - DSL block defining grow rules, cut rules, and end condition
|
|
85
97
|
|
|
86
98
|
**DSL methods:**
|
|
87
99
|
- `grow(name, &block)` - Define growth rule that generates new branches
|
|
88
|
-
- Block receives: `|object, history, **params|`
|
|
100
|
+
- Block receives: `|object|` or `|object, history|` or `|object, history, **params|`
|
|
89
101
|
- Use `branch(new_object)` to create new possibilities
|
|
90
102
|
- `cut(reason, &block)` - Define pruning rule to eliminate invalid paths
|
|
91
|
-
- Block receives: `|object, history, **params|`
|
|
103
|
+
- Block receives: `|object|` or `|object, history|` or `|object, history, **params|`
|
|
92
104
|
- Use `prune` to reject current branch
|
|
93
105
|
- `ended_when(&block)` - Define end condition to mark complete branches
|
|
94
|
-
- Block receives: `|object, history, **params|`
|
|
106
|
+
- Block receives: `|object|` or `|object, history|` or `|object, history, **params|`
|
|
95
107
|
- Return `true` to mark branch as complete
|
|
96
108
|
|
|
97
109
|
**Execution methods:**
|
|
@@ -103,23 +115,83 @@ Production system with growth and pruning rules (similar to L-systems). Generate
|
|
|
103
115
|
- `combinations` - Returns array of all valid complete paths through tree
|
|
104
116
|
- `fish` - Returns array of all valid endpoint objects
|
|
105
117
|
|
|
118
|
+
### Important: The `history` Parameter
|
|
119
|
+
|
|
120
|
+
**Warning:** When using a single seed (the typical case), the `history` parameter is **always an empty array `[]`** in all grow rules. This is because `history` contains the path of *confirmed* objects from previous seeds when using multiple seeds.
|
|
121
|
+
|
|
122
|
+
**Do NOT rely on `history` for tracking sequence length with a single seed:**
|
|
123
|
+
```ruby
|
|
124
|
+
# THIS DOES NOT WORK as expected with a single seed:
|
|
125
|
+
ended_when do |pitch, history|
|
|
126
|
+
history.size == 7 # Always false! history is []
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Recommended Pattern: Cumulative State
|
|
131
|
+
|
|
132
|
+
The recommended approach is to make your **object accumulate state** rather than relying on `history`:
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
require 'musa-dsl'
|
|
136
|
+
|
|
137
|
+
# Generate 8-note melodies with interval constraints
|
|
138
|
+
rules = Musa::Rules::Rules.new do
|
|
139
|
+
# Each grow rule adds ONE note. For 8 notes, we need 7 grow rules (seed + 7).
|
|
140
|
+
7.times do
|
|
141
|
+
grow 'add next note' do |melody, max_interval:|
|
|
142
|
+
last_note = melody.last
|
|
143
|
+
(-max_interval..max_interval).each do |interval|
|
|
144
|
+
next if interval == 0 # Skip unisons
|
|
145
|
+
new_note = last_note + interval
|
|
146
|
+
branch melody + [new_note] if new_note.between?(48, 84) # Range limit
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Cut rule: no repeated notes
|
|
152
|
+
cut 'no repetition' do |melody|
|
|
153
|
+
prune if melody.size >= 2 && melody[-1] == melody[-2]
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# End condition checks the OBJECT size, not history
|
|
157
|
+
ended_when do |melody|
|
|
158
|
+
melody.size == 8
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Seed is [[60]] - double array to prevent automatic flattening
|
|
163
|
+
tree = rules.apply([[60]], max_interval: 4)
|
|
164
|
+
|
|
165
|
+
# Extract all valid 8-note melodies
|
|
166
|
+
melodies = tree.combinations.map(&:last)
|
|
167
|
+
puts "Generated #{melodies.size} valid melodies"
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Key points:**
|
|
171
|
+
- Use `N.times { grow }` to create N levels of depth
|
|
172
|
+
- The object (`melody`) accumulates state as an array
|
|
173
|
+
- Check `object.size` in `ended_when`, not `history.size`
|
|
174
|
+
- Seed with `[[value]]` (double array) to prevent Ruby's `arrayfy` from flattening
|
|
175
|
+
|
|
176
|
+
### Basic Example: Chord Voicings
|
|
177
|
+
|
|
106
178
|
```ruby
|
|
107
179
|
require 'musa-dsl'
|
|
108
180
|
|
|
109
181
|
# Build chord voicings by adding notes sequentially
|
|
110
182
|
rules = Musa::Rules::Rules.new do
|
|
111
|
-
# Step 1: Choose root note
|
|
183
|
+
# Step 1: Choose root note (1st grow rule = 1st level)
|
|
112
184
|
grow 'add root' do |seed|
|
|
113
185
|
[60, 64, 67].each { |root| branch [root] } # C, E, G
|
|
114
186
|
end
|
|
115
187
|
|
|
116
|
-
# Step 2: Add third (
|
|
188
|
+
# Step 2: Add third (2nd grow rule = 2nd level)
|
|
117
189
|
grow 'add third' do |chord|
|
|
118
190
|
branch chord + [chord[0] + 4] # Major third
|
|
119
191
|
branch chord + [chord[0] + 3] # Minor third
|
|
120
192
|
end
|
|
121
193
|
|
|
122
|
-
# Step 3: Add fifth
|
|
194
|
+
# Step 3: Add fifth (3rd grow rule = 3rd level)
|
|
123
195
|
grow 'add fifth' do |chord|
|
|
124
196
|
branch chord + [chord[0] + 7]
|
|
125
197
|
end
|
|
@@ -149,6 +221,20 @@ voicings = combinations.map { |path| path.last }
|
|
|
149
221
|
tree_with_params = rules.apply(0, max_interval: 7)
|
|
150
222
|
```
|
|
151
223
|
|
|
224
|
+
### Multiple Seeds (Advanced)
|
|
225
|
+
|
|
226
|
+
When passing an array of seeds `[s1, s2, s3]`, they are processed **sequentially and chained**: each seed starts from the valid endpoints of the previous seed's tree. In this case, `history` contains the confirmed path from previous seeds.
|
|
227
|
+
|
|
228
|
+
```ruby
|
|
229
|
+
# With multiple seeds, history IS populated:
|
|
230
|
+
rules.apply([seed1, seed2, seed3])
|
|
231
|
+
# seed1 processes → fish valid endpoints
|
|
232
|
+
# seed2 starts from each endpoint, history contains seed1's path
|
|
233
|
+
# seed3 starts from seed2's endpoints, history contains seed1+seed2's paths
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
This is useful for multi-phase generation where each phase has different rules, but for most use cases the single-seed cumulative state pattern is simpler and recommended.
|
|
237
|
+
|
|
152
238
|
## Generative Grammar
|
|
153
239
|
|
|
154
240
|
Formal grammars with combinatorial generation using operators. Useful for generating melodic patterns with rhythmic constraints, harmonic progressions, or variations of musical motifs.
|