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
@@ -7,9 +7,26 @@ module Musa
7
7
  module MusicXML
8
8
  module Builder
9
9
  module Internal
10
+ # Notation element helper.
11
+ #
12
+ # Private helper class for creating MusicXML notation elements with
13
+ # type attributes and optional content. Used internally for elements
14
+ # like slurs, which can be simple (type: 'start'/'stop') or complex
15
+ # (with additional attributes like placement, bezier curves, etc.).
16
+ #
17
+ # @api private
10
18
  class Notation
11
19
  include Helper::ToXML
12
20
 
21
+ # Creates a Notation from various input formats.
22
+ #
23
+ # @param tag [String] XML element tag name
24
+ # @param value_or_attributes [Symbol, String, Hash, nil] type value or hash of attributes
25
+ # @param true_value [String, nil] value to use when type is true
26
+ # @param false_value [String, nil] value to use when type is false
27
+ # @return [Notation, nil]
28
+ #
29
+ # @api private
13
30
  def self.create(tag, value_or_attributes, true_value = nil, false_value = nil)
14
31
  case value_or_attributes
15
32
  when Hash
@@ -28,6 +45,16 @@ module Musa
28
45
  end
29
46
  end
30
47
 
48
+ # Creates a notation element.
49
+ #
50
+ # @param tag [String] XML element tag name
51
+ # @param type [Symbol, String, Boolean] type attribute value
52
+ # @param content [String, nil] element text content
53
+ # @param true_value [String, nil] value to use when type is true
54
+ # @param false_value [String, nil] value to use when type is false
55
+ # @param attributes [Hash] additional XML attributes
56
+ #
57
+ # @api private
31
58
  def initialize(tag, type, content = nil, true_value = nil, false_value = nil, **attributes)
32
59
  @tag = tag
33
60
 
@@ -43,6 +70,14 @@ module Musa
43
70
  @attributes = attributes
44
71
  end
45
72
 
73
+ # Generates the notation XML element.
74
+ #
75
+ # @param io [IO] output stream
76
+ # @param indent [Integer] indentation level
77
+ # @param tabs [String] tab string
78
+ # @return [void]
79
+ #
80
+ # @api private
46
81
  def _to_xml(io, indent:, tabs:)
47
82
  io.print "#{tabs}\t<#{ @tag } "
48
83
 
@@ -62,6 +97,108 @@ module Musa
62
97
 
63
98
  private_constant :Notation
64
99
 
