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
@@ -6,9 +6,53 @@ module Musa
6
6
  module MusicXML
7
7
  module Builder
8
8
  module Internal
9
+ # Key signature specification.
10
+ #
11
+ # Key represents a key signature in terms of the circle of fifths and mode.
12
+ # For multi-staff parts (like piano), the number attribute specifies which
13
+ # staff the key signature applies to.
14
+ #
15
+ # ## Circle of Fifths
16
+ #
17
+ # The `fifths` attribute uses the circle of fifths representation:
18
+ #
19
+ # - **Negative** values: flats (C♭ major = -7, F major = -1)
20
+ # - **Zero**: C major / A minor
21
+ # - **Positive** values: sharps (G major = +1, C♯ major = +7)
22
+ #
23
+ # Common keys:
24
+ #
25
+ # - -7: C♭, -6: G♭, -5: D♭, -4: A♭, -3: E♭, -2: B♭, -1: F
26
+ # - 0: C (major) / A (minor)
27
+ # - +1: G, +2: D, +3: A, +4: E, +5: B, +6: F♯, +7: C♯
28
+ #
29
+ # @example C major
30
+ # Key.new(fifths: 0)
31
+ #
32
+ # @example D major (2 sharps)
33
+ # Key.new(fifths: 2, mode: 'major')
34
+ #
35
+ # @example B♭ minor (5 flats)
36
+ # Key.new(fifths: -5, mode: 'minor')
37
+ #
38
+ # @example Piano - different keys per staff
39
+ # Key.new(1, fifths: 0) # Treble clef: C major
40
+ # Key.new(2, fifths: 0) # Bass clef: C major
9
41
  class Key
10
42
  include Helper::ToXML
11
43
 
44
+ # Creates a key signature.
45
+ #
46
+ # @param number [Integer, nil] staff number (for multi-staff parts)
47
+ # @param cancel [Integer, nil] number of accidentals to cancel from previous key
48
+ # @param fifths [Integer] circle of fifths position (-7 to +7)
49
+ # @param mode [String, nil] 'major' or 'minor'
50
+ #
51
+ # @example G major
52
+ # Key.new(fifths: 1, mode: 'major')
53
+ #
54
+ # @example E minor
55
+ # Key.new(fifths: 1, mode: 'minor')
12
56
  def initialize(number = nil, cancel: nil, fifths:, mode: nil)
13
57
  @number = number
14
58
 
@@ -17,9 +61,30 @@ module Musa
17
61
  @mode = mode
18
62
  end
19
63
 
64
+ # Staff number (for multi-staff instruments).
65
+ # @return [Integer, nil]
20
66
  attr_reader :number
21
- attr_accessor :cancel, :fifths, :mode
22
67
 
68
+ # Number of accidentals to cancel.
69
+ # @return [Integer, nil]
70
+ attr_accessor :cancel
71
+
72
+ # Circle of fifths position (-7 to +7).
73
+ # @return [Integer]
74
+ attr_accessor :fifths
75
+
76
+ # Mode ('major' or 'minor').
77
+ # @return [String, nil]
78
+ attr_accessor :mode
79
+
80
+ # Generates the key signature XML element.
81
+ #
82
+ # @param io [IO] output stream
83
+ # @param indent [Integer] indentation level
84
+ # @param tabs [String] tab string
85
+ # @return [void]
86
+ #
87
+ # @api private
23
88
  def _to_xml(io, indent:, tabs:)
24
89
  io ||= StringIO.new
25
90
  indent ||= 0
@@ -30,7 +95,7 @@ module Musa
30
95
 
31
96
  io.puts "#{tabs}\t<cancel>#{@cancel}</cancel>" if @cancel
32
97
  io.puts "#{tabs}\t<fifths>#{@fifths.to_i}</fifths>"
33
- io.puts "#{tabs}\t<mode>#{@mode}</mode>"
98
+ io.puts "#{tabs}\t<mode>#{@mode}</mode>" if @mode
34
99
 
35
100
  io.puts "#{tabs}</key>"
36
101
 
@@ -38,9 +103,83 @@ module Musa
38
103
  end
39
104
  end
40
105
 
