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.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/.version +6 -0
  4. data/.yardopts +7 -0
  5. data/README.md +227 -6
  6. data/docs/README.md +83 -0
  7. data/docs/api-reference.md +86 -0
  8. data/docs/getting-started/quick-start.md +93 -0
  9. data/docs/getting-started/tutorial.md +58 -0
  10. data/docs/subsystems/core-extensions.md +316 -0
  11. data/docs/subsystems/datasets.md +465 -0
  12. data/docs/subsystems/generative.md +290 -0
  13. data/docs/subsystems/matrix.md +63 -0
  14. data/docs/subsystems/midi.md +123 -0
  15. data/docs/subsystems/music.md +233 -0
  16. data/docs/subsystems/musicxml-builder.md +264 -0
  17. data/docs/subsystems/neumas.md +71 -0
  18. data/docs/subsystems/repl.md +135 -0
  19. data/docs/subsystems/sequencer.md +98 -0
  20. data/docs/subsystems/series.md +302 -0
  21. data/docs/subsystems/transcription.md +152 -0
  22. data/docs/subsystems/transport.md +177 -0
  23. data/lib/musa-dsl/core-ext/array-explode-ranges.rb +68 -0
  24. data/lib/musa-dsl/core-ext/arrayfy.rb +110 -0
  25. data/lib/musa-dsl/core-ext/attribute-builder.rb +91 -30
  26. data/lib/musa-dsl/core-ext/deep-copy.rb +125 -2
  27. data/lib/musa-dsl/core-ext/dynamic-proxy.rb +78 -0
  28. data/lib/musa-dsl/core-ext/extension.rb +53 -0
  29. data/lib/musa-dsl/core-ext/hashify.rb +162 -1
  30. data/lib/musa-dsl/core-ext/inspect-nice.rb +154 -0
  31. data/lib/musa-dsl/core-ext/smart-proc-binder.rb +117 -0
  32. data/lib/musa-dsl/core-ext/with.rb +114 -0
  33. data/lib/musa-dsl/datasets/dataset.rb +109 -0
  34. data/lib/musa-dsl/datasets/delta-d.rb +78 -0
  35. data/lib/musa-dsl/datasets/e.rb +186 -2
  36. data/lib/musa-dsl/datasets/gdv.rb +279 -2
  37. data/lib/musa-dsl/datasets/gdvd.rb +201 -0
  38. data/lib/musa-dsl/datasets/helper.rb +75 -0
  39. data/lib/musa-dsl/datasets/p.rb +177 -2
  40. data/lib/musa-dsl/datasets/packed-v.rb +91 -0
  41. data/lib/musa-dsl/datasets/pdv.rb +136 -1
  42. data/lib/musa-dsl/datasets/ps.rb +134 -4
  43. data/lib/musa-dsl/datasets/score/queriable.rb +143 -1
  44. data/lib/musa-dsl/datasets/score/render.rb +105 -1
  45. data/lib/musa-dsl/datasets/score/to-mxml/process-pdv.rb +138 -1
  46. data/lib/musa-dsl/datasets/score/to-mxml/process-ps.rb +111 -0
  47. data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +200 -1
  48. data/lib/musa-dsl/datasets/score/to-mxml/to-mxml.rb +145 -1
  49. data/lib/musa-dsl/datasets/score.rb +279 -0
  50. data/lib/musa-dsl/datasets/v.rb +88 -0
  51. data/lib/musa-dsl/generative/darwin.rb +180 -1
  52. data/lib/musa-dsl/generative/generative-grammar.rb +359 -0
  53. data/lib/musa-dsl/generative/markov.rb +133 -3
  54. data/lib/musa-dsl/generative/rules.rb +258 -4
  55. data/lib/musa-dsl/generative/variatio.rb +217 -2
  56. data/lib/musa-dsl/logger/logger.rb +267 -2
  57. data/lib/musa-dsl/matrix/matrix.rb +256 -10
  58. data/lib/musa-dsl/midi/midi-recorder.rb +108 -1
  59. data/lib/musa-dsl/midi/midi-voices.rb +265 -4
  60. data/lib/musa-dsl/music/chord-definition.rb +233 -1
  61. data/lib/musa-dsl/music/chord-definitions.rb +33 -6
  62. data/lib/musa-dsl/music/chords.rb +308 -2
  63. data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +315 -0
  64. data/lib/musa-dsl/music/scales.rb +957 -40
  65. data/lib/musa-dsl/musicxml/builder/attributes.rb +483 -3
  66. data/lib/musa-dsl/musicxml/builder/backup-forward.rb +166 -1
  67. data/lib/musa-dsl/musicxml/builder/direction.rb +243 -0
  68. data/lib/musa-dsl/musicxml/builder/helper.rb +240 -0
  69. data/lib/musa-dsl/musicxml/builder/measure.rb +284 -0
  70. data/lib/musa-dsl/musicxml/builder/note-complexities.rb +324 -8
  71. data/lib/musa-dsl/musicxml/builder/note.rb +285 -0
  72. data/lib/musa-dsl/musicxml/builder/part-group.rb +108 -1
  73. data/lib/musa-dsl/musicxml/builder/part.rb +139 -0
  74. data/lib/musa-dsl/musicxml/builder/pitched-note.rb +124 -0
  75. data/lib/musa-dsl/musicxml/builder/rest.rb +93 -0
  76. data/lib/musa-dsl/musicxml/builder/score-partwise.rb +276 -0
  77. data/lib/musa-dsl/musicxml/builder/typed-text.rb +62 -1
  78. data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +83 -0
  79. data/lib/musa-dsl/neumalang/neumalang.rb +675 -0
  80. data/lib/musa-dsl/neumas/array-to-neumas.rb +149 -0
  81. data/lib/musa-dsl/neumas/neuma-decoder.rb +253 -0
  82. data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +142 -2
  83. data/lib/musa-dsl/neumas/neuma-gdvd-decoder.rb +82 -0
  84. data/lib/musa-dsl/neumas/neumas.rb +67 -0
  85. data/lib/musa-dsl/neumas/string-to-neumas.rb +233 -1
  86. data/lib/musa-dsl/repl/repl.rb +550 -0
  87. data/lib/musa-dsl/sequencer/base-sequencer-implementation-every.rb +118 -2
  88. data/lib/musa-dsl/sequencer/base-sequencer-implementation-move.rb +149 -2
  89. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +296 -0
  90. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-timed.rb +88 -2
  91. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb +161 -0
  92. data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +263 -0
  93. data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +173 -1
  94. data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +177 -0
  95. data/lib/musa-dsl/sequencer/base-sequencer.rb +710 -10
  96. data/lib/musa-dsl/sequencer/sequencer-dsl.rb +210 -0
  97. data/lib/musa-dsl/sequencer/timeslots.rb +79 -0
  98. data/lib/musa-dsl/series/array-to-serie.rb +37 -1
  99. data/lib/musa-dsl/series/base-series.rb +843 -5
  100. data/lib/musa-dsl/series/buffer-serie.rb +48 -0
  101. data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +41 -0
  102. data/lib/musa-dsl/series/main-serie-constructors.rb +398 -2
  103. data/lib/musa-dsl/series/main-serie-operations.rb +538 -16
  104. data/lib/musa-dsl/series/proxy-serie.rb +67 -0
  105. data/lib/musa-dsl/series/quantizer-serie.rb +45 -7
  106. data/lib/musa-dsl/series/queue-serie.rb +65 -0
  107. data/lib/musa-dsl/series/series-composer.rb +701 -0
  108. data/lib/musa-dsl/series/timed-serie.rb +473 -28
  109. data/lib/musa-dsl/transcription/from-gdv-to-midi.rb +404 -1
  110. data/lib/musa-dsl/transcription/from-gdv-to-musicxml.rb +118 -0
  111. data/lib/musa-dsl/transcription/from-gdv.rb +84 -1
  112. data/lib/musa-dsl/transcription/transcription.rb +265 -0
  113. data/lib/musa-dsl/transport/clock.rb +125 -0
  114. data/lib/musa-dsl/transport/dummy-clock.rb +89 -2
  115. data/lib/musa-dsl/transport/external-tick-clock.rb +91 -0
  116. data/lib/musa-dsl/transport/input-midi-clock.rb +133 -1
  117. data/lib/musa-dsl/transport/timer-clock.rb +183 -1
  118. data/lib/musa-dsl/transport/timer.rb +83 -0
  119. data/lib/musa-dsl/transport/transport.rb +318 -0
  120. data/lib/musa-dsl/version.rb +1 -1
  121. data/lib/musa-dsl.rb +132 -25
  122. data/musa-dsl.gemspec +12 -10
  123. 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
+