100
+ # Abstract base class for all note types.
101
+ #
102
+ # Note is the foundation for pitched notes, rests, and unpitched percussion notes.
103
+ # It provides comprehensive support for musical notation including:
104
+ #
105
+ # ## Core Note Properties
106
+ # - **Duration and Type**: Timing (duration in divisions, type: whole/half/quarter/etc.)
107
+ # - **Voice and Staff**: Multi-voice and multi-staff support
108
+ # - **Dots**: Dotted and double-dotted notes
109
+ # - **Grace Notes**: Ornamental notes without duration
110
+ # - **Cue Notes**: Smaller notes for reference
111
+ # - **Chords**: Notes that sound simultaneously
112
+ #
113
+ # ## Notations
114
+ # The `<notations>` element groups musical symbols attached to notes:
115
+ #
116
+ # ### Basic Notations
117
+ # - **Ties/Tied**: Visual ties connecting notes (tie) vs sustained sound (tied)
118
+ # - **Slurs**: Phrase markings
119
+ # - **Tuplets**: Irregular rhythmic groupings (triplets, quintuplets, etc.)
120
+ # - **Dynamics**: Volume markings (pp, p, mp, mf, f, ff, etc.)
121
+ # - **Fermata**: Pause/hold symbols
122
+ # - **Accidental Marks**: Sharp, flat, natural annotations
123
+ # - **Arpeggiate**: Rolled chord indication
124
+ # - **Glissando/Slide**: Pitch glide
125
+ #
126
+ # ### Articulations
127
+ # Attack and release characteristics:
128
+ # - **accent**: Emphasis (>)
129
+ # - **staccato**: Short, detached (•)
130
+ # - **tenuto**: Full value (−)
131
+ # - **staccatissimo**: Very short (▼)
132
+ # - **spiccato**: Bouncing bow
133
+ # - **strong_accent**: Forceful (^)
134
+ # - **detached_legato**: Portato
135
+ # - **breath_mark**: Breath pause
136
+ # - **caesura**: Railroad tracks (caesura)
137
+ # - Plus: doit, falloff, plop, scoop, stress, unstress
138
+ #
139
+ # ### Ornaments
140
+ # Melodic decorations:
141
+ # - **trill_mark**: Rapid alternation with upper neighbor
142
+ # - **mordent**: Single alternation with lower neighbor
143
+ # - **inverted_mordent**: Single alternation with upper neighbor
144
+ # - **turn**: Four-note figure around main note
145
+ # - **inverted_turn**: Inverted turn figure
146
+ # - **delayed_turn**: Turn after main note
147
+ # - **shake**: Extended trill
148
+ # - **tremolo**: Rapid repetition (single) or alternation (start/stop)
149
+ # - **schleifer**: Slide ornament
150
+ # - **wavy_line**: Trill extension
151
+ #
152
+ # ### Technical Markings
153
+ # Performance technique indicators:
154
+ #
155
+ # **String Instruments**:
156
+ # - **fingering**: Finger numbers
157
+ # - **up_bow/down_bow**: Bowing direction (↑/↓)
158
+ # - **harmonic**: Natural or artificial harmonics
159
+ # - **open_string**: Open string indication (○)
160
+ # - **stopped**: Stopped note (+)
161
+ # - **snap_pizzicato**: Bartók pizzicato
162
+ # - **thumb_position**: Cello thumb position
163
+ # - **string**: String number
164
+ # - **hammer_on/pull_off**: Legato technique
165
+ #
166
+ # **Wind Instruments**:
167
+ # - **double_tongue/triple_tongue**: Tonguing technique
168
+ # - **fingernails**: Use fingernails
169
+ # - **hole**: Woodwind fingering holes
170
+ #
171
+ # **Guitar/Fretted**:
172
+ # - **fret**: Fret number
173
+ # - **bend**: String bend
174
+ # - **tap**: Tapping technique
175
+ # - **pluck**: Plucking style
176
+ #
177
+ # **Other**:
178
+ # - **arrow**: Directional arrow
179
+ # - **handbell**: Handbell technique (damp, echo, gyro, etc.)
180
+ # - **heel/toe**: Organ pedal technique
181
+ #
182
+ # ## Hierarchy
183
+ #
184
+ # Note is an abstract base class with three concrete subclasses:
185
+ # - {PitchedNote}: Notes with specific pitch (step, octave, alteration)
186
+ # - {Rest}: Silences with duration
187
+ # - {UnpitchedNote}: Percussion notes without specific pitch
188
+ #
189
+ # ## Usage
190
+ #
191
+ # Note is not used directly—use {PitchedNote}, {Rest}, or {UnpitchedNote}.
192
+ # Notes are typically added via {Measure} convenience methods:
193
+ # - {Measure#add_pitch} / {Measure#pitch}
194
+ # - {Measure#add_rest} / {Measure#rest}
195
+ # - {Measure#add_unpitched} / {Measure#unpitched}
196
+ #
197
+ # @abstract Subclass and override {#specific_to_xml} to implement.
198
+ # @see PitchedNote Pitched notes with step/octave
199
+ # @see Rest Rests and measure rests
200
+ # @see UnpitchedNote Unpitched percussion
201
+ # @see Measure Container for notes
65
202
  class Note
66
203
  extend Musa::Extension::AttributeBuilder
67
204
  include Musa::Extension::With
@@ -69,6 +206,127 @@ module Musa
69
206
  include Helper
70
207
  include ToXML
71
208
 