106
+ # Time signature specification.
107
+ #
108
+ # Time represents time signatures in MusicXML. It supports simple meters
109
+ # (4/4, 3/4), compound meters (6/8), complex meters (5/4), and compound
110
+ # time signatures with multiple beat groups. Also supports unmeasured time
111
+ # (senza misura) for cadenzas and free-form sections.
112
+ #
113
+ # For multi-staff parts (like piano), the number attribute specifies which
114
+ # staff the time signature applies to.
115
+ #
116
+ # ## Simple Time Signatures
117
+ #
118
+ # Most common meters use a single beats/beat_type pair:
119
+ #
120
+ # - 4/4 (common time): beats=4, beat_type=4
121
+ # - 3/4 (waltz): beats=3, beat_type=4
122
+ # - 6/8 (compound): beats=6, beat_type=8
123
+ # - 2/2 (cut time): beats=2, beat_type=2
124
+ #
125
+ # ## Compound Time Signatures
126
+ #
127
+ # Some meters combine multiple beat groups (e.g., 3+2+3/8):
128
+ #
129
+ # Time.new do |t|
130
+ # t.add_beats beats: 3, beat_type: 8
131
+ # t.add_beats beats: 2, beat_type: 8
132
+ # t.add_beats beats: 3, beat_type: 8
133
+ # end
134
+ #
135
+ # ## Senza Misura (Unmeasured Time)
136
+ #
137
+ # For cadenzas and free-form sections without strict meter:
138
+ #
139
+ # Time.new(senza_misura: '')
140
+ #
141
+ # @example Common time (4/4)
142
+ # Time.new(beats: 4, beat_type: 4)
143
+ #
144
+ # @example Waltz (3/4)
145
+ # Time.new(beats: 3, beat_type: 4)
146
+ #
147
+ # @example Compound meter (6/8)
148
+ # Time.new(beats: 6, beat_type: 8)
149
+ #
150
+ # @example Complex meter (5/4)
151
+ # Time.new(beats: 5, beat_type: 4)
152
+ #
153
+ # @example Compound signature (3+2+3/8)
154
+ # time = Time.new
155
+ # time.add_beats(beats: 3, beat_type: 8)
156
+ # time.add_beats(beats: 2, beat_type: 8)
157
+ # time.add_beats(beats: 3, beat_type: 8)
158
+ #
159
+ # @example Piano - different time per staff
160
+ # Time.new(1, beats: 4, beat_type: 4) # Treble: 4/4
161
+ # Time.new(2, beats: 3, beat_type: 4) # Bass: 3/4
162
+ #
163
+ # @example Cadenza (unmeasured)
164
+ # Time.new(senza_misura: '')
41
165
  class Time
42
166
  include Helper::ToXML
43
167
 
168
+ # Creates a time signature.
169
+ #
170
+ # @param number [Integer, nil] staff number (for multi-staff parts)
171
+ # @param senza_misura [String, nil] unmeasured time indicator (typically empty string)
172
+ # @param beats [Integer, nil] time signature numerator (beats per measure)
173
+ # @param beat_type [Integer, nil] time signature denominator (note value per beat)
174
+ #
175
+ # @example 4/4 time
176
+ # Time.new(beats: 4, beat_type: 4)
177
+ #
178
+ # @example 6/8 time
179
+ # Time.new(beats: 6, beat_type: 8)
180
+ #
181
+ # @example Unmeasured time
182
+ # Time.new(senza_misura: '')
44
183
  def initialize(number = nil, senza_misura: nil, beats: nil, beat_type: nil)
45
184
  @number = number
46
185
 
@@ -50,14 +189,44 @@ module Musa
50
189
  add_beats beats: beats, beat_type: beat_type if beats && beat_type
51
190
  end
52
191
 
192
+ # Staff number (for multi-staff instruments).
193
+ # @return [Integer, nil]
53
194
  attr_reader :number
195
+
196
+ # Senza misura indicator for unmeasured time.
197
+ # @return [String, nil]
54
198
  attr_accessor :senza_misura
199
+
200
+ # Array of beat groups for compound time signatures.
201
+ # @return [Array<Hash>]
55
202
  attr_reader :beats
56
203
 
