musa-dsl 0.30.2 → 0.40.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 -1
- data/.version +6 -0
- data/.yardopts +7 -0
- data/README.md +227 -6
- data/docs/README.md +83 -0
- data/docs/api-reference.md +86 -0
- data/docs/getting-started/quick-start.md +93 -0
- data/docs/getting-started/tutorial.md +58 -0
- data/docs/subsystems/core-extensions.md +316 -0
- data/docs/subsystems/datasets.md +465 -0
- data/docs/subsystems/generative.md +290 -0
- data/docs/subsystems/matrix.md +63 -0
- data/docs/subsystems/midi.md +123 -0
- data/docs/subsystems/music.md +233 -0
- data/docs/subsystems/musicxml-builder.md +264 -0
- data/docs/subsystems/neumas.md +71 -0
- data/docs/subsystems/repl.md +135 -0
- data/docs/subsystems/sequencer.md +98 -0
- data/docs/subsystems/series.md +302 -0
- data/docs/subsystems/transcription.md +152 -0
- data/docs/subsystems/transport.md +177 -0
- data/lib/musa-dsl/core-ext/array-explode-ranges.rb +68 -0
- data/lib/musa-dsl/core-ext/arrayfy.rb +110 -0
- data/lib/musa-dsl/core-ext/attribute-builder.rb +91 -30
- data/lib/musa-dsl/core-ext/deep-copy.rb +125 -2
- data/lib/musa-dsl/core-ext/dynamic-proxy.rb +78 -0
- data/lib/musa-dsl/core-ext/extension.rb +53 -0
- data/lib/musa-dsl/core-ext/hashify.rb +162 -1
- data/lib/musa-dsl/core-ext/inspect-nice.rb +154 -0
- data/lib/musa-dsl/core-ext/smart-proc-binder.rb +117 -0
- data/lib/musa-dsl/core-ext/with.rb +114 -0
- data/lib/musa-dsl/datasets/dataset.rb +109 -0
- data/lib/musa-dsl/datasets/delta-d.rb +78 -0
- data/lib/musa-dsl/datasets/e.rb +186 -2
- data/lib/musa-dsl/datasets/gdv.rb +279 -2
- data/lib/musa-dsl/datasets/gdvd.rb +201 -0
- data/lib/musa-dsl/datasets/helper.rb +75 -0
- data/lib/musa-dsl/datasets/p.rb +177 -2
- data/lib/musa-dsl/datasets/packed-v.rb +91 -0
- data/lib/musa-dsl/datasets/pdv.rb +136 -1
- data/lib/musa-dsl/datasets/ps.rb +134 -4
- data/lib/musa-dsl/datasets/score/queriable.rb +143 -1
- data/lib/musa-dsl/datasets/score/render.rb +105 -1
- data/lib/musa-dsl/datasets/score/to-mxml/process-pdv.rb +138 -1
- data/lib/musa-dsl/datasets/score/to-mxml/process-ps.rb +111 -0
- data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +200 -1
- data/lib/musa-dsl/datasets/score/to-mxml/to-mxml.rb +145 -1
- data/lib/musa-dsl/datasets/score.rb +279 -0
- data/lib/musa-dsl/datasets/v.rb +88 -0
- data/lib/musa-dsl/generative/darwin.rb +180 -1
- data/lib/musa-dsl/generative/generative-grammar.rb +359 -0
- data/lib/musa-dsl/generative/markov.rb +133 -3
- data/lib/musa-dsl/generative/rules.rb +258 -4
- data/lib/musa-dsl/generative/variatio.rb +217 -2
- data/lib/musa-dsl/logger/logger.rb +267 -2
- data/lib/musa-dsl/matrix/matrix.rb +256 -10
- data/lib/musa-dsl/midi/midi-recorder.rb +108 -1
- data/lib/musa-dsl/midi/midi-voices.rb +265 -4
- data/lib/musa-dsl/music/chord-definition.rb +233 -1
- data/lib/musa-dsl/music/chord-definitions.rb +33 -6
- data/lib/musa-dsl/music/chords.rb +308 -2
- data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +315 -0
- data/lib/musa-dsl/music/scales.rb +957 -40
- data/lib/musa-dsl/musicxml/builder/attributes.rb +483 -3
- data/lib/musa-dsl/musicxml/builder/backup-forward.rb +166 -1
- data/lib/musa-dsl/musicxml/builder/direction.rb +243 -0
- data/lib/musa-dsl/musicxml/builder/helper.rb +240 -0
- data/lib/musa-dsl/musicxml/builder/measure.rb +284 -0
- data/lib/musa-dsl/musicxml/builder/note-complexities.rb +324 -8
- data/lib/musa-dsl/musicxml/builder/note.rb +285 -0
- data/lib/musa-dsl/musicxml/builder/part-group.rb +108 -1
- data/lib/musa-dsl/musicxml/builder/part.rb +139 -0
- data/lib/musa-dsl/musicxml/builder/pitched-note.rb +124 -0
- data/lib/musa-dsl/musicxml/builder/rest.rb +93 -0
- data/lib/musa-dsl/musicxml/builder/score-partwise.rb +276 -0
- data/lib/musa-dsl/musicxml/builder/typed-text.rb +62 -1
- data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +83 -0
- data/lib/musa-dsl/neumalang/neumalang.rb +675 -0
- data/lib/musa-dsl/neumas/array-to-neumas.rb +149 -0
- data/lib/musa-dsl/neumas/neuma-decoder.rb +253 -0
- data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +142 -2
- data/lib/musa-dsl/neumas/neuma-gdvd-decoder.rb +82 -0
- data/lib/musa-dsl/neumas/neumas.rb +67 -0
- data/lib/musa-dsl/neumas/string-to-neumas.rb +233 -1
- data/lib/musa-dsl/repl/repl.rb +550 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-every.rb +118 -2
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-move.rb +149 -2
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +296 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-timed.rb +88 -2
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb +161 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +263 -0
- data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +173 -1
- data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +177 -0
- data/lib/musa-dsl/sequencer/base-sequencer.rb +710 -10
- data/lib/musa-dsl/sequencer/sequencer-dsl.rb +210 -0
- data/lib/musa-dsl/sequencer/timeslots.rb +79 -0
- data/lib/musa-dsl/series/array-to-serie.rb +37 -1
- data/lib/musa-dsl/series/base-series.rb +843 -5
- data/lib/musa-dsl/series/buffer-serie.rb +48 -0
- data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +41 -0
- data/lib/musa-dsl/series/main-serie-constructors.rb +398 -2
- data/lib/musa-dsl/series/main-serie-operations.rb +538 -16
- data/lib/musa-dsl/series/proxy-serie.rb +67 -0
- data/lib/musa-dsl/series/quantizer-serie.rb +45 -7
- data/lib/musa-dsl/series/queue-serie.rb +65 -0
- data/lib/musa-dsl/series/series-composer.rb +701 -0
- data/lib/musa-dsl/series/timed-serie.rb +473 -28
- data/lib/musa-dsl/transcription/from-gdv-to-midi.rb +404 -1
- data/lib/musa-dsl/transcription/from-gdv-to-musicxml.rb +118 -0
- data/lib/musa-dsl/transcription/from-gdv.rb +84 -1
- data/lib/musa-dsl/transcription/transcription.rb +265 -0
- data/lib/musa-dsl/transport/clock.rb +125 -0
- data/lib/musa-dsl/transport/dummy-clock.rb +89 -2
- data/lib/musa-dsl/transport/external-tick-clock.rb +91 -0
- data/lib/musa-dsl/transport/input-midi-clock.rb +133 -1
- data/lib/musa-dsl/transport/timer-clock.rb +183 -1
- data/lib/musa-dsl/transport/timer.rb +83 -0
- data/lib/musa-dsl/transport/transport.rb +318 -0
- data/lib/musa-dsl/version.rb +1 -1
- data/lib/musa-dsl.rb +132 -25
- data/musa-dsl.gemspec +12 -10
- metadata +87 -8
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# Generative - Algorithmic Composition
|
|
2
|
+
|
|
3
|
+
Tools for generative and algorithmic music composition.
|
|
4
|
+
|
|
5
|
+
## Markov Chains
|
|
6
|
+
|
|
7
|
+
Probabilistic sequence generation using transition matrices. Markov chains generate sequences where each value depends only on the current state and transition probabilities.
|
|
8
|
+
|
|
9
|
+
Parameters:
|
|
10
|
+
- `start:` - Initial state value
|
|
11
|
+
- `finish:` - End state symbol (transitions to this value terminate the sequence)
|
|
12
|
+
- `transitions:` - Hash mapping each state to possible next states with probabilities
|
|
13
|
+
- Format: `state => { next_state => probability, ... }`
|
|
14
|
+
- Probabilities for each state should sum to 1.0
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
require 'musa-dsl'
|
|
18
|
+
|
|
19
|
+
markov = Musa::Markov::Markov.new(
|
|
20
|
+
start: 0,
|
|
21
|
+
finish: :end,
|
|
22
|
+
transitions: {
|
|
23
|
+
0 => { 2 => 0.5, 4 => 0.3, 7 => 0.2 },
|
|
24
|
+
2 => { 0 => 0.3, 4 => 0.5, 5 => 0.2 },
|
|
25
|
+
4 => { 2 => 0.4, 5 => 0.4, 7 => 0.2 },
|
|
26
|
+
5 => { 0 => 0.5, :end => 0.5 },
|
|
27
|
+
7 => { 0 => 0.6, :end => 0.4 }
|
|
28
|
+
}
|
|
29
|
+
).i
|
|
30
|
+
|
|
31
|
+
# Generate melody pitches (Markov is a Serie, so we can use .to_a)
|
|
32
|
+
melody_pitches = markov.to_a
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Variatio
|
|
36
|
+
|
|
37
|
+
Generates all combinations of parameter variations using Cartesian product. Useful for creating comprehensive parameter sweeps, exploring all possibilities of a musical motif, or generating exhaustive harmonic permutations.
|
|
38
|
+
|
|
39
|
+
**Constructor parameters:**
|
|
40
|
+
- `instance_name` (Symbol) - Name for the object parameter in blocks (e.g., `:chord`, `:note`, `:synth`)
|
|
41
|
+
- `&block` - DSL block defining fields, constructor, and optional attributes/finalize
|
|
42
|
+
|
|
43
|
+
**DSL methods:**
|
|
44
|
+
- `field(name, options)` - Define a parameter field with possible values (Array or Range)
|
|
45
|
+
- `fieldset(name, options, &block)` - Define nested field group with its own fields
|
|
46
|
+
- `constructor(&block)` - Define how to construct each variation object (required)
|
|
47
|
+
- `with_attributes(&block)` - Modify objects with field/fieldset values (optional)
|
|
48
|
+
- `finalize(&block)` - Post-process completed objects (optional)
|
|
49
|
+
|
|
50
|
+
**Execution methods:**
|
|
51
|
+
- `run` - Generate all variations with default field values
|
|
52
|
+
- `on(**values)` - Generate variations with runtime field value overrides
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
require 'musa-dsl'
|
|
56
|
+
|
|
57
|
+
variatio = Musa::Variatio::Variatio.new :chord do
|
|
58
|
+
field :root, [60, 64, 67] # C, E, G
|
|
59
|
+
field :type, [:major, :minor]
|
|
60
|
+
|
|
61
|
+
constructor do |root:, type:|
|
|
62
|
+
{ root: root, type: type }
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
all_chords = variatio.run
|
|
67
|
+
# => [
|
|
68
|
+
# { root: 60, type: :major }, { root: 60, type: :minor },
|
|
69
|
+
# { root: 64, type: :major }, { root: 64, type: :minor },
|
|
70
|
+
# { root: 67, type: :major }, { root: 67, type: :minor }
|
|
71
|
+
# ]
|
|
72
|
+
# 3 roots × 2 types = 6 variations
|
|
73
|
+
|
|
74
|
+
# Override field values at runtime
|
|
75
|
+
limited_chords = variatio.on(root: [60, 64])
|
|
76
|
+
# => 2 roots × 2 types = 4 variations
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Rules
|
|
80
|
+
|
|
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
|
+
|
|
83
|
+
**Constructor parameters:**
|
|
84
|
+
- `&block` - DSL block defining grow rules, cut rules, and end condition
|
|
85
|
+
|
|
86
|
+
**DSL methods:**
|
|
87
|
+
- `grow(name, &block)` - Define growth rule that generates new branches
|
|
88
|
+
- Block receives: `|object, history, **params|`
|
|
89
|
+
- Use `branch(new_object)` to create new possibilities
|
|
90
|
+
- `cut(reason, &block)` - Define pruning rule to eliminate invalid paths
|
|
91
|
+
- Block receives: `|object, history, **params|`
|
|
92
|
+
- Use `prune` to reject current branch
|
|
93
|
+
- `ended_when(&block)` - Define end condition to mark complete branches
|
|
94
|
+
- Block receives: `|object, history, **params|`
|
|
95
|
+
- Return `true` to mark branch as complete
|
|
96
|
+
|
|
97
|
+
**Execution methods:**
|
|
98
|
+
- `apply(seed_or_seeds, **params)` - Apply rules to initial object(s), returns tree Node
|
|
99
|
+
- Accepts single object or array of objects
|
|
100
|
+
- Optional parameters passed to all rule blocks
|
|
101
|
+
|
|
102
|
+
**Tree Node methods:**
|
|
103
|
+
- `combinations` - Returns array of all valid complete paths through tree
|
|
104
|
+
- `fish` - Returns array of all valid endpoint objects
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
require 'musa-dsl'
|
|
108
|
+
|
|
109
|
+
# Build chord voicings by adding notes sequentially
|
|
110
|
+
rules = Musa::Rules::Rules.new do
|
|
111
|
+
# Step 1: Choose root note
|
|
112
|
+
grow 'add root' do |seed|
|
|
113
|
+
[60, 64, 67].each { |root| branch [root] } # C, E, G
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Step 2: Add third (major or minor)
|
|
117
|
+
grow 'add third' do |chord|
|
|
118
|
+
branch chord + [chord[0] + 4] # Major third
|
|
119
|
+
branch chord + [chord[0] + 3] # Minor third
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Step 3: Add fifth
|
|
123
|
+
grow 'add fifth' do |chord|
|
|
124
|
+
branch chord + [chord[0] + 7]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Pruning rule: avoid wide intervals
|
|
128
|
+
cut 'no wide spacing' do |chord|
|
|
129
|
+
if chord.size >= 2
|
|
130
|
+
prune if (chord[-1] - chord[-2]) > 12 # Max octave between adjacent notes
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# End after three notes
|
|
135
|
+
ended_when do |chord|
|
|
136
|
+
chord.size == 3
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
tree = rules.apply(0) # seed value (triggers generation)
|
|
141
|
+
combinations = tree.combinations
|
|
142
|
+
|
|
143
|
+
# Extract final voicings from each path
|
|
144
|
+
voicings = combinations.map { |path| path.last }
|
|
145
|
+
# => [[60, 64, 67], [60, 63, 67], [64, 68, 71], [64, 67, 71], [67, 71, 74], [67, 70, 74]]
|
|
146
|
+
# 3 roots × 2 thirds × 1 fifth = 6 voicings
|
|
147
|
+
|
|
148
|
+
# With parameters
|
|
149
|
+
tree_with_params = rules.apply(0, max_interval: 7)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Generative Grammar
|
|
153
|
+
|
|
154
|
+
Formal grammars with combinatorial generation using operators. Useful for generating melodic patterns with rhythmic constraints, harmonic progressions, or variations of musical motifs.
|
|
155
|
+
|
|
156
|
+
**Constructors:**
|
|
157
|
+
- `N(content, **attributes)` - Create terminal node with fixed content and attributes
|
|
158
|
+
- `N(**attributes, &block)` - Create block node with dynamic content generation
|
|
159
|
+
- `PN()` - Create proxy node for recursive grammar definitions
|
|
160
|
+
|
|
161
|
+
**Combination operators:**
|
|
162
|
+
- `|` (or) - Alternative/choice between nodes (e.g., `a | b`)
|
|
163
|
+
- `+` (next) - Concatenation/sequence of nodes (e.g., `a + b`)
|
|
164
|
+
- `repeat(exactly:)` or `repeat(min:, max:)` - Repeat node multiple times
|
|
165
|
+
- `limit(&block)` - Filter options by condition
|
|
166
|
+
|
|
167
|
+
**Result methods:**
|
|
168
|
+
- `options(content: :join)` - Generate all combinations as joined strings
|
|
169
|
+
- `options(content: :itself)` - Generate all combinations as arrays (default)
|
|
170
|
+
- `options(raw: true)` - Generate raw OptionElement objects with attributes
|
|
171
|
+
- `options(&condition)` - Generate filtered combinations
|
|
172
|
+
|
|
173
|
+
```ruby
|
|
174
|
+
require 'musa-dsl'
|
|
175
|
+
|
|
176
|
+
include Musa::GenerativeGrammar
|
|
177
|
+
|
|
178
|
+
a = N('a', size: 1)
|
|
179
|
+
b = N('b', size: 1)
|
|
180
|
+
c = N('c', size: 1)
|
|
181
|
+
d = b | c # d can be either b or c
|
|
182
|
+
|
|
183
|
+
# Grammar: (a or d) repeated 3 times, then c
|
|
184
|
+
grammar = (a | d).repeat(3) + c
|
|
185
|
+
|
|
186
|
+
# Generate all possibilities
|
|
187
|
+
grammar.options(content: :join)
|
|
188
|
+
# => ["aaac", "aabc", "aacc", "abac", "abbc", "abcc", "acac", "acbc", "accc",
|
|
189
|
+
# "baac", "babc", "bacc", "bbac", "bbbc", "bbcc", "bcac", "bcbc", "bccc",
|
|
190
|
+
# "caac", "cabc", "cacc", "cbac", "cbbc", "cbcc", "ccac", "ccbc", "cccc"]
|
|
191
|
+
# 3^3 × 1 = 27 combinations
|
|
192
|
+
|
|
193
|
+
# With constraints - filter by attribute
|
|
194
|
+
grammar_with_limit = (a | d).repeat(min: 1, max: 4).limit { |o|
|
|
195
|
+
o.collect { |e| e.attributes[:size] }.sum <= 3
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
result_limited = grammar_with_limit.options(content: :join)
|
|
199
|
+
# Includes: ["a", "b", "c", "aa", "ab", "ac", "ba", "bb", "bc", "ca", "cb", "cc", "aaa", "aab", "aac", ...]
|
|
200
|
+
# Only combinations where total size <= 3
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Darwin
|
|
204
|
+
|
|
205
|
+
Evolutionary selection algorithm based on fitness evaluation. Darwin doesn't generate populations - it selects and ranks existing candidates using user-defined measures (features and dimensions) and weights. Each object is evaluated, normalized across the population, scored, and sorted by fitness.
|
|
206
|
+
|
|
207
|
+
**How it works:**
|
|
208
|
+
1. Define measures (features & dimensions) to evaluate each candidate
|
|
209
|
+
2. Define weights for each measure
|
|
210
|
+
3. Darwin evaluates all candidates, normalizes dimensions, applies weights
|
|
211
|
+
4. Returns population sorted by fitness (best first)
|
|
212
|
+
|
|
213
|
+
**Constructor:**
|
|
214
|
+
- `&block` - DSL block defining measures and weights
|
|
215
|
+
|
|
216
|
+
**DSL methods:**
|
|
217
|
+
- `measures(&block)` - Define evaluation block for each object
|
|
218
|
+
- Block receives each object to evaluate
|
|
219
|
+
- Inside block use: `feature(name)`, `dimension(name, value)`, `die`
|
|
220
|
+
- `weight(**weights)` - Assign weights to features/dimensions
|
|
221
|
+
- Positive weights favor the measure
|
|
222
|
+
- Negative weights penalize the measure
|
|
223
|
+
|
|
224
|
+
**Measures methods (inside measures block):**
|
|
225
|
+
- `feature(name)` - Mark object as having a boolean feature
|
|
226
|
+
- `dimension(name, value)` - Record numeric measurement (will be normalized 0-1)
|
|
227
|
+
- `die` - Mark object as non-viable (will be excluded from results)
|
|
228
|
+
|
|
229
|
+
**Execution methods:**
|
|
230
|
+
- `select(population)` - Evaluate and rank population, returns sorted array (best first)
|
|
231
|
+
|
|
232
|
+
```ruby
|
|
233
|
+
require 'musa-dsl'
|
|
234
|
+
|
|
235
|
+
# Generate candidate melodies using Variatio
|
|
236
|
+
variatio = Musa::Variatio::Variatio.new :melody do
|
|
237
|
+
field :interval, 1..7 # Intervals in semitones
|
|
238
|
+
field :contour, [:up, :down, :repeat]
|
|
239
|
+
field :duration, [1/4r, 1/2r, 1r]
|
|
240
|
+
|
|
241
|
+
constructor do |interval:, contour:, duration:|
|
|
242
|
+
{ interval: interval, contour: contour, duration: duration }
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
candidates = variatio.run # Generate all combinations
|
|
247
|
+
|
|
248
|
+
# Create Darwin selector with musical criteria
|
|
249
|
+
darwin = Musa::Darwin::Darwin.new do
|
|
250
|
+
measures do |melody|
|
|
251
|
+
# Eliminate melodies with unwanted characteristics
|
|
252
|
+
die if melody[:interval] > 5 # No large leaps
|
|
253
|
+
|
|
254
|
+
# Binary features (present/absent)
|
|
255
|
+
feature :stepwise if melody[:interval] <= 2 # Stepwise motion
|
|
256
|
+
feature :has_quarter_notes if melody[:duration] == 1/4r
|
|
257
|
+
|
|
258
|
+
# Numeric dimensions (will be normalized across population)
|
|
259
|
+
# Use negative values to prefer lower numbers
|
|
260
|
+
dimension :interval_size, -melody[:interval].to_f
|
|
261
|
+
dimension :duration_value, melody[:duration].to_f
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Weight each measure's contribution to fitness
|
|
265
|
+
weight interval_size: 2.0, # Strongly prefer smaller intervals
|
|
266
|
+
stepwise: 1.5, # Prefer stepwise motion
|
|
267
|
+
has_quarter_notes: 1.0, # Slightly prefer quarter notes
|
|
268
|
+
duration_value: -0.5 # Slightly penalize longer durations
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Select and rank melodies by fitness
|
|
272
|
+
ranked = darwin.select(candidates)
|
|
273
|
+
|
|
274
|
+
best_melody = ranked.first # Highest fitness
|
|
275
|
+
top_10 = ranked.first(10) # Top 10 melodies
|
|
276
|
+
worst = ranked.last # Lowest fitness (but still viable)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## API Reference
|
|
280
|
+
|
|
281
|
+
**Complete API documentation:**
|
|
282
|
+
- [Musa::Generative::Markov](https://rubydoc.info/gems/musa-dsl/Musa/Generative/Markov) - Probabilistic sequence generation
|
|
283
|
+
- [Musa::Generative::Variatio](https://rubydoc.info/gems/musa-dsl/Musa/Generative/Variatio) - Cartesian product variations
|
|
284
|
+
- [Musa::Generative::Rules](https://rubydoc.info/gems/musa-dsl/Musa/Generative/Rules) - L-system production rules
|
|
285
|
+
- [Musa::Generative::GenerativeGrammar](https://rubydoc.info/gems/musa-dsl/Musa/Generative/GenerativeGrammar) - Formal grammar generation
|
|
286
|
+
- [Musa::Generative::Darwin](https://rubydoc.info/gems/musa-dsl/Musa/Generative/Darwin) - Genetic algorithms
|
|
287
|
+
|
|
288
|
+
**Source code:** `lib/generative/`
|
|
289
|
+
|
|
290
|
+
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Matrix - Sonic Gesture Conversion
|
|
2
|
+
|
|
3
|
+
Musa::Matrix provides refinements to convert matrix representations to P (point sequences) for sequencer playback. Sonic gestures can be represented as matrices where rows are time steps and columns are sonic parameters.
|
|
4
|
+
|
|
5
|
+
This opens a world of compositional possibilities by treating sonic gestures as **geometric objects in multidimensional spaces**. Each dimension of the matrix can represent a different sonic parameter (pitch, velocity, timbre, pan, filter cutoff, etc.), allowing you to:
|
|
6
|
+
|
|
7
|
+
- **Apply mathematical transformations**: Use standard matrix operations (rotation, scaling, translation, shearing) to transform sonic gestures geometrically. A rotation matrix can morph a melodic contour into a completely different shape while maintaining its gestural coherence.
|
|
8
|
+
|
|
9
|
+
- **Compose in geometric space**: Design sonic trajectories as paths through multidimensional parameter spaces. A straight line in pitch-velocity space becomes a linear glissando with proportional dynamic changes.
|
|
10
|
+
|
|
11
|
+
- **Interpolate and morph**: Create smooth transitions between sonic states by interpolating matrix points, generating continuous parameter sweeps that move through complex multidimensional spaces.
|
|
12
|
+
|
|
13
|
+
- **Decompose and recompose**: Extract individual parameter dimensions for independent processing, then recombine them into new sonic configurations.
|
|
14
|
+
|
|
15
|
+
The conversion to P format preserves the temporal relationships (durations calculated from time differences) while making the data suitable for sequencer playback, enabling you to realize complex geometric transformations as actual sonic events.
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
require 'musa-dsl'
|
|
19
|
+
|
|
20
|
+
using Musa::Extension::Matrix
|
|
21
|
+
|
|
22
|
+
# Matrix representing a melodic gesture: [time, pitch]
|
|
23
|
+
# Time progresses: 0 -> 1 -> 2
|
|
24
|
+
# Pitch changes: 60 -> 62 -> 64
|
|
25
|
+
melody_matrix = Matrix[[0, 60], [1, 62], [2, 64]]
|
|
26
|
+
|
|
27
|
+
# Convert to P format for sequencer playback
|
|
28
|
+
# time_dimension: 0 means first column is time
|
|
29
|
+
# Time dimension removed, used only for duration calculation
|
|
30
|
+
p_sequence = melody_matrix.to_p(time_dimension: 0)
|
|
31
|
+
|
|
32
|
+
# Result: [[[60], 1, [62], 1, [64]]]
|
|
33
|
+
# Format: [[pitch1], duration1, [pitch2], duration2, [pitch3]]
|
|
34
|
+
# Each value [60], [62], [64] is extended with V module
|
|
35
|
+
|
|
36
|
+
# Multi-parameter example: [time, pitch, velocity]
|
|
37
|
+
gesture = Matrix[[0, 60, 100], [0.5, 62, 110], [1, 64, 120]]
|
|
38
|
+
p_with_velocity = gesture.to_p(time_dimension: 0)
|
|
39
|
+
# Result: [[[60, 100], 0.5, [62, 110], 0.5, [64, 120]]]
|
|
40
|
+
|
|
41
|
+
# Condensing connected gestures
|
|
42
|
+
phrase1 = Matrix[[0, 60], [1, 62]]
|
|
43
|
+
phrase2 = Matrix[[1, 62], [2, 64], [3, 65]]
|
|
44
|
+
|
|
45
|
+
# Matrices that share endpoints are automatically merged
|
|
46
|
+
merged = [phrase1, phrase2].to_p(time_dimension: 0)
|
|
47
|
+
# Result: [[[60], 1, [62], 1, [64], 1, [65]]]
|
|
48
|
+
# Both phrases merged into continuous sequence
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Use cases:**
|
|
52
|
+
- Converting recorded MIDI data to playable sequences
|
|
53
|
+
- Transforming algorithmic compositions from matrix form to time-based sequences
|
|
54
|
+
- Merging fragmented sonic gestures
|
|
55
|
+
|
|
56
|
+
## API Reference
|
|
57
|
+
|
|
58
|
+
**Complete API documentation:**
|
|
59
|
+
- [Musa::Matrix](https://rubydoc.info/gems/musa-dsl/Musa/Matrix) - Matrix to point sequence conversion
|
|
60
|
+
|
|
61
|
+
**Source code:** `lib/matrix/`
|
|
62
|
+
|
|
63
|
+
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# MIDI - Voice Management & Recording
|
|
2
|
+
|
|
3
|
+
High-level MIDI tools for sequencer-synchronized playback and recording. These utilities integrate MIDI I/O with the sequencer timeline, ensuring correct timing even during fast-forward or quantization.
|
|
4
|
+
|
|
5
|
+
## MIDIVoices - Polyphonic Voice Management
|
|
6
|
+
|
|
7
|
+
**MIDIVoices** manages MIDI channels as voices synchronized with the sequencer clock. Each voice maintains state (active notes, controllers, sustain pedal) and schedules all events on the musical timeline.
|
|
8
|
+
|
|
9
|
+
**Key features:**
|
|
10
|
+
- Voice abstraction for MIDI channels with automatic note scheduling
|
|
11
|
+
- Duration tracking and note-off scheduling
|
|
12
|
+
- Sustain pedal management
|
|
13
|
+
- Fast-forward support for silent timeline catch-up
|
|
14
|
+
- Polyphonic playback with chord support
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
require 'musa-dsl'
|
|
18
|
+
require 'midi-communications'
|
|
19
|
+
|
|
20
|
+
# Setup sequencer and MIDI output
|
|
21
|
+
output = MIDICommunications::Output.gets # Select MIDI output interactively
|
|
22
|
+
sequencer = Musa::Sequencer::Sequencer.new(4, 24)
|
|
23
|
+
|
|
24
|
+
# Create voice manager
|
|
25
|
+
voices = Musa::MIDIVoices::MIDIVoices.new(
|
|
26
|
+
sequencer: sequencer,
|
|
27
|
+
output: output,
|
|
28
|
+
channels: [0, 1, 2] # Use MIDI channels 0, 1, and 2
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Get a voice and play notes
|
|
32
|
+
voice = voices.voices.first
|
|
33
|
+
|
|
34
|
+
# Play single notes with automatic note-off
|
|
35
|
+
voice.note pitch: 60, velocity: 100, duration: 1/4r # Quarter note
|
|
36
|
+
|
|
37
|
+
# Play chords
|
|
38
|
+
voice.note pitch: [60, 64, 67], velocity: 90, duration: 1r # C major chord, whole note
|
|
39
|
+
|
|
40
|
+
# Control notes manually
|
|
41
|
+
note_ctrl = voice.note pitch: 64, velocity: 80, duration: nil # Indefinite duration
|
|
42
|
+
note_ctrl.on_stop { puts "Note ended!" }
|
|
43
|
+
# ... later:
|
|
44
|
+
note_ctrl.note_off # Manually stop the note
|
|
45
|
+
|
|
46
|
+
# Use fast-forward for silent catch-up (useful for seeking)
|
|
47
|
+
voices.fast_forward = true
|
|
48
|
+
# ... replay past events silently ...
|
|
49
|
+
voices.fast_forward = false # Resume audible output
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## MIDIRecorder - MIDI Event Recording
|
|
53
|
+
|
|
54
|
+
**MIDIRecorder** captures raw MIDI bytes alongside sequencer position timestamps and converts them into structured note events. Useful for recording phrases from external MIDI controllers synchronized with the sequencer timeline.
|
|
55
|
+
|
|
56
|
+
**Key features:**
|
|
57
|
+
- Records MIDI events with sequencer position timestamps
|
|
58
|
+
- Transcribes raw MIDI into structured note hashes
|
|
59
|
+
- Pairs note-on/note-off events automatically
|
|
60
|
+
- Calculates durations and detects silences
|
|
61
|
+
- Output format compatible with Musa transcription pipelines
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
require 'musa-dsl'
|
|
65
|
+
require 'midi-communications'
|
|
66
|
+
|
|
67
|
+
# Setup sequencer and MIDI input
|
|
68
|
+
input = MIDICommunications::Input.gets # Select MIDI input interactively
|
|
69
|
+
sequencer = Musa::Sequencer::Sequencer.new(4, 24)
|
|
70
|
+
|
|
71
|
+
# Create recorder
|
|
72
|
+
recorder = Musa::MIDIRecorder::MIDIRecorder.new(sequencer)
|
|
73
|
+
|
|
74
|
+
# Capture MIDI from controller during playback
|
|
75
|
+
input.on_message { |bytes| recorder.record(bytes) }
|
|
76
|
+
|
|
77
|
+
# Start sequencer and play/record...
|
|
78
|
+
# (MIDI events from controller are captured with timing)
|
|
79
|
+
|
|
80
|
+
# After recording, get structured notes
|
|
81
|
+
notes = recorder.transcription
|
|
82
|
+
|
|
83
|
+
# The transcription returns an array of note hashes:
|
|
84
|
+
# [
|
|
85
|
+
# { position: 1r, channel: 0, pitch: 60, velocity: 100, duration: 1/4r, velocity_off: 64 },
|
|
86
|
+
# { position: 5/4r, channel: 0, pitch: :silence, duration: 1/8r },
|
|
87
|
+
# { position: 11/8r, channel: 0, pitch: 62, velocity: 90, duration: 1/4r, velocity_off: 64 }
|
|
88
|
+
# ]
|
|
89
|
+
|
|
90
|
+
notes.each do |note|
|
|
91
|
+
if note[:pitch] == :silence
|
|
92
|
+
puts "Silence at #{note[:position]} for #{note[:duration]} bars"
|
|
93
|
+
else
|
|
94
|
+
puts "Note #{note[:pitch]} at #{note[:position]} for #{note[:duration]} bars (vel: #{note[:velocity]})"
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Access raw recorded messages if needed
|
|
99
|
+
raw_messages = recorder.raw # Array of timestamped MIDI events
|
|
100
|
+
|
|
101
|
+
# Clear for next recording
|
|
102
|
+
recorder.clear
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Transcription output format:**
|
|
106
|
+
|
|
107
|
+
Each note hash contains:
|
|
108
|
+
- `:position` - Sequencer position (Rational) when note started
|
|
109
|
+
- `:channel` - MIDI channel (0-15)
|
|
110
|
+
- `:pitch` - MIDI note number (0-127) or `:silence` for gaps
|
|
111
|
+
- `:velocity` - Note-on velocity (0-127)
|
|
112
|
+
- `:duration` - Note duration in bars (Rational)
|
|
113
|
+
- `:velocity_off` - Note-off velocity (0-127)
|
|
114
|
+
|
|
115
|
+
## API Reference
|
|
116
|
+
|
|
117
|
+
**Complete API documentation:**
|
|
118
|
+
- [Musa::MIDIVoices](https://rubydoc.info/gems/musa-dsl/Musa/MIDIVoices) - Voice management and polyphonic playback
|
|
119
|
+
- [Musa::MIDIRecorder](https://rubydoc.info/gems/musa-dsl/Musa/MIDIRecorder) - MIDI input recording and transcription
|
|
120
|
+
|
|
121
|
+
**Source code:** `lib/midi/`
|
|
122
|
+
|
|
123
|
+
|