209
+ # Creates a note (abstract base constructor).
210
+ #
211
+ # This constructor is called by subclasses ({PitchedNote}, {Rest}, {UnpitchedNote}).
212
+ # The extensive parameter list supports all MusicXML notation features.
213
+ #
214
+ # ## Parameter Categories
215
+ #
216
+ # ### Core Note Properties
217
+ # @param grace [Boolean, nil] grace note (ornamental, no time value)
218
+ # @param cue [Boolean, nil] cue note (smaller, reference indication)
219
+ # @param chord [Boolean, nil] note is part of a chord (sounds with previous note)
220
+ # @param duration [Integer, nil] duration in division units
221
+ # @param type [String, nil] note type: 'whole', 'half', 'quarter', 'eighth', '16th', etc.
222
+ # @param dots [Integer, nil] number of augmentation dots (1 or 2)
223
+ # @param voice [Integer, nil] voice number for polyphonic music
224
+ # @param staff [Integer, nil] staff number for multi-staff instruments
225
+ # @param tie_start [Boolean, nil] start a tie to next note
226
+ # @param tie_stop [Boolean, nil] stop a tie from previous note
227
+ # @param accidental [String, nil] visual accidental: 'sharp', 'flat', 'natural', etc.
228
+ # @param stem [String, nil] stem direction: 'up', 'down', 'double', 'none'
229
+ # @param pizzicato [Boolean, nil] pizzicato attribute on note element
230
+ #
231
+ # ### Rhythm Modification
232
+ # @param time_modification [TimeModification, Hash, nil] tuplet ratio (e.g., 3:2 for triplets)
233
+ # @param notehead [Notehead, Hash, nil] notehead style and properties
234
+ #
235
+ # ### Basic Notations
236
+ # @param tied [String, nil] tied notation: 'start', 'stop', 'continue'
237
+ # @param tuplet [Tuplet, Hash, nil] tuplet bracket/number notation
238
+ # @param slur [String, Hash, nil] slur: 'start', 'stop', 'continue' or hash with attributes
239
+ # @param dynamics [String, Array<String>, nil] dynamics: 'pp', 'p', 'mp', 'mf', 'f', 'ff', etc.
240
+ # @param fermata [Boolean, String, nil] fermata: true, 'upright', 'inverted'
241
+ # @param accidental_mark [String, nil] accidental in notations: 'sharp', 'flat', 'natural'
242
+ # @param arpeggiate [Boolean, String, nil] arpeggio: true, 'up', 'down'
243
+ # @param non_arpeggiate [String, nil] non-arpeggio: 'top', 'bottom'
244
+ # @param glissando [String, nil] glissando: 'start', 'stop'
245
+ # @param slide [String, nil] slide: 'start', 'stop'
246
+ #
247
+ # ### Articulations
248
+ # @param accent [Boolean, nil] accent mark (>)
249
+ # @param staccato [Boolean, nil] staccato (•)
250
+ # @param tenuto [Boolean, nil] tenuto (−)
251
+ # @param staccatissimo [Boolean, nil] staccatissimo (▼)
252
+ # @param spiccato [Boolean, nil] spiccato
253
+ # @param strong_accent [Boolean, String, nil] strong accent (^): true, 'up', 'down'
254
+ # @param detached_legato [Boolean, nil] detached legato (portato)
255
+ # @param breath_mark [Boolean, String, nil] breath mark: true, 'comma', 'tick'
256
+ # @param caesura [Boolean, nil] caesura (railroad tracks)
257
+ # @param doit [Boolean, nil] doit
258
+ # @param falloff [Boolean, nil] falloff
259
+ # @param plop [Boolean, nil] plop
260
+ # @param scoop [Boolean, nil] scoop
261
+ # @param stress [Boolean, nil] stress
262
+ # @param unstress [Boolean, nil] unstress
263
+ # @param other_articulation [String, nil] custom articulation text
264
+ #
265
+ # ### Ornaments
266
+ # @param trill_mark [Boolean, nil] trill
267
+ # @param mordent [Boolean, nil] mordent (lower neighbor)
268
+ # @param inverted_mordent [Boolean, nil] inverted mordent (upper neighbor)
269
+ # @param turn [Boolean, nil] turn
270
+ # @param inverted_turn [Boolean, nil] inverted turn
271
+ # @param delayed_turn [Boolean, nil] delayed turn
272
+ # @param delayed_inverted_turn [Boolean, nil] delayed inverted turn
273
+ # @param shake [Boolean, nil] shake
274
+ # @param tremolo [String, nil] tremolo: 'single', 'start', 'stop'
275
+ # @param schleifer [Boolean, nil] schleifer
276
+ # @param wavy_line [Boolean, nil] wavy line (trill extension)
277
+ # @param vertical_turn [Boolean, nil] vertical turn
278
+ # @param other_ornament [Boolean, nil] custom ornament
279
+ # @param ornament_accidental_mark [String, nil] ornament accidental: 'sharp', 'flat', 'natural'
280
+ #
281
+ # ### String Technique
282
+ # @param fingering [Fingering, Hash, nil] fingering indication
283
+ # @param up_bow [Boolean, nil] up bow (↑)
284
+ # @param down_bow [Boolean, nil] down bow (↓)
285
+ # @param harmonic [Harmonic, Hash, nil] harmonic
286
+ # @param open_string [Boolean, nil] open string (○)
287
+ # @param stopped [Boolean, nil] stopped note (+)
288
+ # @param snap_pizzicato [Boolean, nil] Bartók pizzicato
289
+ # @param thumb_position [Boolean, nil] cello thumb position
290
+ # @param string [Integer, nil] string number
291
+ # @param hammer_on [String, nil] hammer-on: 'start', 'stop'
292
+ # @param pull_off [String, nil] pull-off: 'start', 'stop'
293
+ #
294
+ # ### Wind Technique
295
+ # @param double_tongue [Boolean, nil] double tonguing
296
+ # @param triple_tongue [Boolean, nil] triple tonguing
297
+ # @param fingernails [Boolean, nil] use fingernails
298
+ # @param hole [Hole, Hash, nil] woodwind fingering hole
299
+ #
300
+ # ### Fretted Instrument Technique
301
+ # @param fret [Integer, nil] fret number
302
+ # @param bend [Bend, Hash, nil] string bend
303
+ # @param tap [String, nil] tapping
304
+ # @param pluck [String, nil] plucking technique
305
+ #
306
+ # ### Other Technical
307
+ # @param arrow [Arrow, Hash, nil] arrow indication
308
+ # @param handbell [String, nil] handbell technique: 'damp', 'echo', 'gyro', etc.
309
+ # @param heel [Boolean, nil] heel (organ pedal)
310
+ # @param toe [Boolean, nil] toe (organ pedal)
311
+ # @param other_technical [String, nil] custom technical text
312
+ #
313
+ # @yield Optional DSL block for setting properties
314
+ #
315
+ # @example Basic quarter note with staccato
316
+ # PitchedNote.new('C', octave: 4, duration: 2, type: 'quarter', staccato: true)
317
+ #
318
+ # @example Dotted eighth with slur start
319
+ # PitchedNote.new('D', octave: 5, duration: 3, type: 'eighth', dots: 1, slur: 'start')
320
+ #
321
+ # @example Grace note with accent
322
+ # PitchedNote.new('E', octave: 5, grace: true, type: 'eighth', accent: true)
323
+ #
324
+ # @example Multi-voice with fermata
325
+ # PitchedNote.new('G', octave: 4, duration: 4, type: 'half', voice: 2, fermata: true)
326
+ #
327
+ # For detailed parameter documentation, see {NoteComplexities::PARAMETERS}
328
+ #
329
+ # @api private (called by subclasses)
72
330
  def initialize(*_rest,
73
331
  pizzicato: nil, # true
74
332
  # main content
@@ -351,6 +609,18 @@ module Musa
351
609
  attr_simple_builder :triple_tongue
352
610
  attr_simple_builder :up_bow
353
611
 
612
+ # Generates the note XML element.
613
+ #
614
+ # Outputs a complete `<note>` element with all sub-elements in MusicXML order:
615
+ # grace, cue, chord, pitch/rest/unpitched, duration, tie, voice, type, dots,
616
+ # accidental, time_modification, stem, notehead, staff, notations.
617
+ #
618
+ # @param io [IO] output stream
619
+ # @param indent [Integer] indentation level
620
+ # @param tabs [String] tab string
621
+ # @return [void]
622
+ #
623
+ # @api private
354
624
  def _to_xml(io, indent:, tabs:)
355
625
  io.puts "#{tabs}<note#{" pizzicato=\"yes\"" if @pizzicato}>"