204
+ # Adds a beat group to the time signature.
205
+ #
206
+ # Used for compound time signatures that combine multiple beat groups
207
+ # (e.g., 3+2+3/8 or 2+2+3/4).
208
+ #
209
+ # @param beats [Integer] numerator for this beat group
210
+ # @param beat_type [Integer] denominator for this beat group
211
+ # @return [void]
212
+ #
213
+ # @example Complex meter (3+2+3/8)
214
+ # time = Time.new
215
+ # time.add_beats(beats: 3, beat_type: 8)
216
+ # time.add_beats(beats: 2, beat_type: 8)
217
+ # time.add_beats(beats: 3, beat_type: 8)
57
218
  def add_beats(beats:, beat_type:)
58
219
  @beats << { beats: beats, beat_type: beat_type }
59
220
  end
60
221
 
222
+ # Generates the time signature XML element.
223
+ #
224
+ # @param io [IO] output stream
225
+ # @param indent [Integer] indentation level
226
+ # @param tabs [String] tab string
227
+ # @return [void]
228
+ #
229
+ # @api private
61
230
  def _to_xml(io, indent:, tabs:)
62
231
  io.puts "#{tabs}<time#{" number=\"#{@number.to_i}\"" if @number}>"
63
232
 
@@ -71,9 +240,102 @@ module Musa
71
240
  end
72
241
  end
73
242
 
243
+ # Clef specification.
244
+ #
245
+ # Clef represents musical clefs that determine the pitch range displayed
246
+ # on a staff. MusicXML supports standard clefs (treble, bass, alto, tenor),
247
+ # percussion clefs, and tablature clefs.
248
+ #
249
+ # For multi-staff parts (like piano), the number attribute specifies which
250
+ # staff the clef applies to.
251
+ #
252
+ # ## Common Clefs
253
+ #
254
+ # Standard clefs are defined by a sign and staff line number:
255
+ #
256
+ # - **Treble (G clef)**: sign='G', line=2
257
+ # - **Bass (F clef)**: sign='F', line=4
258
+ # - **Alto (C clef)**: sign='C', line=3
259
+ # - **Tenor (C clef)**: sign='C', line=4
260
+ # - **Percussion**: sign='percussion'
261
+ # - **Tablature**: sign='TAB'
262
+ #
263
+ # ## Clef Signs
264
+ #
265
+ # The sign parameter determines the clef type:
266
+ #
267
+ # - **G**: Treble clef family
268
+ # - **F**: Bass clef family
269
+ # - **C**: Alto/tenor clef family (movable C clef)
270
+ # - **percussion**: For unpitched percussion
271
+ # - **TAB**: For guitar/bass tablature
272
+ #
273
+ # ## Staff Lines
274
+ #
275
+ # The line parameter indicates which staff line the clef sign sits on:
276
+ #
277
+ # - Lines are numbered 1-5 from bottom to top
278
+ # - Treble clef (G) typically on line 2
279
+ # - Bass clef (F) typically on line 4
280
+ # - Alto clef (C) on line 3, Tenor clef (C) on line 4
281
+ #
282
+ # ## Octave Transposition
283
+ #
284
+ # The octave_change parameter transposes notation by octaves:
285
+ #
286
+ # - **-2**: 15ma basso (two octaves down)
287
+ # - **-1**: 8va basso (one octave down)
288
+ # - **0**: No transposition (default)
289
+ # - **+1**: 8va alta (one octave up)
290
+ # - **+2**: 15ma alta (two octaves up)
291
+ #
292
+ # Common for tenor voice (treble clef 8va basso) and piccolo (treble 8va alta).
293
+ #
294
+ # @example Treble clef
295
+ # Clef.new(sign: 'G', line: 2)
296
+ #
297
+ # @example Bass clef
298
+ # Clef.new(sign: 'F', line: 4)
299
+ #
300
+ # @example Alto clef
301
+ # Clef.new(sign: 'C', line: 3)
302
+ #
303
+ # @example Tenor clef
304
+ # Clef.new(sign: 'C', line: 4)
305
+ #
306
+ # @example Tenor voice (treble 8va basso)
307
+ # Clef.new(sign: 'G', line: 2, octave_change: -1)
308
+ #
309
+ # @example Piccolo (treble 8va alta)
310
+ # Clef.new(sign: 'G', line: 2, octave_change: 1)
311
+ #
312
+ # @example Piano - different clefs per staff
313
+ # Clef.new(1, sign: 'G', line: 2) # Treble clef (right hand)
314
+ # Clef.new(2, sign: 'F', line: 4) # Bass clef (left hand)
315
+ #
316
+ # @example Percussion
317
+ # Clef.new(sign: 'percussion')
318
+ #
319
+ # @example Guitar tablature
320
+ # Clef.new(sign: 'TAB')
74
321
  class Clef
