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,465 @@
|
|
|
1
|
+
# Datasets - Sonic Data Structures
|
|
2
|
+
|
|
3
|
+
Comprehensive framework for representing and transforming sonic events and processes. Datasets are flexible, extensible hash structures that support multiple representations (MIDI, score notation, delta encoding) with rich conversion capabilities.
|
|
4
|
+
|
|
5
|
+
**Key characteristics:**
|
|
6
|
+
|
|
7
|
+
- **Flexible and extensible**: All datasets are hashes that can include any custom parameters beyond their natural keys
|
|
8
|
+
- **Event vs Process abstractions**: Distinguish between instantaneous events and time-spanning processes
|
|
9
|
+
- **Bidirectional conversions**: Transform between MIDI (PDV), score notation (GDV), delta encoding (GDVd), and other formats
|
|
10
|
+
- **Integration**: Used throughout MusaDSL components (sequencer, series, neumas, transcription, matrix)
|
|
11
|
+
|
|
12
|
+
## Dataset Hierarchy
|
|
13
|
+
|
|
14
|
+
**Event Type Modules (E)** - Define absolute vs delta encoding:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
E (base event)
|
|
18
|
+
├── Abs (absolute values)
|
|
19
|
+
│ ├── AbsI (array-indexed) → used by V
|
|
20
|
+
│ ├── AbsTimed (with :time) → used by P conversions
|
|
21
|
+
│ └── AbsD (with :duration) → used by PDV, GDV
|
|
22
|
+
└── Delta (incremental values)
|
|
23
|
+
├── DeltaI (array-indexed delta)
|
|
24
|
+
└── DeltaD (delta with duration) → used by GDVd
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Data Structure Modules** - Basic containers:
|
|
28
|
+
|
|
29
|
+
- **V**: Value arrays - ordered values in array form
|
|
30
|
+
- **PackedV**: Packed values - key-value hash pairs
|
|
31
|
+
- **P**: Point series - sequential points in time with durations [point, duration, point, duration, ...]
|
|
32
|
+
|
|
33
|
+
**Dataset Modules** - Domain-specific representations:
|
|
34
|
+
|
|
35
|
+
**Musical datasets** (scale-based and MIDI):
|
|
36
|
+
- **PDV**: Pitch/Duration/Velocity - MIDI-style absolute pitches (0-127)
|
|
37
|
+
- **GDV**: Grade/Duration/Velocity - Score-style scale degrees with dynamics
|
|
38
|
+
- **GDVd**: Grade/Duration/Velocity delta - Incremental encoding for compression
|
|
39
|
+
|
|
40
|
+
**Sonic datasets** (continuous parameters and events):
|
|
41
|
+
- **PS**: Parameter Segments - Continuous changes between multidimensional points (from/to/duration for glissandi, sweeps, modulations)
|
|
42
|
+
- **Score**: Time-indexed container for organizing sonic events
|
|
43
|
+
|
|
44
|
+
## Event Categories
|
|
45
|
+
|
|
46
|
+
**Instantaneous Sound Events** - Occur at a point in time:
|
|
47
|
+
- Events without duration (triggers, markers)
|
|
48
|
+
- AbsTimed events (time-stamped values)
|
|
49
|
+
|
|
50
|
+
**Sound Processes** - Span duration over time:
|
|
51
|
+
- Notes with duration (AbsD, PDV, GDV)
|
|
52
|
+
- Glissandi and parameter sweeps (PS)
|
|
53
|
+
- Dynamics changes and other evolving parameters
|
|
54
|
+
|
|
55
|
+
## Extensibility
|
|
56
|
+
|
|
57
|
+
All datasets support custom parameters beyond their natural keys:
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
require 'musa-dsl'
|
|
61
|
+
include Musa::Datasets
|
|
62
|
+
|
|
63
|
+
# GDV with standard parameters
|
|
64
|
+
gdv = { grade: 0, duration: 1r, velocity: 0 }.extend(GDV)
|
|
65
|
+
|
|
66
|
+
# Extended with custom parameters for your composition
|
|
67
|
+
gdv_extended = {
|
|
68
|
+
grade: 0,
|
|
69
|
+
duration: 1r,
|
|
70
|
+
velocity: 0,
|
|
71
|
+
# Custom parameters
|
|
72
|
+
articulation: :staccato,
|
|
73
|
+
timbre: :bright,
|
|
74
|
+
reverb_send: 0.3,
|
|
75
|
+
custom_control: 42
|
|
76
|
+
}.extend(GDV)
|
|
77
|
+
|
|
78
|
+
# Custom parameters preserved through conversions
|
|
79
|
+
scale = Musa::Scales::Scales.et12[440.0].major[60]
|
|
80
|
+
pdv = gdv_extended.to_pdv(scale)
|
|
81
|
+
# => { pitch: 60, duration: 1r, velocity: 64,
|
|
82
|
+
# articulation: :staccato, timbre: :bright, ... }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Dataset Validation
|
|
86
|
+
|
|
87
|
+
Check dataset validity and type:
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
include Musa::Datasets
|
|
91
|
+
|
|
92
|
+
# Create dataset
|
|
93
|
+
gdv = { grade: 0, duration: 1r, velocity: 0 }.extend(GDV)
|
|
94
|
+
|
|
95
|
+
# Validation methods
|
|
96
|
+
gdv.valid? # => true - check if valid
|
|
97
|
+
gdv.validate! # Raises if invalid, returns if valid
|
|
98
|
+
|
|
99
|
+
# Type checking
|
|
100
|
+
gdv.is_a?(GDV) # => true
|
|
101
|
+
gdv.is_a?(Abs) # => true (GDV includes Abs)
|
|
102
|
+
gdv.is_a?(AbsD) # => true (GDV includes AbsD for duration)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Dataset Conversions
|
|
106
|
+
|
|
107
|
+
Datasets provide rich conversion capabilities for transforming between different representations, each optimized for specific compositional tasks:
|
|
108
|
+
|
|
109
|
+
- **GDV ↔ PDV** (Score ↔ MIDI): Bidirectional conversion between symbolic information (scale degrees) and absolute pitches for (i.e.) MIDI output
|
|
110
|
+
- **GDV ↔ GDVd** (Absolute ↔ Delta): Generation and analysis of melodic patterns using incremental encoding
|
|
111
|
+
- **V ↔ PackedV** (Array ↔ Hash): Compact representation that expands to verbose structures with semantic labels
|
|
112
|
+
- **P → PS** (Points → Segments): Creation of glissandi and continuous interpolations between sequentially timed points
|
|
113
|
+
- **P → AbsTimed** (Relative → Absolute time): Conversion from relative temporal expressions to absolute time coordinates for (i.e.) scheduling and motif replication at different temporal positions
|
|
114
|
+
- **GDV → Neuma**: Export to Neumalang notation strings for human-readable scores
|
|
115
|
+
|
|
116
|
+
**Score ↔ MIDI (GDV ↔ PDV)**:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
include Musa::Datasets
|
|
120
|
+
|
|
121
|
+
scale = Musa::Scales::Scales.et12[440.0].major[60]
|
|
122
|
+
|
|
123
|
+
# Score to MIDI
|
|
124
|
+
gdv = { grade: 0, octave: 0, duration: 1r, velocity: 0 }.extend(GDV)
|
|
125
|
+
pdv = gdv.to_pdv(scale)
|
|
126
|
+
# => { pitch: 60, duration: 1r, velocity: 64 }
|
|
127
|
+
|
|
128
|
+
# MIDI to Score
|
|
129
|
+
pdv = { pitch: 64, duration: 1r, velocity: 80 }.extend(PDV)
|
|
130
|
+
gdv = pdv.to_gdv(scale)
|
|
131
|
+
# => { grade: 2, octave: 0, duration: 1r, velocity: 1 }
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Absolute ↔ Delta Encoding (GDV ↔ GDVd)**:
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
include Musa::Datasets
|
|
138
|
+
|
|
139
|
+
scale = Musa::Scales::Scales.et12[440.0].major[60]
|
|
140
|
+
|
|
141
|
+
# First note (absolute)
|
|
142
|
+
gdv1 = { grade: 0, duration: 1r, velocity: 0 }.extend(GDV)
|
|
143
|
+
gdvd1 = gdv1.to_gdvd(scale)
|
|
144
|
+
# => { abs_grade: 0, abs_duration: 1r, abs_velocity: 0 }
|
|
145
|
+
|
|
146
|
+
# Second note (delta from previous)
|
|
147
|
+
gdv2 = { grade: 2, duration: 1r, velocity: 1 }.extend(GDV)
|
|
148
|
+
gdvd2 = gdv2.to_gdvd(scale, previous: gdv1)
|
|
149
|
+
# => { delta_grade: 2, delta_velocity: 1 }
|
|
150
|
+
# duration unchanged, omitted for compression
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Array ↔ Hash (V ↔ PackedV)**:
|
|
154
|
+
|
|
155
|
+
```ruby
|
|
156
|
+
include Musa::Datasets
|
|
157
|
+
|
|
158
|
+
# Array to hash
|
|
159
|
+
v = [60, 1r, 64].extend(V)
|
|
160
|
+
pv = v.to_packed_V([:pitch, :duration, :velocity])
|
|
161
|
+
# => { pitch: 60, duration: 1r, velocity: 64 }
|
|
162
|
+
|
|
163
|
+
# Hash to array
|
|
164
|
+
pv = { pitch: 60, duration: 1r, velocity: 64 }.extend(PackedV)
|
|
165
|
+
v = pv.to_V([:pitch, :duration, :velocity])
|
|
166
|
+
# => [60, 1r, 64]
|
|
167
|
+
|
|
168
|
+
# With default values (compression)
|
|
169
|
+
v = [60, 1r, 64].extend(V)
|
|
170
|
+
pv = v.to_packed_V({ pitch: 60, duration: 1r, velocity: 64 })
|
|
171
|
+
# => {} (all values match defaults, fully compressed)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Series ↔ Segments (P → PS)**:
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
include Musa::Datasets
|
|
178
|
+
|
|
179
|
+
# Point series to parameter segments
|
|
180
|
+
p = [60, 4, 64, 8, 67].extend(P)
|
|
181
|
+
p.base_duration = 1/4r
|
|
182
|
+
|
|
183
|
+
ps_serie = p.to_ps_serie
|
|
184
|
+
ps1 = ps_serie.next_value
|
|
185
|
+
# => { from: 60, to: 64, duration: 1r, right_open: true }
|
|
186
|
+
|
|
187
|
+
ps2 = ps_serie.next_value
|
|
188
|
+
# => { from: 64, to: 67, duration: 2r, right_open: false }
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Series → Timed Events (P → AbsTimed)**:
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
include Musa::Datasets
|
|
195
|
+
|
|
196
|
+
p = [60, 4, 64, 8, 67].extend(P)
|
|
197
|
+
|
|
198
|
+
timed_serie = p.to_timed_serie(base_duration: 1/4r, time_start: 0)
|
|
199
|
+
timed_serie.next_value # => { time: 0r, value: 60 }
|
|
200
|
+
timed_serie.next_value # => { time: 1r, value: 64 }
|
|
201
|
+
timed_serie.next_value # => { time: 3r, value: 67 }
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Score Notation → String (GDV → Neuma)**:
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
include Musa::Datasets
|
|
208
|
+
|
|
209
|
+
gdv = { grade: 0, octave: 1, duration: 1r, velocity: 2 }.extend(GDV)
|
|
210
|
+
gdv.base_duration = 1/4r
|
|
211
|
+
|
|
212
|
+
neuma = gdv.to_neuma
|
|
213
|
+
# => "(0 o1 4 f)"
|
|
214
|
+
# Format: (grade octave duration_in_quarters dynamics)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Integration with Other Components
|
|
218
|
+
|
|
219
|
+
**Sequencer** - Accepts extended datasets:
|
|
220
|
+
|
|
221
|
+
```ruby
|
|
222
|
+
require 'musa-dsl'
|
|
223
|
+
include Musa::All
|
|
224
|
+
|
|
225
|
+
sequencer = Sequencer.new(4, 24)
|
|
226
|
+
|
|
227
|
+
# Use GDV datasets directly
|
|
228
|
+
sequencer.at 1 do
|
|
229
|
+
event = { grade: 0, duration: 1r, velocity: 0, articulation: :legato }.extend(GDV)
|
|
230
|
+
# Process event...
|
|
231
|
+
end
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Series** - Work with any dataset type:
|
|
235
|
+
|
|
236
|
+
```ruby
|
|
237
|
+
include Musa::All
|
|
238
|
+
|
|
239
|
+
# Series of GDV events
|
|
240
|
+
gdv_serie = S(
|
|
241
|
+
{ grade: 0, duration: 1r }.extend(GDV),
|
|
242
|
+
{ grade: 2, duration: 1r }.extend(GDV),
|
|
243
|
+
{ grade: 4, duration: 1r }.extend(GDV)
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
# Transform while preserving dataset type
|
|
247
|
+
scale = Scales.et12[440.0].major[60]
|
|
248
|
+
pdv_serie = gdv_serie.map { |gdv| gdv.to_pdv(scale) }
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**Matrix** - Generates P series:
|
|
252
|
+
|
|
253
|
+
```ruby
|
|
254
|
+
require 'musa-dsl'
|
|
255
|
+
using Musa::Extension::Matrix
|
|
256
|
+
|
|
257
|
+
# Matrix to P format
|
|
258
|
+
gesture = Matrix[[0, 60], [1, 62], [2, 64]]
|
|
259
|
+
p_sequences = gesture.to_p(time_dimension: 0)
|
|
260
|
+
# => [[[60], 1, [62], 1, [64]]]
|
|
261
|
+
# P format: alternating values and durations
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**Neumas** - Parse to GDV:
|
|
265
|
+
|
|
266
|
+
```ruby
|
|
267
|
+
include Musa::All
|
|
268
|
+
|
|
269
|
+
# Neuma strings parse to GDV datasets
|
|
270
|
+
neuma = "(0 4 mf) (2 4 f) (4 4 ff)"
|
|
271
|
+
gdv_serie = Neumas(neuma, scale: Scales.default_system.default_tuning.major[60])
|
|
272
|
+
|
|
273
|
+
gdv_serie.each do |gdv|
|
|
274
|
+
puts gdv.inspect # Each is a GDV hash
|
|
275
|
+
# => { grade: 0, duration: 1r, velocity: 0 }
|
|
276
|
+
# => { grade: 2, duration: 1r, velocity: 1 }
|
|
277
|
+
# => { grade: 4, duration: 1r, velocity: 2 }
|
|
278
|
+
end
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Transcription** - Converts between representations:
|
|
282
|
+
|
|
283
|
+
```ruby
|
|
284
|
+
include Musa::All
|
|
285
|
+
|
|
286
|
+
scale = Scales.et12[440.0].major[60]
|
|
287
|
+
|
|
288
|
+
# GDV to PDV for MIDI output
|
|
289
|
+
gdv_events = [
|
|
290
|
+
{ grade: 0, duration: 1r, velocity: 0 }.extend(GDV),
|
|
291
|
+
{ grade: 2, duration: 1r, velocity: 1 }.extend(GDV)
|
|
292
|
+
]
|
|
293
|
+
|
|
294
|
+
midi_events = gdv_events.map { |gdv| gdv.to_pdv(scale) }
|
|
295
|
+
# Send to MIDI output...
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
**Score Container** - Organize events in time:
|
|
299
|
+
|
|
300
|
+
```ruby
|
|
301
|
+
include Musa::Datasets
|
|
302
|
+
|
|
303
|
+
score = Score.new
|
|
304
|
+
|
|
305
|
+
# Add events at specific times
|
|
306
|
+
score.at(1r, add: { grade: 0, duration: 1r }.extend(GDV))
|
|
307
|
+
score.at(2r, add: { grade: 2, duration: 1r }.extend(GDV))
|
|
308
|
+
score.at(3r, add: { grade: 4, duration: 1r }.extend(GDV))
|
|
309
|
+
|
|
310
|
+
# Query events
|
|
311
|
+
events_at_2 = score.at(2r)
|
|
312
|
+
events_in_range = score.between(1r, 3r)
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## API Reference
|
|
316
|
+
|
|
317
|
+
**Complete API documentation:**
|
|
318
|
+
- [Musa::Datasets](https://rubydoc.info/gems/musa-dsl/Musa/Datasets) - Musical data structures (GDV, PDV, Score)
|
|
319
|
+
|
|
320
|
+
**Source code:** `lib/datasets/`
|
|
321
|
+
|
|
322
|
+
## Score - Advanced Queries & Filtering
|
|
323
|
+
|
|
324
|
+
Score provides powerful query and filtering capabilities beyond basic event storage. These methods enable temporal analysis, event filtering, and subset extraction.
|
|
325
|
+
|
|
326
|
+
**Interval Queries** - `between(start, finish)`:
|
|
327
|
+
|
|
328
|
+
Retrieves all events that overlap a given time interval. Returns events with their effective start/finish times within the query range.
|
|
329
|
+
|
|
330
|
+
```ruby
|
|
331
|
+
include Musa::Datasets
|
|
332
|
+
|
|
333
|
+
score = Score.new
|
|
334
|
+
|
|
335
|
+
# Add events with durations
|
|
336
|
+
score.at(1r, add: { pitch: 60, duration: 2r }.extend(PDV)) # 1-3
|
|
337
|
+
score.at(2r, add: { pitch: 64, duration: 1r }.extend(PDV)) # 2-3
|
|
338
|
+
score.at(3r, add: { pitch: 67, duration: 2r }.extend(PDV)) # 3-5
|
|
339
|
+
|
|
340
|
+
# Query events overlapping interval [2, 4)
|
|
341
|
+
events = score.between(2r, 4r)
|
|
342
|
+
|
|
343
|
+
events.each do |event|
|
|
344
|
+
puts "Pitch #{event[:dataset][:pitch]}"
|
|
345
|
+
puts " Original: #{event[:start]} - #{event[:finish]}"
|
|
346
|
+
puts " In interval: #{event[:start_in_interval]} - #{event[:finish_in_interval]}"
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# => Pitch 60 (started at 1, overlaps 2-4)
|
|
350
|
+
# Original: 1r - 3r
|
|
351
|
+
# In interval: 2r - 3r
|
|
352
|
+
#
|
|
353
|
+
# => Pitch 64 (completely within 2-4)
|
|
354
|
+
# Original: 2r - 3r
|
|
355
|
+
# In interval: 2r - 3r
|
|
356
|
+
#
|
|
357
|
+
# => Pitch 67 (started at 3, overlaps 2-4)
|
|
358
|
+
# Original: 3r - 5r
|
|
359
|
+
# In interval: 3r - 4r
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**Timeline Changes** - `changes_between(start, finish)`:
|
|
363
|
+
|
|
364
|
+
Returns a timeline of note-on/note-off style events. Useful for real-time rendering, event-based processing, or analyzing harmonic density over time.
|
|
365
|
+
|
|
366
|
+
```ruby
|
|
367
|
+
include Musa::Datasets
|
|
368
|
+
|
|
369
|
+
score = Score.new
|
|
370
|
+
|
|
371
|
+
score.at(1r, add: { pitch: 60, duration: 2r }.extend(PDV))
|
|
372
|
+
score.at(2r, add: { pitch: 64, duration: 1r }.extend(PDV))
|
|
373
|
+
score.at(3r, add: { pitch: 67, duration: 1r }.extend(PDV))
|
|
374
|
+
|
|
375
|
+
# Get all start/finish events in bar
|
|
376
|
+
changes = score.changes_between(0r, 4r)
|
|
377
|
+
|
|
378
|
+
changes.each do |change|
|
|
379
|
+
case change[:change]
|
|
380
|
+
when :start
|
|
381
|
+
puts "#{change[:time]}: Note ON - pitch #{change[:dataset][:pitch]}"
|
|
382
|
+
when :finish
|
|
383
|
+
puts "#{change[:time]}: Note OFF - pitch #{change[:dataset][:pitch]}"
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# => 1r: Note ON - pitch 60
|
|
388
|
+
# 2r: Note ON - pitch 64
|
|
389
|
+
# 3r: Note OFF - pitch 60
|
|
390
|
+
# 3r: Note OFF - pitch 64
|
|
391
|
+
# 3r: Note ON - pitch 67
|
|
392
|
+
# 4r: Note OFF - pitch 67
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
**Attribute Collection** - `values_of(attribute)`:
|
|
396
|
+
|
|
397
|
+
Extracts all unique values for a specific attribute across all events. Useful for analysis, validation, or generating material from existing compositions.
|
|
398
|
+
|
|
399
|
+
```ruby
|
|
400
|
+
include Musa::Datasets
|
|
401
|
+
|
|
402
|
+
score = Score.new
|
|
403
|
+
|
|
404
|
+
score.at(1r, add: { pitch: 60, duration: 1r }.extend(PDV))
|
|
405
|
+
score.at(2r, add: { pitch: 64, duration: 1r }.extend(PDV))
|
|
406
|
+
score.at(3r, add: { pitch: 67, duration: 1r }.extend(PDV))
|
|
407
|
+
score.at(4r, add: { pitch: 64, duration: 1r }.extend(PDV)) # Repeated
|
|
408
|
+
|
|
409
|
+
# Get all unique pitches used
|
|
410
|
+
pitches = score.values_of(:pitch)
|
|
411
|
+
# => #<Set: {60, 64, 67}>
|
|
412
|
+
|
|
413
|
+
# Analyze durations
|
|
414
|
+
durations = score.values_of(:duration)
|
|
415
|
+
# => #<Set: {1r}>
|
|
416
|
+
|
|
417
|
+
# Check velocities
|
|
418
|
+
velocities = score.values_of(:velocity)
|
|
419
|
+
# => #<Set: {64}> (default velocity from PDV)
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
**Filtering** - `subset { |event| condition }`:
|
|
423
|
+
|
|
424
|
+
Creates a new Score containing only events matching a condition. Preserves timing and all event attributes.
|
|
425
|
+
|
|
426
|
+
```ruby
|
|
427
|
+
include Musa::Datasets
|
|
428
|
+
|
|
429
|
+
score = Score.new
|
|
430
|
+
|
|
431
|
+
score.at(1r, add: { pitch: 60, velocity: 80, duration: 1r }.extend(PDV))
|
|
432
|
+
score.at(2r, add: { pitch: 72, velocity: 100, duration: 1r }.extend(PDV))
|
|
433
|
+
score.at(3r, add: { pitch: 64, velocity: 60, duration: 1r }.extend(PDV))
|
|
434
|
+
score.at(4r, add: { pitch: 79, velocity: 90, duration: 1r }.extend(PDV))
|
|
435
|
+
|
|
436
|
+
# Filter by pitch range
|
|
437
|
+
high_notes = score.subset { |event| event[:pitch] >= 70 }
|
|
438
|
+
|
|
439
|
+
high_notes.at(2r).first[:pitch] # => 72
|
|
440
|
+
high_notes.at(4r).first[:pitch] # => 79
|
|
441
|
+
|
|
442
|
+
# Filter by velocity
|
|
443
|
+
loud_notes = score.subset { |event| event[:velocity] >= 85 }
|
|
444
|
+
|
|
445
|
+
# Filter by custom attribute
|
|
446
|
+
scale = Musa::Scales::Scales.et12[440.0].major[60]
|
|
447
|
+
|
|
448
|
+
score_gdv = Score.new
|
|
449
|
+
score_gdv.at(1r, add: { grade: 0, duration: 1r }.extend(GDV))
|
|
450
|
+
score_gdv.at(2r, add: { grade: 2, duration: 1r }.extend(GDV))
|
|
451
|
+
score_gdv.at(3r, add: { grade: 4, duration: 1r }.extend(GDV))
|
|
452
|
+
|
|
453
|
+
# Extract tonic notes only
|
|
454
|
+
tonic_notes = score_gdv.subset { |event| event[:grade] == 0 }
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
**Use Cases:**
|
|
458
|
+
|
|
459
|
+
- **Score Analysis**: Extract patterns, identify harmonic structures, analyze voice leading
|
|
460
|
+
- **Partial Rendering**: Render only specific time ranges or event types
|
|
461
|
+
- **Material Generation**: Extract pitches, rhythms, or other parameters for reuse
|
|
462
|
+
- **Real-time Processing**: Convert to timeline format for event-based playback
|
|
463
|
+
- **Filtering**: Create variations by extracting subsets (high notes, loud notes, specific scales)
|
|
464
|
+
|
|
465
|
+
|