356
626
 
@@ -488,8 +758,23 @@ module Musa
488
758
 
489
759
  private
490
760
 
761
+ # Outputs note-type-specific XML content.
762
+ #
763
+ # Abstract method overridden by subclasses to output pitch, rest, or unpitched elements.
764
+ # Called during XML generation between chord and duration elements.
765
+ #
766
+ # @param io [IO] output stream
767
+ # @param indent [Integer] indentation level
768
+ # @return [void]
769
+ #
770
+ # @abstract Subclasses must implement this method
771
+ # @api private
491
772
  def specific_to_xml(io, indent:); end
492
773
 
774
+ # Checks if any notation elements are present.
775
+ #
776
+ # @return [Boolean] true if any notations should be output
777
+ # @api private
493
778
  def _notations
494
779
  @accidental_mark ||
495
780
  @arpeggiate ||
@@ -4,10 +4,83 @@ module Musa
4
4
  module MusicXML
5
5
  module Builder
6
6
  module Internal
7
+ # Part group for bracketing multiple parts together.
8
+ #
9
+ # PartGroup represents the `<part-group>` element in the MusicXML part-list
10
+ # section. Part groups visually bracket or brace related parts together
11
+ # (e.g., string sections, choir SATB, piano grand staff).
12
+ #
13
+ # ## Usage
14
+ #
15
+ # Part groups are defined by matching start/stop pairs with the same number:
16
+ #
17
+ # <part-group number="1" type="start">
18
+ # <group-name>Strings</group-name>
19
+ # <group-symbol>bracket</group-symbol>
20
+ # </part-group>
21
+ # <score-part id="p1">...</score-part>
22
+ # <score-part id="p2">...</score-part>
23
+ # <part-group number="1" type="stop" />
24
+ #
25
+ # ## Nesting
26
+ #
27
+ # Groups can be nested using different numbers:
28
+ #
29
+ # <part-group number="1" type="start" name="Orchestra" />
30
+ # <part-group number="2" type="start" name="Strings" />
31
+ # <score-part id="vln1" />
32
+ # <score-part id="vln2" />
33
+ # <part-group number="2" type="stop" />
34
+ # <part-group number="1" type="stop" />
35
+ #
36
+ # ## Symbols
37
+ #
38
+ # Common bracket symbols:
39
+ # - **bracket**: Standard square bracket
40
+ # - **brace**: Curly brace (for piano, organ)
41
+ # - **line**: Simple vertical line
42
+ # - **square**: Square bracket (rare)
43
+ #
44
+ # @example String quartet grouping
45
+ # group_start = PartGroup.new(1,
46
+ # type: 'start',
47
+ # name: "String Quartet",
48
+ # symbol: 'bracket'
49
+ # )
50
+ # # ... add parts vln1, vln2, vla, vlc ...
51
+ # group_stop = PartGroup.new(1, type: 'stop')
52
+ #
53
+ # @example Piano grand staff
54
+ # PartGroup.new(1,
55
+ # type: 'start',
56
+ # symbol: 'brace',
57
+ # group_barline: true
58
+ # )
59
+ # # ... add parts for right hand and left hand ...
60
+ # PartGroup.new(1, type: 'stop')
7
61
  class PartGroup