75
322
  include Helper::ToXML
76
323
 
324
+ # Creates a clef.
325
+ #
326
+ # @param number [Integer, nil] staff number (for multi-staff parts)
327
+ # @param sign [String] clef sign: 'G', 'F', 'C', 'percussion', 'TAB'
328
+ # @param line [Integer] staff line number (1-5, bottom to top)
329
+ # @param octave_change [Integer, nil] octave transposition (-2, -1, 0, +1, +2)
330
+ #
331
+ # @example Treble clef
332
+ # Clef.new(sign: 'G', line: 2)
333
+ #
334
+ # @example Bass clef
335
+ # Clef.new(sign: 'F', line: 4)
336
+ #
337
+ # @example Tenor voice (treble 8va basso)
338
+ # Clef.new(sign: 'G', line: 2, octave_change: -1)
77
339
  def initialize(number = nil, sign:, line:, octave_change: nil)
78
340
  @number = number
79
341
  @sign = sign
@@ -81,9 +343,30 @@ module Musa
81
343
  @octave_change = octave_change
82
344
  end
83
345
 
346
+ # Staff number (for multi-staff instruments).
347
+ # @return [Integer, nil]
84
348
  attr_reader :number
85
- attr_accessor :sign, :line, :octave_change
86
349
 
350
+ # Clef sign (G, F, C, percussion, TAB).
351
+ # @return [String]
352
+ attr_accessor :sign
353
+
354
+ # Staff line number (1-5).
355
+ # @return [Integer]
356
+ attr_accessor :line
357
+
358
+ # Octave transposition (-2 to +2).
359
+ # @return [Integer, nil]
360
+ attr_accessor :octave_change
361
+
362
+ # Generates the clef XML element.
363
+ #
364
+ # @param io [IO] output stream
365
+ # @param indent [Integer] indentation level
366
+ # @param tabs [String] tab string
367
+ # @return [void]
368
+ #
369
+ # @api private
87
370
  def _to_xml(io, indent:, tabs:)
88
371
  io.puts "#{tabs}<clef#{" number=\"#{@number.to_i}\"" if @number}>"
89
372
 
@@ -95,12 +378,131 @@ module Musa
95
378
  end
96
379
  end
97
380
 