8
62
  include Helper
9
63
  include Helper::HeaderToXML
10
64
 
65
+ # Creates a part group declaration.
66
+ #
67
+ # @param number [Integer, nil] group number for matching start/stop pairs
68
+ # @param type [String] 'start' or 'stop'
69
+ # @param name [String, nil] group name displayed on bracket
70
+ # @param abbreviation [String, nil] abbreviated group name
71
+ # @param symbol [String, nil] bracket type: 'bracket', 'brace', 'line', 'square'
72
+ # @param group_barline [Boolean, String, nil] whether barlines connect across group
73
+ # @param group_time [Boolean, String, nil] whether time signatures are shared
74
+ #
75
+ # @example Start a bracket group
76
+ # PartGroup.new(1,
77
+ # type: 'start',
78
+ # name: "Woodwinds",
79
+ # symbol: 'bracket'
80
+ # )
81
+ #
82
+ # @example Stop a group
83
+ # PartGroup.new(1, type: 'stop')
11
84
  def initialize(number = nil, # number
12
85
  type:,
13
86
  name: nil,
@@ -24,8 +97,42 @@ module Musa
24
97
  @group_time = group_time
25
98
  end
26
99
 
27
- attr_accessor :number, :type, :name, :abbreviation, :symbol, :group_barline, :group_time
100
+ # Group number (for matching start/stop pairs).
101
+ # @return [Integer, nil]
102
+ attr_accessor :number
28
103
 
104
+ # Type: 'start' or 'stop'.
105
+ # @return [String]
106
+ attr_accessor :type
107
+
108
+ # Group name displayed on bracket.
109
+ # @return [String, nil]
110
+ attr_accessor :name
111
+
112
+ # Abbreviated group name.
113
+ # @return [String, nil]
114
+ attr_accessor :abbreviation
115
+
116
+ # Bracket symbol type.
117
+ # @return [String, nil]
118
+ attr_accessor :symbol
119
+
120
+ # Whether barlines connect across the group.
121
+ # @return [Boolean, String, nil]
122
+ attr_accessor :group_barline
123
+
124
+ # Whether time signatures are shared.
125
+ # @return [Boolean, String, nil]
126
+ attr_accessor :group_time
127
+
128
+ # Generates the part-group XML element for the part-list section.
129
+ #
130
+ # @param io [IO] output stream
131
+ # @param indent [Integer] indentation level
132
+ # @param tabs [String] tab string
133
+ # @return [void]
134
+ #
135
+ # @api private
29
136
  def _header_to_xml(io, indent:, tabs:)
30
137
  io.puts "#{tabs}<part-group#{ decode_bool_or_string_attribute(@number&.to_i, 'number') } type=\"#{@type}\">"
31
138
 
@@ -8,6 +8,43 @@ module Musa
8
8
  module MusicXML
9
9
  module Builder
10
10
  module Internal
11
+ # Individual part (instrument/voice) in a score.
12
+ #
13
+ # Part represents a single instrument or voice in the score, containing
14
+ # a sequence of measures with musical content. Each part has a unique
15
+ # identifier, full name, and optional abbreviation.
16
+ #
17
+ # ## Structure
18
+ #
19
+ # A part contains:
20
+ # - **Header**: Declared in `<part-list>` with `<score-part>`
21
+ # - **Content**: Sequence of `<measure>` elements with notes, dynamics, etc.
22
+ #
23
+ # ## Usage
24
+ #
25
+ # Parts are typically created via {Musa::MusicXML::Builder::ScorePartwise#part} or {Musa::MusicXML::Builder::ScorePartwise#add_part}.
26
+ # Measures are added sequentially, automatically numbered starting from 1.
27
+ #
28
+ # @example Creating a part with measures
29
+ # part = Part.new(:p1, name: "Violin I", abbreviation: "Vln. I") do
30
+ # measure do
31
+ # attributes do
32
+ # divisions 4
33
+ # key fifths: 1 # G major
34
+ # time beats: 4, beat_type: 4
35
+ # clef sign: 'G', line: 2
36
+ # end
37
+ # pitch 'D', octave: 5, duration: 4, type: 'quarter'
38
+ # end
39
+ #
40
+ # measure do
41
+ # pitch 'E', octave: 5, duration: 4, type: 'quarter'
42
+ # pitch 'F', octave: 5, duration: 4, type: 'quarter', alter: 1
43
+ # end
44
+ # end
45
+ #
46
+ # @see Measure Measure implementation
47
+ # @see ScorePartwise#part Main way to create parts
11
48
  class Part
12
49
  extend Musa::Extension::AttributeBuilder
13
50
  include Musa::Extension::With
@@ -15,6 +52,31 @@ module Musa
15
52
  include Helper::HeaderToXML
16
53
  include Helper::ToXML
17
54
 
55
+ # Creates a new part.
56
+ #
57
+ # @param id [Symbol, String] unique part identifier (referenced in `<part>` elements)
58
+ # @param name [String] full part name (e.g., "Violin I", "Piano")
59
+ # @param abbreviation [String, nil] abbreviated name for subsequent systems
60
+ # @param first_measure_attributes [Hash] optional attributes for auto-created first measure
61
+ # @yield Optional DSL block for adding measures
62
+ #
63
+ # @example With abbreviation
64
+ # Part.new(:p1, name: "Violoncello", abbreviation: "Vc.")
65
+ #
66
+ # @example With first measure attributes
67
+ # Part.new(:p1,
68
+ # name: "Flute",
69
+ # divisions: 4,
70
+ # key_fifths: 0,
71
+ # time_beats: 3, time_beat_type: 4
72
+ # )
73
+ #
74
+ # @example With DSL block
75
+ # Part.new(:p1, name: "Clarinet") do
76
+ # measure do
77
+ # # measure content
78
+ # end
79
+ # end
18
80
  def initialize(id, name:, abbreviation: nil, **first_measure_attributes, &block)
19
81
  @id = id
20
82
  @name = name
@@ -29,10 +91,67 @@ module Musa
29
91
  with &block if block_given?
30
92
  end
31
93
 
94
+ # Part ID builder/setter.
95
+ #
96
+ # @overload id(value)
97
+ # Sets part ID via DSL
98
+ # @param value [Symbol, String] part identifier
99
+ # @overload id=(value)
100
+ # Sets part ID via assignment
101
+ # @param value [Symbol, String] part identifier
32
102
  attr_simple_builder :id
103
+
104
+ # Part name builder/setter.
105
+ #
106
+ # @overload name(value)
107
+ # Sets part name via DSL
108
+ # @param value [String] part name
109
+ # @overload name=(value)
110
+ # Sets part name via assignment
111
+ # @param value [String] part name
33
112
  attr_simple_builder :name
113
+
114
+ # Part abbreviation builder/setter.
115
+ #
116
+ # @overload abbreviation(value)
117
+ # Sets abbreviation via DSL
118
+ # @param value [String] abbreviated name
119
+ # @overload abbreviation=(value)
120
+ # Sets abbreviation via assignment
121
+ # @param value [String] abbreviated name
34
122
  attr_simple_builder :abbreviation
35
123
 
124
+ # Adds a measure to the part.
125
+ #
126
+ # Measures are automatically numbered sequentially starting from 1.
127
+ # The first measure typically contains attributes (key, time, clef, divisions).
128
+ #
129
+ # @option divisions [Integer, nil] divisions per quarter note (timing resolution)
130
+ # @option key_cancel [Integer, nil] key cancellation
131
+ # @option key_fifths [Integer, nil] key signature (circle of fifths: -7 to +7)
132
+ # @option key_mode [String, nil] mode (major/minor)
133
+ # @option time_senza_misura [Boolean, nil] unmeasured time
134
+ # @option time_beats [Integer, nil] time signature numerator
135
+ # @option time_beat_type [Integer, nil] time signature denominator
136
+ # @option clef_sign [String, nil] clef sign (G/F/C)
137
+ # @option clef_line [Integer, nil] clef line number
138
+ # @option clef_octave_change [Integer, nil] octave transposition
139
+ # @yield Optional DSL block for measure content
140
+ # @return [Measure] the created measure
141
+ #
142
+ # @example First measure with attributes
143
+ # part.add_measure(
144
+ # divisions: 4,
145
+ # key_fifths: 2, # D major
146
+ # time_beats: 4, time_beat_type: 4,
147
+ # clef_sign: 'G', clef_line: 2
148
+ # )
149
+ #
150
+ # @example Measure with DSL block
151
+ # part.measure do
152
+ # pitch 'C', octave: 4, duration: 4, type: 'quarter'
153
+ # rest duration: 4, type: 'quarter'
154
+ # end
36
155
  attr_complex_adder_to_custom :measure, variable: :@measures do
37
156
  | divisions: nil,
38
157
  key_cancel: nil, key_fifths: nil, key_mode: nil,
@@ -46,6 +165,16 @@ module Musa
46
165
  clef_sign: clef_sign, clef_line: clef_line, clef_octave_change: clef_octave_change).tap { |measure| @measures << measure }
47
166
  end
48
167
 
168
+ # Generates the part declaration for the part-list section.
169
+ #
170
+ # Creates a `<score-part>` element with part name and optional abbreviation.
171
+ #
172
+ # @param io [IO] output stream
173
+ # @param indent [Integer] indentation level
174
+ # @param tabs [String] tab string
175
+ # @return [void]
176
+ #
177
+ # @api private
49
178
  def _header_to_xml(io, indent:, tabs:)
50
179
  io.puts "#{tabs}<score-part id=\"#{@id}\">"
51
180
  io.puts "#{tabs}\t<part-name>#{@name}</part-name>"
@@ -53,6 +182,16 @@ module Musa
53
182
  io.puts "#{tabs}</score-part>"
54
183
  end
55
184
 
185
+ # Generates the part content with all measures.
186
+ #
187
+ # Creates a `<part>` element containing all measures in sequence.
188
+ #
189
+ # @param io [IO] output stream
190
+ # @param indent [Integer] indentation level
191
+ # @param tabs [String] tab string
192
+ # @return [void]
193
+ #
194
+ # @api private
56
195
  def _to_xml(io, indent:, tabs:)
57
196
  io.puts "#{tabs}<part id=\"#{@id}\">"
58
197
  @measures.each do |measure|