381
+ # Musical attributes container.
382
+ #
383
+ # Attributes represents the `<attributes>` element in MusicXML, which
384
+ # contains key signatures, time signatures, clefs, and timing divisions.
385
+ # This element typically appears at the beginning of a measure to establish
386
+ # or change the musical context.
387
+ #
388
+ # ## Timing Resolution (Divisions)
389
+ #
390
+ # The divisions parameter sets the timing resolution for note durations
391
+ # in the measure. It represents how many divisions per quarter note:
392
+ #
393
+ # - **divisions=1**: Quarter note = 1 unit (limited precision)
394
+ # - **divisions=2**: Eighth notes possible
395
+ # - **divisions=4**: Sixteenth notes possible
396
+ # - **divisions=8**: Thirty-second notes possible
397
+ # - **divisions=24**: Common choice (supports triplets and quintuplets)
398
+ #
399
+ # Note durations are expressed as multiples of this division unit.
400
+ #
401
+ # ## Single-Staff vs Multi-Staff
402
+ #
403
+ # For single-staff instruments (violin, flute), one key/time/clef suffices.
404
+ # For multi-staff instruments (piano, organ, harp), different attributes
405
+ # can be specified per staff using the staff number parameter.
406
+ #
407
+ # The `<staves>` element is automatically generated based on the maximum
408
+ # number of keys, times, or clefs defined.
409
+ #
410
+ # ## Usage Styles
411
+ #
412
+ # Two equivalent approaches:
413
+ #
414
+ # **Constructor parameters** (convenient for simple cases):
415
+ #
416
+ # Attributes.new(
417
+ # divisions: 4,
418
+ # key_fifths: 0,
419
+ # time_beats: 4, time_beat_type: 4,
420
+ # clef_sign: 'G', clef_line: 2
421
+ # )
422
+ #
423
+ # **DSL with explicit elements** (flexible for multi-staff):
424
+ #
425
+ # Attributes.new do
426
+ # divisions 4
427
+ # key fifths: 0
428
+ # time beats: 4, beat_type: 4
429
+ # clef sign: 'G', line: 2
430
+ # end
431
+ #
432
+ # @example Simple single-staff attributes
433
+ # Attributes.new(
434
+ # divisions: 4,
435
+ # key_fifths: 1, # G major
436
+ # time_beats: 4, time_beat_type: 4,
437
+ # clef_sign: 'G', clef_line: 2
438
+ # )
439
+ #
440
+ # @example Piano with different keys per staff
441
+ # Attributes.new do
442
+ # divisions 4
443
+ # key 1, fifths: 0 # Treble: C major
444
+ # key 2, fifths: -1 # Bass: F major
445
+ # time beats: 3, beat_type: 4
446
+ # clef 1, sign: 'G', line: 2
447
+ # clef 2, sign: 'F', line: 4
448
+ # end
449
+ #
450
+ # @example Change key signature mid-score
451
+ # Attributes.new do
452
+ # key cancel: 2, fifths: -1 # From D major (2♯) to F major (1♭)
453
+ # end
454
+ #
455
+ # @example High timing resolution for complex rhythms
456
+ # Attributes.new(divisions: 24) # Supports triplets, quintuplets
457
+ #
458
+ # @see Key Key signature class
459
+ # @see Time Time signature class
460
+ # @see Clef Clef class
98
461
  class Attributes
99
462
  extend Musa::Extension::AttributeBuilder
100
463
  include Musa::Extension::With
101
464
 
102
465
  include Helper::ToXML
103
466
 
467
+ # Creates a musical attributes container.
468
+ #
469
+ # @param divisions [Integer, nil] timing resolution (divisions per quarter note)
470
+ # @param key_cancel [Integer, nil] accidentals to cancel from previous key
471
+ # @param key_fifths [Integer, nil] key signature (circle of fifths: -7 to +7)
472
+ # @param key_mode [String, nil] mode ('major' or 'minor')
473
+ # @param time_senza_misura [Boolean, nil] unmeasured time
474
+ # @param time_beats [Integer, nil] time signature numerator
475
+ # @param time_beat_type [Integer, nil] time signature denominator
476
+ # @param clef_sign [String, nil] clef sign ('G', 'F', 'C', 'percussion', 'TAB')
477
+ # @param clef_line [Integer, nil] clef staff line (1-5)
478
+ # @param clef_octave_change [Integer, nil] clef octave transposition
479
+ # @yield Optional DSL block for adding elements explicitly
480
+ #
481
+ # @example Constructor parameters approach
482
+ # Attributes.new(
483
+ # divisions: 4,
484
+ # key_fifths: 2, # D major
485
+ # time_beats: 6, time_beat_type: 8,
486
+ # clef_sign: 'G', clef_line: 2
487
+ # )
488
+ #
489
+ # @example DSL block approach
490
+ # Attributes.new do
491
+ # divisions 4
492
+ # key fifths: -3, mode: 'minor' # C minor
493
+ # time beats: 4, beat_type: 4
494
+ # clef sign: 'F', line: 4
495
+ # end
496
+ #
497
+ # @example Multi-staff piano
498
+ # Attributes.new do
499
+ # divisions 8
500
+ # key 1, fifths: 0
501
+ # key 2, fifths: 0
502
+ # time beats: 3, beat_type: 4
503
+ # clef 1, sign: 'G', line: 2
504
+ # clef 2, sign: 'F', line: 4
505
+ # end
104
506
  def initialize(divisions: nil,
105
507
  key_cancel: nil, key_fifths: nil, key_mode: nil,
106
508
  time_senza_misura: nil, time_beats: nil, time_beat_type: nil,
@@ -120,12 +522,90 @@ module Musa
120
522
  with &block if block_given?
121
523
  end
122
524
 
525
+ # Timing divisions builder/setter.
526
+ #
527
+ # Sets or updates the divisions per quarter note. Higher values provide
528
+ # finer timing resolution for complex rhythms.
529
+ #
530
+ # @overload divisions(value)
531
+ # Sets divisions via DSL
532
+ # @param value [Integer] divisions per quarter note
533
+ # @overload divisions=(value)
534
+ # Sets divisions via assignment
535
+ # @param value [Integer] divisions per quarter note
536
+ #
537
+ # @example
538
+ # attributes.divisions 24 # High resolution for triplets
123
539
  attr_simple_builder :divisions
124
540
 
541
+ # Adds a key signature.
542
+ #
543
+ # Multiple keys can be added for multi-staff parts where each staff
544
+ # has a different key signature.
545
+ #
546
+ # @option cancel [Integer, nil] accidentals to cancel
547
+ # @option fifths [Integer] circle of fifths position (-7 to +7)
548
+ # @option mode [String, nil] 'major' or 'minor'
549
+ # @yield Optional DSL block for configuring the key
550
+ # @return [Key] the created key signature
551
+ #
552
+ # @example Single key
553
+ # attributes.add_key(fifths: 2) # D major
554
+ #
555
+ # @example Multi-staff with different keys
556
+ # attributes.add_key(1, fifths: 0) # Treble: C major
557
+ # attributes.add_key(2, fifths: -1) # Bass: F major
125
558
  attr_complex_adder_to_array :key, Key
559
+
560
+ # Adds a time signature.
561
+ #
562
+ # Multiple time signatures can be added for multi-staff parts where
563
+ # each staff has a different meter (polyrhythm).
564
+ #
565
+ # @option senza_misura [Boolean, nil] unmeasured time
566
+ # @option beats [Integer, nil] time signature numerator
567
+ # @option beat_type [Integer, nil] time signature denominator
568
+ # @yield Optional DSL block for configuring the time signature
569
+ # @return [Time] the created time signature
570
+ #
571
+ # @example Single time signature
572
+ # attributes.add_time(beats: 4, beat_type: 4)
573
+ #
574
+ # @example Polyrhythm (different meters per staff)
575
+ # attributes.add_time(1, beats: 3, beat_type: 4) # Treble: 3/4
576
+ # attributes.add_time(2, beats: 6, beat_type: 8) # Bass: 6/8
126
577
  attr_complex_adder_to_array :time, Time
578
+
579
+ # Adds a clef.
580
+ #
581
+ # Multiple clefs are needed for multi-staff parts (piano, organ, harp).
582
+ #
583
+ # @option sign [String] clef sign ('G', 'F', 'C', 'percussion', 'TAB')
584
+ # @option line [Integer] staff line (1-5)
585
+ # @option octave_change [Integer, nil] octave transposition
586
+ # @yield Optional DSL block for configuring the clef
587
+ # @return [Clef] the created clef
588
+ #
589
+ # @example Single clef
590
+ # attributes.add_clef(sign: 'G', line: 2)
591
+ #
592
+ # @example Piano (two staves)
593
+ # attributes.add_clef(1, sign: 'G', line: 2) # Treble
594
+ # attributes.add_clef(2, sign: 'F', line: 4) # Bass
127
595
  attr_complex_adder_to_array :clef, Clef
128
596
 
597
+ # Generates the attributes XML element.
598
+ #
599
+ # Automatically determines the number of staves based on the maximum
600
+ # count of keys, times, or clefs. Outputs divisions, keys, times,
601
+ # staves count (if > 1), and clefs.
602
+ #
603
+ # @param io [IO] output stream
604
+ # @param indent [Integer] indentation level
605
+ # @param tabs [String] tab string
606
+ # @return [void]
607
+ #
608
+ # @api private
129
609
  def _to_xml(io, indent:, tabs:)
130
610
  io.puts "#{tabs}<attributes>"
131
611