musa-dsl 0.30.2 → 0.41.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 +5 -1
- data/.version +6 -0
- data/.yardopts +7 -0
- data/Gemfile +0 -1
- 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 +544 -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 +215 -1
- data/lib/musa-dsl/generative/generative-grammar.rb +387 -0
- data/lib/musa-dsl/generative/markov.rb +135 -3
- data/lib/musa-dsl/generative/rules.rb +312 -4
- data/lib/musa-dsl/generative/variatio.rb +286 -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 +113 -2
- data/lib/musa-dsl/midi/midi-voices.rb +275 -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 +353 -2
- data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +70 -206
- data/lib/musa-dsl/music/scale_kinds/bebop/bebop_dominant_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/bebop/bebop_major_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/bebop/bebop_minor_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/blues/blues_major_scale_kind.rb +100 -0
- data/lib/musa-dsl/music/scale_kinds/blues/blues_scale_kind.rb +99 -0
- data/lib/musa-dsl/music/scale_kinds/chromatic_scale_kind.rb +79 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/double_harmonic_scale_kind.rb +102 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/hungarian_minor_scale_kind.rb +102 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/neapolitan_major_scale_kind.rb +102 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/neapolitan_minor_scale_kind.rb +101 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/phrygian_dominant_scale_kind.rb +103 -0
- data/lib/musa-dsl/music/scale_kinds/harmonic_major/harmonic_major_scale_kind.rb +104 -0
- data/lib/musa-dsl/music/scale_kinds/major_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/altered_scale_kind.rb +106 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/dorian_b2_scale_kind.rb +104 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/locrian_sharp2_scale_kind.rb +103 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/lydian_augmented_scale_kind.rb +103 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/lydian_dominant_scale_kind.rb +106 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/melodic_minor_scale_kind.rb +104 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/mixolydian_b6_scale_kind.rb +103 -0
- data/lib/musa-dsl/music/scale_kinds/minor_harmonic_scale_kind.rb +125 -0
- data/lib/musa-dsl/music/scale_kinds/minor_natural_scale_kind.rb +123 -0
- data/lib/musa-dsl/music/scale_kinds/modes/dorian_scale_kind.rb +111 -0
- data/lib/musa-dsl/music/scale_kinds/modes/locrian_scale_kind.rb +114 -0
- data/lib/musa-dsl/music/scale_kinds/modes/lydian_scale_kind.rb +111 -0
- data/lib/musa-dsl/music/scale_kinds/modes/mixolydian_scale_kind.rb +111 -0
- data/lib/musa-dsl/music/scale_kinds/modes/phrygian_scale_kind.rb +111 -0
- data/lib/musa-dsl/music/scale_kinds/pentatonic/pentatonic_major_scale_kind.rb +93 -0
- data/lib/musa-dsl/music/scale_kinds/pentatonic/pentatonic_minor_scale_kind.rb +99 -0
- data/lib/musa-dsl/music/scale_kinds/symmetric/diminished_hw_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/symmetric/diminished_wh_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/symmetric/whole_tone_scale_kind.rb +99 -0
- data/lib/musa-dsl/music/scale_systems/equally_tempered_12_tone_scale_system.rb +80 -0
- data/lib/musa-dsl/music/scale_systems/twelve_semitones_scale_system.rb +60 -0
- data/lib/musa-dsl/music/scales.rb +1384 -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 +54 -0
- data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +64 -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 +57 -7
- data/lib/musa-dsl/series/queue-serie.rb +78 -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 +2 -1
- data/lib/musa-dsl.rb +132 -25
- data/musa-dsl.gemspec +25 -18
- metadata +158 -16
|
@@ -5,6 +5,136 @@ require_relative 'pdv'
|
|
|
5
5
|
require_relative 'helper'
|
|
6
6
|
|
|
7
7
|
module Musa::Datasets
|
|
8
|
+
# Score-style musical events with scale degrees.
|
|
9
|
+
#
|
|
10
|
+
# GDV (Grade/Duration/Velocity) represents musical events using score notation
|
|
11
|
+
# with scale degrees, octaves, and dynamics. Extends {AbsD} for duration support.
|
|
12
|
+
#
|
|
13
|
+
# ## Purpose
|
|
14
|
+
#
|
|
15
|
+
# GDV is the score representation layer of the dataset framework:
|
|
16
|
+
#
|
|
17
|
+
# - Uses scale degrees (grade) instead of absolute pitches
|
|
18
|
+
# - Uses dynamics markings (velocity -5 to +4) instead of MIDI velocity
|
|
19
|
+
# - Human-readable and musically meaningful
|
|
20
|
+
# - Independent of specific tuning or scale
|
|
21
|
+
#
|
|
22
|
+
# Contrast with {PDV} which uses MIDI absolute pitches and velocities.
|
|
23
|
+
#
|
|
24
|
+
# ## Natural Keys
|
|
25
|
+
#
|
|
26
|
+
# - **:grade**: Scale degree (integer, 0-based)
|
|
27
|
+
# - **:sharps**: Chromatic alteration (integer, positive = sharp, negative = flat)
|
|
28
|
+
# - **:octave**: Octave offset (integer, 0 = base octave)
|
|
29
|
+
# - **:velocity**: Dynamics (-3 to +4, where 0 = mp, 1 = mf)
|
|
30
|
+
# - **:silence**: Indicates rest (boolean or symbol)
|
|
31
|
+
# - **:duration**: Event duration (from {AbsD})
|
|
32
|
+
# - **:note_duration**, **:forward_duration**: Additional duration keys (from {AbsD})
|
|
33
|
+
#
|
|
34
|
+
# ## Pitch Representation
|
|
35
|
+
#
|
|
36
|
+
# Pitches are specified as:
|
|
37
|
+
#
|
|
38
|
+
# - **grade**: Position in scale (0 = first note, 1 = second note, etc.)
|
|
39
|
+
# - **octave**: Octave offset (0 = base, 1 = up one octave, -1 = down one octave)
|
|
40
|
+
# - **sharps**: Chromatic alteration (1 = sharp, -1 = flat, 2 = double sharp, etc.)
|
|
41
|
+
#
|
|
42
|
+
# Example in C major scale:
|
|
43
|
+
#
|
|
44
|
+
# - C4 = { grade: 0, octave: 0 }
|
|
45
|
+
# - D4 = { grade: 1, octave: 0 }
|
|
46
|
+
# - C5 = { grade: 0, octave: 1 }
|
|
47
|
+
# - C#4 = { grade: 0, octave: 0, sharps: 1 }
|
|
48
|
+
#
|
|
49
|
+
# ## Velocity (Dynamics)
|
|
50
|
+
#
|
|
51
|
+
# Velocity represents musical dynamics in range -3 to +4:
|
|
52
|
+
#
|
|
53
|
+
# -3: ppp (pianississimo)
|
|
54
|
+
# -2: pp (pianissimo)
|
|
55
|
+
# -1: p (piano)
|
|
56
|
+
# 0: mp (mezzo-piano)
|
|
57
|
+
# +1: mf (mezzo-forte)
|
|
58
|
+
# +2: f (forte)
|
|
59
|
+
# +3: ff (fortissimo)
|
|
60
|
+
# +4: fff (fortississimo)
|
|
61
|
+
#
|
|
62
|
+
# ## Conversions
|
|
63
|
+
#
|
|
64
|
+
# ### To PDV (MIDI)
|
|
65
|
+
#
|
|
66
|
+
# Converts score notation to MIDI using a scale:
|
|
67
|
+
#
|
|
68
|
+
# gdv = { grade: 0, octave: 0, duration: 1.0, velocity: 0 }.extend(GDV)
|
|
69
|
+
# scale = Musa::Scales::Scales.et12[440.0].major[60]
|
|
70
|
+
# pdv = gdv.to_pdv(scale)
|
|
71
|
+
# # => { pitch: 60, duration: 1.0, velocity: 64 }
|
|
72
|
+
#
|
|
73
|
+
# ### To GDVd (Delta Encoding)
|
|
74
|
+
#
|
|
75
|
+
# Converts to delta encoding for efficient storage:
|
|
76
|
+
#
|
|
77
|
+
# gdv1 = { grade: 0, octave: 0, duration: 1.0, velocity: 0 }.extend(GDV)
|
|
78
|
+
# gdv2 = { grade: 2, octave: 0, duration: 1.0, velocity: 1 }.extend(GDV)
|
|
79
|
+
# gdvd = gdv2.to_gdvd(scale, previous: gdv1)
|
|
80
|
+
# # => { delta_grade: 2, delta_velocity: 1 }
|
|
81
|
+
#
|
|
82
|
+
# ### To Neuma Notation
|
|
83
|
+
#
|
|
84
|
+
# Converts to Neuma string format for serialization:
|
|
85
|
+
#
|
|
86
|
+
# gdv = { grade: 0, duration: 1.0, velocity: 0 }.extend(GDV)
|
|
87
|
+
# gdv.base_duration = 1/4r
|
|
88
|
+
# gdv.to_neuma # => "(0 4 mp)"
|
|
89
|
+
#
|
|
90
|
+
# ## MIDI Velocity Mapping
|
|
91
|
+
#
|
|
92
|
+
# Dynamics are mapped to MIDI velocities using interpolation:
|
|
93
|
+
#
|
|
94
|
+
# -3 (ppp) → 16
|
|
95
|
+
# -2 (pp) → 33
|
|
96
|
+
# -1 (p) → 49
|
|
97
|
+
# 0 (mp) → 64
|
|
98
|
+
# +1 (mf) → 80
|
|
99
|
+
# +2 (f) → 96
|
|
100
|
+
# +3 (ff) → 112
|
|
101
|
+
# +4 (fff) → 127
|
|
102
|
+
#
|
|
103
|
+
# @example Basic score event
|
|
104
|
+
# gdv = { grade: 0, octave: 0, duration: 1.0, velocity: 0 }.extend(Musa::Datasets::GDV)
|
|
105
|
+
# gdv.base_duration = 1/4r
|
|
106
|
+
# # First scale degree, base octave, 1 beat, mp dynamics
|
|
107
|
+
#
|
|
108
|
+
# @example Chromatic alteration
|
|
109
|
+
# gdv = { grade: 0, octave: 0, sharps: 1, duration: 1.0 }.extend(GDV)
|
|
110
|
+
# # First scale degree sharp (C# in C major)
|
|
111
|
+
#
|
|
112
|
+
# @example Silence (rest)
|
|
113
|
+
# gdv = { grade: :silence, duration: 1.0 }.extend(GDV)
|
|
114
|
+
# # Rest for 1 beat
|
|
115
|
+
#
|
|
116
|
+
# @example Convert to MIDI
|
|
117
|
+
# gdv = { grade: 0, octave: 0, duration: 1.0, velocity: 0 }.extend(GDV)
|
|
118
|
+
# scale = Musa::Scales::Scales.et12[440.0].major[60]
|
|
119
|
+
# pdv = gdv.to_pdv(scale)
|
|
120
|
+
# # => { pitch: 60, duration: 1.0, velocity: 64 }
|
|
121
|
+
#
|
|
122
|
+
# @example Convert to delta encoding
|
|
123
|
+
# gdv1 = { grade: 0, duration: 1.0, velocity: 0 }.extend(GDV)
|
|
124
|
+
# gdv2 = { grade: 2, duration: 1.0, velocity: 1 }.extend(GDV)
|
|
125
|
+
# scale = Musa::Scales::Scales.et12[440.0].major[60]
|
|
126
|
+
# gdvd = gdv2.to_gdvd(scale, previous: gdv1)
|
|
127
|
+
# # => { delta_grade: 2, delta_velocity: 1 }
|
|
128
|
+
#
|
|
129
|
+
# @example Convert to Neuma notation
|
|
130
|
+
# gdv = { grade: 0, octave: 1, duration: 1.0, velocity: 2 }.extend(GDV)
|
|
131
|
+
# gdv.base_duration = 1/4r
|
|
132
|
+
# gdv.to_neuma # => "(0 o1 4 ff)"
|
|
133
|
+
#
|
|
134
|
+
# @see PDV MIDI-style representation
|
|
135
|
+
# @see GDVd Delta encoding
|
|
136
|
+
# @see AbsD Absolute duration events
|
|
137
|
+
# @see Helper String formatting utilities
|
|
8
138
|
module GDV
|
|
9
139
|
using Musa::Extension::InspectNice
|
|
10
140
|
|
|
@@ -12,14 +142,60 @@ module Musa::Datasets
|
|
|
12
142
|
|
|
13
143
|
include Helper
|
|
14
144
|
|
|
145
|
+
# Natural keys for score events.
|
|
146
|
+
# @return [Array<Symbol>]
|
|
15
147
|
NaturalKeys = (NaturalKeys + [:grade, :sharps, :octave, :velocity, :silence]).freeze
|
|
16
148
|
|
|
149
|
+
# Base duration for time calculations.
|
|
150
|
+
# @return [Rational]
|
|
17
151
|
attr_accessor :base_duration
|
|
18
152
|
|
|
153
|
+
# MIDI velocity mapping for dynamics.
|
|
154
|
+
#
|
|
155
|
+
# Maps dynamics values (-5 to +4) to MIDI velocities (0-127).
|
|
156
|
+
# Used for interpolation in {#to_pdv}.
|
|
157
|
+
#
|
|
158
|
+
# @return [Array<Integer>] MIDI velocity breakpoints
|
|
159
|
+
# @api private
|
|
19
160
|
# TODO create a customizable MIDI velocity to score dynamics bidirectional conversor
|
|
20
161
|
# ppp = 16 ... fff = 127 (-5 ... 4) the standard used by Musescore 3 and others starts at ppp = 16
|
|
21
162
|
VELOCITY_MAP = [1, 8, 16, 33, 49, 64, 80, 96, 112, 127].freeze
|
|
22
163
|
|
|
164
|
+
# Converts to PDV (MIDI representation).
|
|
165
|
+
#
|
|
166
|
+
# Translates score notation to MIDI using a scale:
|
|
167
|
+
# - Scale degree → MIDI pitch (via scale lookup)
|
|
168
|
+
# - Dynamics → MIDI velocity (via interpolation)
|
|
169
|
+
# - Duration values copied
|
|
170
|
+
# - Additional keys preserved
|
|
171
|
+
#
|
|
172
|
+
# @param scale [Musa::Scales::Scale] reference scale for pitch conversion
|
|
173
|
+
#
|
|
174
|
+
# @return [PDV] MIDI representation dataset
|
|
175
|
+
#
|
|
176
|
+
# @example Basic conversion
|
|
177
|
+
# gdv = { grade: 0, octave: 0, duration: 1.0, velocity: 0 }.extend(GDV)
|
|
178
|
+
# scale = Musa::Scales::Scales.et12[440.0].major[60]
|
|
179
|
+
# pdv = gdv.to_pdv(scale)
|
|
180
|
+
# # => { pitch: 60, duration: 1.0, velocity: 64 }
|
|
181
|
+
#
|
|
182
|
+
# @example Chromatic note
|
|
183
|
+
# gdv = { grade: 0, octave: 0, sharps: 1, duration: 1.0 }.extend(GDV)
|
|
184
|
+
# scale = Musa::Scales::Scales.et12[440.0].major[60]
|
|
185
|
+
# pdv = gdv.to_pdv(scale)
|
|
186
|
+
# # => { pitch: 61, duration: 1.0 }
|
|
187
|
+
#
|
|
188
|
+
# @example Silence
|
|
189
|
+
# gdv = { grade: :silence, duration: 1.0 }.extend(GDV)
|
|
190
|
+
# scale = Musa::Scales::Scales.et12[440.0].major[60]
|
|
191
|
+
# pdv = gdv.to_pdv(scale)
|
|
192
|
+
# # => { pitch: :silence, duration: 1.0 }
|
|
193
|
+
#
|
|
194
|
+
# @example Dynamics interpolation
|
|
195
|
+
# gdv = { grade: 0, velocity: 0.5 }.extend(GDV)
|
|
196
|
+
# scale = Musa::Scales::Scales.et12[440.0].major[60]
|
|
197
|
+
# pdv = gdv.to_pdv(scale)
|
|
198
|
+
# # velocity 0.5 interpolates between mf (64) and f (80)
|
|
23
199
|
def to_pdv(scale)
|
|
24
200
|
pdv = {}.extend PDV
|
|
25
201
|
pdv.base_duration = @base_duration
|
|
@@ -50,7 +226,7 @@ module Musa::Datasets
|
|
|
50
226
|
else
|
|
51
227
|
self[:velocity] < -5 ? -5 : 4
|
|
52
228
|
end
|
|
53
|
-
|
|
229
|
+
|
|
54
230
|
index_min = index.floor
|
|
55
231
|
index_max = index.ceil
|
|
56
232
|
|
|
@@ -65,6 +241,51 @@ module Musa::Datasets
|
|
|
65
241
|
pdv
|
|
66
242
|
end
|
|
67
243
|
|
|
244
|
+
# Converts to Neuma notation string.
|
|
245
|
+
#
|
|
246
|
+
# Neuma is a compact text format for score notation. Format:
|
|
247
|
+
#
|
|
248
|
+
# (grade[sharps] [octave] [duration] [velocity] [modifiers...])
|
|
249
|
+
#
|
|
250
|
+
# - **grade**: Scale degree number (0, 1, 2...) or 'silence' for rests
|
|
251
|
+
# - **sharps**: '#' for sharp, '_' for flat (e.g., "0#" = first degree sharp)
|
|
252
|
+
# - **octave**: 'o' + number (e.g., "o1" = up one octave, "o-1" = down one)
|
|
253
|
+
# - **duration**: Number of base_duration units
|
|
254
|
+
# - **velocity**: Dynamics string (ppp, pp, p, mp, mf, f, ff, fff)
|
|
255
|
+
# - **modifiers**: Additional key-value pairs (e.g., "staccato")
|
|
256
|
+
#
|
|
257
|
+
# @return [String] Neuma notation
|
|
258
|
+
#
|
|
259
|
+
# @example Basic note
|
|
260
|
+
# gdv = { grade: 0, duration: 1.0, velocity: 0 }.extend(GDV)
|
|
261
|
+
# gdv.base_duration = 1/4r
|
|
262
|
+
# gdv.to_neuma # => "(0 4 mf)"
|
|
263
|
+
# # grade 0, duration 4 quarters, mf dynamics
|
|
264
|
+
#
|
|
265
|
+
# @example With octave
|
|
266
|
+
# gdv = { grade: 2, octave: 1, duration: 0.5, velocity: 2 }.extend(GDV)
|
|
267
|
+
# gdv.base_duration = 1/4r
|
|
268
|
+
# gdv.to_neuma # => "(2 o1 2 ff)"
|
|
269
|
+
#
|
|
270
|
+
# @example Sharp note
|
|
271
|
+
# gdv = { grade: 0, sharps: 1, duration: 1.0 }.extend(GDV)
|
|
272
|
+
# gdv.base_duration = 1/4r
|
|
273
|
+
# gdv.to_neuma # => "(0# 4)"
|
|
274
|
+
#
|
|
275
|
+
# @example Flat note
|
|
276
|
+
# gdv = { grade: 1, sharps: -1, duration: 1.0 }.extend(GDV)
|
|
277
|
+
# gdv.base_duration = 1/4r
|
|
278
|
+
# gdv.to_neuma # => "(1_ 4)"
|
|
279
|
+
#
|
|
280
|
+
# @example Silence
|
|
281
|
+
# gdv = { grade: :silence, duration: 1.0 }.extend(GDV)
|
|
282
|
+
# gdv.base_duration = 1/4r
|
|
283
|
+
# gdv.to_neuma # => "(silence 4)"
|
|
284
|
+
#
|
|
285
|
+
# @example With modifiers
|
|
286
|
+
# gdv = { grade: 0, duration: 1.0, staccato: true }.extend(GDV)
|
|
287
|
+
# gdv.base_duration = 1/4r
|
|
288
|
+
# gdv.to_neuma # => "(0 4 staccato)"
|
|
68
289
|
def to_neuma
|
|
69
290
|
@base_duration ||= Rational(1, 4)
|
|
70
291
|
|
|
@@ -80,7 +301,7 @@ module Musa::Datasets
|
|
|
80
301
|
if self[:sharps] > 0
|
|
81
302
|
attributes[c] += '#' * self[:sharps]
|
|
82
303
|
elsif self[:sharps] < 0
|
|
83
|
-
attributes[c] += '_' * self[:sharps]
|
|
304
|
+
attributes[c] += '_' * self[:sharps].abs
|
|
84
305
|
end
|
|
85
306
|
end
|
|
86
307
|
end
|
|
@@ -98,12 +319,68 @@ module Musa::Datasets
|
|
|
98
319
|
'(' + attributes.join(' ') + ')'
|
|
99
320
|
end
|
|
100
321
|
|
|
322
|
+
# Converts velocity number to dynamics string.
|
|
323
|
+
#
|
|
324
|
+
# Maps numeric velocity (-3 to +4) to standard dynamics markings.
|
|
325
|
+
#
|
|
326
|
+
# @param x [Integer] velocity value
|
|
327
|
+
# @return [String] dynamics marking
|
|
328
|
+
#
|
|
329
|
+
# @example
|
|
330
|
+
# velocity_of(-3) # => "ppp"
|
|
331
|
+
# velocity_of(0) # => "mp"
|
|
332
|
+
# velocity_of(1) # => "mf"
|
|
333
|
+
# velocity_of(4) # => "fff"
|
|
334
|
+
#
|
|
335
|
+
# @api private
|
|
101
336
|
def velocity_of(x)
|
|
102
337
|
%w[ppp pp p mp mf f ff fff][x + 3]
|
|
103
338
|
end
|
|
104
339
|
|
|
105
340
|
private :velocity_of
|
|
106
341
|
|
|
342
|
+
# Converts to GDVd (delta encoding).
|
|
343
|
+
#
|
|
344
|
+
# Creates delta-encoded representation relative to a previous event.
|
|
345
|
+
# Only changed values are included, making the representation compact.
|
|
346
|
+
#
|
|
347
|
+
# Without previous event (first in sequence):
|
|
348
|
+
# - Uses abs_ keys for all values
|
|
349
|
+
#
|
|
350
|
+
# With previous event:
|
|
351
|
+
# - Uses delta_ keys for changed values
|
|
352
|
+
# - Omits unchanged values
|
|
353
|
+
# - Uses abs_ keys when changing from nil to value
|
|
354
|
+
#
|
|
355
|
+
# @param scale [Musa::Scales::Scale] reference scale for grade calculation
|
|
356
|
+
# @param previous [GDV, nil] previous event for delta calculation
|
|
357
|
+
#
|
|
358
|
+
# @return [GDVd] delta-encoded dataset
|
|
359
|
+
#
|
|
360
|
+
# @example First event (no previous)
|
|
361
|
+
# gdv = { grade: 0, duration: 1.0, velocity: 0 }.extend(GDV)
|
|
362
|
+
# gdvd = gdv.to_gdvd(scale)
|
|
363
|
+
# # => { abs_grade: 0, abs_duration: 1.0, abs_velocity: 0 }
|
|
364
|
+
#
|
|
365
|
+
# @example Changed values
|
|
366
|
+
# gdv1 = { grade: 0, duration: 1.0, velocity: 0 }.extend(GDV)
|
|
367
|
+
# gdv2 = { grade: 2, duration: 1.0, velocity: 1 }.extend(GDV)
|
|
368
|
+
# gdvd = gdv2.to_gdvd(scale, previous: gdv1)
|
|
369
|
+
# # => { delta_grade: 2, delta_velocity: 1 }
|
|
370
|
+
# # duration unchanged, so omitted
|
|
371
|
+
#
|
|
372
|
+
# @example Unchanged values
|
|
373
|
+
# gdv1 = { grade: 0, duration: 1.0, velocity: 0 }.extend(GDV)
|
|
374
|
+
# gdv2 = { grade: 0, duration: 1.0, velocity: 0 }.extend(GDV)
|
|
375
|
+
# gdvd = gdv2.to_gdvd(scale, previous: gdv1)
|
|
376
|
+
# # => {}
|
|
377
|
+
# # Everything unchanged
|
|
378
|
+
#
|
|
379
|
+
# @example Chromatic alteration
|
|
380
|
+
# gdv1 = { grade: 0, octave: 0 }.extend(GDV)
|
|
381
|
+
# gdv2 = { grade: 0, octave: 0, sharps: 1 }.extend(GDV)
|
|
382
|
+
# gdvd = gdv2.to_gdvd(scale, previous: gdv1)
|
|
383
|
+
# # => { delta_sharps: 1 }
|
|
107
384
|
def to_gdvd(scale, previous: nil)
|
|
108
385
|
gdvd = {}.extend GDVd
|
|
109
386
|
gdvd.base_duration = @base_duration
|
|
@@ -5,6 +5,117 @@ require_relative 'helper'
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
module Musa::Datasets
|
|
8
|
+
# Delta-encoded score events for efficient compression.
|
|
9
|
+
#
|
|
10
|
+
# GDVd (Grade/Duration/Velocity delta) represents musical events using
|
|
11
|
+
# delta encoding - storing only changes from previous events.
|
|
12
|
+
# Extends {DeltaD} for flexible duration encoding and {DeltaI} for indexed deltas.
|
|
13
|
+
#
|
|
14
|
+
# ## Purpose
|
|
15
|
+
#
|
|
16
|
+
# GDVd provides efficient delta encoding for musical sequences:
|
|
17
|
+
#
|
|
18
|
+
# - **Compact storage**: Only changed values are stored
|
|
19
|
+
# - **Efficient serialization**: Neuma format uses delta notation
|
|
20
|
+
# - **Lossless compression**: Full reconstruction via {#to_gdv}
|
|
21
|
+
# - **Musical patterns**: Captures relative motion (intervals, velocity changes)
|
|
22
|
+
#
|
|
23
|
+
# ## Encoding Types
|
|
24
|
+
#
|
|
25
|
+
# Each parameter can be encoded as absolute or delta:
|
|
26
|
+
#
|
|
27
|
+
# ### Pitch Encoding
|
|
28
|
+
#
|
|
29
|
+
# **Absolute**:
|
|
30
|
+
#
|
|
31
|
+
# - **abs_grade**: Set grade to specific value
|
|
32
|
+
# - **abs_sharps**: Set chromatic alteration
|
|
33
|
+
# - **abs_octave**: Set octave to specific value
|
|
34
|
+
#
|
|
35
|
+
# **Delta**:
|
|
36
|
+
#
|
|
37
|
+
# - **delta_grade**: Change grade by semitones
|
|
38
|
+
# - **delta_sharps**: Change chromatic alteration
|
|
39
|
+
# - **delta_interval**: Change by scale interval (with delta_interval_sign)
|
|
40
|
+
# - **delta_octave**: Change octave
|
|
41
|
+
#
|
|
42
|
+
# ### Duration Encoding (from {DeltaD})
|
|
43
|
+
#
|
|
44
|
+
# - **abs_duration**: Set duration
|
|
45
|
+
# - **delta_duration**: Add to duration
|
|
46
|
+
# - **factor_duration**: Multiply duration
|
|
47
|
+
#
|
|
48
|
+
# ### Velocity Encoding
|
|
49
|
+
#
|
|
50
|
+
# - **abs_velocity**: Set velocity
|
|
51
|
+
# - **delta_velocity**: Add to velocity
|
|
52
|
+
#
|
|
53
|
+
# ## Natural Keys
|
|
54
|
+
#
|
|
55
|
+
# - **:abs_grade**, **:abs_sharps**, **:abs_octave**: Absolute pitch
|
|
56
|
+
# - **:delta_grade**, **:delta_sharps**, **:delta_interval**, **:delta_interval_sign**, **:delta_octave**: Delta pitch
|
|
57
|
+
# - **:abs_velocity**, **:delta_velocity**: Velocity encoding
|
|
58
|
+
# - **:abs_duration**, **:delta_duration**, **:factor_duration**: Duration encoding
|
|
59
|
+
# - **:modifiers**: Hash of additional modifiers
|
|
60
|
+
#
|
|
61
|
+
# ## Reconstruction
|
|
62
|
+
#
|
|
63
|
+
# Delta events require a previous event for reconstruction:
|
|
64
|
+
#
|
|
65
|
+
# gdvd = { delta_grade: 2, delta_velocity: 1 }.extend(GDVd)
|
|
66
|
+
# previous = { grade: 0, octave: 0, duration: 1.0, velocity: 0 }.extend(GDV)
|
|
67
|
+
# gdv = gdvd.to_gdv(scale, previous: previous)
|
|
68
|
+
# # => { grade: 2, octave: 0, duration: 1.0, velocity: 1 }
|
|
69
|
+
#
|
|
70
|
+
# ## Neuma Delta Notation
|
|
71
|
+
#
|
|
72
|
+
# Delta events use special notation in Neuma format:
|
|
73
|
+
#
|
|
74
|
+
# - **Delta grade**: "+2" or "-2" (semitone change)
|
|
75
|
+
# - **Delta sharps**: "+#" or "-_" (chromatic change)
|
|
76
|
+
# - **Delta octave**: "+o1" or "-o1" (octave change)
|
|
77
|
+
# - **Delta duration**: "+0.5" or "-0.5" (duration change)
|
|
78
|
+
# - **Factor duration**: "*2" or "*0.5" (duration multiply)
|
|
79
|
+
# - **Delta velocity**: "+f" or "-f" (dynamics change)
|
|
80
|
+
#
|
|
81
|
+
# @example First event (absolute encoding)
|
|
82
|
+
# gdvd = { abs_grade: 0, abs_duration: 1.0, abs_velocity: 0 }.extend(GDVd)
|
|
83
|
+
# gdvd.base_duration = 1/4r
|
|
84
|
+
# gdvd.to_neuma # => "(0 4 mp)"
|
|
85
|
+
#
|
|
86
|
+
# @example Delta encoding (unchanged duration)
|
|
87
|
+
# gdvd = { delta_grade: 2, delta_velocity: 1 }.extend(GDVd)
|
|
88
|
+
# gdvd.base_duration = 1/4r
|
|
89
|
+
# gdvd.to_neuma # => "(+2 +f)"
|
|
90
|
+
# # Grade +2 semitones, velocity +1 (one f louder)
|
|
91
|
+
#
|
|
92
|
+
# @example Chromatic change
|
|
93
|
+
# gdvd = { delta_sharps: 1 }.extend(GDVd)
|
|
94
|
+
# gdvd.to_neuma # => "(+#)"
|
|
95
|
+
# # Add one sharp
|
|
96
|
+
#
|
|
97
|
+
# @example Duration multiplication
|
|
98
|
+
# gdvd = { factor_duration: 2 }.extend(GDVd)
|
|
99
|
+
# gdvd.base_duration = 1/4r
|
|
100
|
+
# gdvd.to_neuma # => "(. *2)"
|
|
101
|
+
# # Double duration
|
|
102
|
+
#
|
|
103
|
+
# @example Reconstruction from delta
|
|
104
|
+
# previous = { grade: 0, octave: 0, duration: 1.0, velocity: 0 }.extend(GDV)
|
|
105
|
+
# gdvd = { delta_grade: 2, delta_velocity: 1 }.extend(GDVd)
|
|
106
|
+
# scale = Musa::Scales::Scales.et12[440.0].major[60]
|
|
107
|
+
# gdv = gdvd.to_gdv(scale, previous: previous)
|
|
108
|
+
# # => { grade: 2, octave: 0, duration: 1.0, velocity: 1 }
|
|
109
|
+
#
|
|
110
|
+
# @example Octave change
|
|
111
|
+
# gdvd = { delta_grade: -2, delta_octave: 1 }.extend(GDVd)
|
|
112
|
+
# gdvd.to_neuma # => "(-2 +o1)"
|
|
113
|
+
# # Down 2 semitones, up one octave
|
|
114
|
+
#
|
|
115
|
+
# @see GDV Absolute score notation
|
|
116
|
+
# @see DeltaD Delta duration encoding
|
|
117
|
+
# @see DeltaI Delta indexed encoding
|
|
118
|
+
# @see Helper String formatting utilities
|
|
8
119
|
module GDVd
|
|
9
120
|
include DeltaD
|
|
10
121
|
include DeltaI
|
|
@@ -13,14 +124,28 @@ module Musa::Datasets
|
|
|
13
124
|
|
|
14
125
|
using Musa::Extension::InspectNice
|
|
15
126
|
|
|
127
|
+
# Natural keys for delta encoding.
|
|
128
|
+
# @return [Array<Symbol>]
|
|
16
129
|
NaturalKeys = (NaturalKeys +
|
|
17
130
|
[:abs_grade, :abs_sharps, :abs_octave,
|
|
18
131
|
:delta_grade, :delta_sharps, :delta_interval_sign, :delta_interval, :delta_octave,
|
|
19
132
|
:abs_velocity, :delta_velocity,
|
|
20
133
|
:modifiers]).freeze
|
|
21
134
|
|
|
135
|
+
# Base duration for time calculations.
|
|
136
|
+
# @return [Rational]
|
|
22
137
|
attr_reader :base_duration
|
|
23
138
|
|
|
139
|
+
# Sets base duration, adjusting existing duration values.
|
|
140
|
+
#
|
|
141
|
+
# When base_duration changes, existing abs_duration and delta_duration
|
|
142
|
+
# are scaled proportionally to maintain actual time values.
|
|
143
|
+
#
|
|
144
|
+
# @param value [Rational] new base duration
|
|
145
|
+
#
|
|
146
|
+
# @example
|
|
147
|
+
# gdvd[:abs_duration] = 1.0
|
|
148
|
+
# gdvd.base_duration = 1/4r # abs_duration scaled by factor
|
|
24
149
|
def base_duration=(value)
|
|
25
150
|
factor = value / (@base_duration || 1)
|
|
26
151
|
@base_duration = value
|
|
@@ -29,6 +154,33 @@ module Musa::Datasets
|
|
|
29
154
|
self[:delta_duration] *= factor if has_key?(:delta_duration)
|
|
30
155
|
end
|
|
31
156
|
|
|
157
|
+
# Reconstructs absolute GDV from delta encoding.
|
|
158
|
+
#
|
|
159
|
+
# Applies delta changes to previous event to create new absolute event.
|
|
160
|
+
# Handles all encoding types (abs_, delta_, factor_) appropriately.
|
|
161
|
+
#
|
|
162
|
+
# @param scale [Musa::Scales::Scale] reference scale for pitch calculations
|
|
163
|
+
# @param previous [GDV] previous absolute event (required for reconstruction)
|
|
164
|
+
#
|
|
165
|
+
# @return [GDV] reconstructed absolute event
|
|
166
|
+
#
|
|
167
|
+
# @example Basic delta reconstruction
|
|
168
|
+
# previous = { grade: 0, octave: 0, duration: 1.0, velocity: 0 }.extend(GDV)
|
|
169
|
+
# gdvd = { delta_grade: 2, delta_velocity: 1 }.extend(GDVd)
|
|
170
|
+
# gdv = gdvd.to_gdv(scale, previous: previous)
|
|
171
|
+
# # => { grade: 2, octave: 0, duration: 1.0, velocity: 1 }
|
|
172
|
+
#
|
|
173
|
+
# @example Absolute override
|
|
174
|
+
# previous = { grade: 0, duration: 1.0 }.extend(GDV)
|
|
175
|
+
# gdvd = { abs_grade: 5, abs_duration: 2.0 }.extend(GDVd)
|
|
176
|
+
# gdv = gdvd.to_gdv(scale, previous: previous)
|
|
177
|
+
# # => { grade: 5, duration: 2.0 }
|
|
178
|
+
#
|
|
179
|
+
# @example Duration factor
|
|
180
|
+
# previous = { grade: 0, duration: 1.0 }.extend(GDV)
|
|
181
|
+
# gdvd = { factor_duration: 2 }.extend(GDVd)
|
|
182
|
+
# gdv = gdvd.to_gdv(scale, previous: previous)
|
|
183
|
+
# # => { grade: 0, duration: 2.0 }
|
|
32
184
|
def to_gdv(scale, previous:)
|
|
33
185
|
r = previous.clone.delete_if {|k,_| !GDV::NaturalKeys.include?(k)}.extend GDV
|
|
34
186
|
|
|
@@ -109,6 +261,18 @@ module Musa::Datasets
|
|
|
109
261
|
r
|
|
110
262
|
end
|
|
111
263
|
|
|
264
|
+
# Normalizes chromatic pitch to scale note.
|
|
265
|
+
#
|
|
266
|
+
# Converts arbitrary grade + sharps to closest scale note representation.
|
|
267
|
+
# If chromatic, returns background note + chromatic alteration.
|
|
268
|
+
#
|
|
269
|
+
# @param scale [Musa::Scales::Scale] reference scale
|
|
270
|
+
# @param grade [Integer] scale degree (wide grade)
|
|
271
|
+
# @param sharps [Integer] chromatic alteration
|
|
272
|
+
#
|
|
273
|
+
# @return [Array(Integer, Integer)] [normalized_grade, normalized_sharps]
|
|
274
|
+
#
|
|
275
|
+
# @api private
|
|
112
276
|
private def normalize_to_scale(scale, grade, sharps)
|
|
113
277
|
note = scale[grade].sharp(sharps)
|
|
114
278
|
background = note.background_note
|
|
@@ -120,6 +284,43 @@ module Musa::Datasets
|
|
|
120
284
|
end
|
|
121
285
|
end
|
|
122
286
|
|
|
287
|
+
# Converts to Neuma delta notation string.
|
|
288
|
+
#
|
|
289
|
+
# Neuma delta format uses special notation for changes:
|
|
290
|
+
#
|
|
291
|
+
# ([grade_delta] [octave_delta] [duration_delta] [velocity_delta] [modifiers...])
|
|
292
|
+
#
|
|
293
|
+
# - **Grade delta**: "+2" or "-2" (semitone change)
|
|
294
|
+
# - **Sharp delta**: "+#" or "-_" (chromatic change)
|
|
295
|
+
# - **Octave delta**: "+o1" or "-o1" (octave change)
|
|
296
|
+
# - **Duration delta**: "+0.5", "-0.5", or "*2" (duration change)
|
|
297
|
+
# - **Velocity delta**: "+f" or "-f" (dynamics change by f's)
|
|
298
|
+
#
|
|
299
|
+
# @return [String] Neuma delta notation
|
|
300
|
+
#
|
|
301
|
+
# @example Delta grade
|
|
302
|
+
# gdvd = { delta_grade: 2 }.extend(GDVd)
|
|
303
|
+
# gdvd.base_duration = 1/4r
|
|
304
|
+
# gdvd.to_neuma # => "(+2)"
|
|
305
|
+
#
|
|
306
|
+
# @example Multiple deltas
|
|
307
|
+
# gdvd = { delta_grade: -2, delta_velocity: 1 }.extend(GDVd)
|
|
308
|
+
# gdvd.base_duration = 1/4r
|
|
309
|
+
# gdvd.to_neuma # => "(-2 +f)"
|
|
310
|
+
#
|
|
311
|
+
# @example Duration factor
|
|
312
|
+
# gdvd = { factor_duration: 2 }.extend(GDVd)
|
|
313
|
+
# gdvd.base_duration = 1/4r
|
|
314
|
+
# gdvd.to_neuma # => "(. *2)"
|
|
315
|
+
#
|
|
316
|
+
# @example Chromatic change
|
|
317
|
+
# gdvd = { delta_sharps: 1 }.extend(GDVd)
|
|
318
|
+
# gdvd.to_neuma # => "(+#)"
|
|
319
|
+
#
|
|
320
|
+
# @example Absolute values
|
|
321
|
+
# gdvd = { abs_grade: 0, abs_duration: 1.0 }.extend(GDVd)
|
|
322
|
+
# gdvd.base_duration = 1/4r
|
|
323
|
+
# gdvd.to_neuma # => "(0 4)"
|
|
123
324
|
def to_neuma
|
|
124
325
|
@base_duration ||= Rational(1,4)
|
|
125
326
|
|
|
@@ -1,19 +1,86 @@
|
|
|
1
1
|
module Musa::Datasets
|
|
2
|
+
# Helper utilities for dataset formatting and string generation.
|
|
3
|
+
#
|
|
4
|
+
# Helper provides utility methods for converting datasets to string
|
|
5
|
+
# representations, particularly for the Neuma notation format.
|
|
6
|
+
#
|
|
7
|
+
# These methods handle:
|
|
8
|
+
#
|
|
9
|
+
# - Sign formatting (+/-)
|
|
10
|
+
# - Velocity to dynamics conversion
|
|
11
|
+
# - Modifier parameter formatting
|
|
12
|
+
#
|
|
13
|
+
# @api private
|
|
2
14
|
module Helper
|
|
3
15
|
private
|
|
4
16
|
|
|
17
|
+
# Returns '+' for non-negative numbers, empty string for negative.
|
|
18
|
+
#
|
|
19
|
+
# Used for formatting delta values in Neuma notation.
|
|
20
|
+
#
|
|
21
|
+
# @param x [Numeric] number to check
|
|
22
|
+
# @return [String] '+' or ''
|
|
23
|
+
#
|
|
24
|
+
# @example
|
|
25
|
+
# positive_sign_of(5) # => '+'
|
|
26
|
+
# positive_sign_of(-3) # => ''
|
|
27
|
+
#
|
|
28
|
+
# @api private
|
|
5
29
|
def positive_sign_of(x)
|
|
6
30
|
x >= 0 ? '+' : ''
|
|
7
31
|
end
|
|
8
32
|
|
|
33
|
+
# Returns '+', '+', or '-' based on number's sign.
|
|
34
|
+
#
|
|
35
|
+
# @param x [Numeric] number to check
|
|
36
|
+
# @return [String] '+' (positive), '+' (zero), or '-' (negative)
|
|
37
|
+
#
|
|
38
|
+
# @example
|
|
39
|
+
# sign_of(5) # => '+'
|
|
40
|
+
# sign_of(0) # => '+'
|
|
41
|
+
# sign_of(-3) # => '-'
|
|
42
|
+
#
|
|
43
|
+
# @api private
|
|
9
44
|
def sign_of(x)
|
|
10
45
|
'++-'[x <=> 0]
|
|
11
46
|
end
|
|
12
47
|
|
|
48
|
+
# Converts numeric velocity to dynamics marking.
|
|
49
|
+
#
|
|
50
|
+
# Maps velocity values (-5 to +4) to standard dynamics markings.
|
|
51
|
+
# Range: ppp (-5) to fff (+4), centered at mf (0).
|
|
52
|
+
#
|
|
53
|
+
# @param x [Integer] velocity value
|
|
54
|
+
# @return [String] dynamics marking
|
|
55
|
+
#
|
|
56
|
+
# @example
|
|
57
|
+
# velocity_of(-5) # => 'ppp'
|
|
58
|
+
# velocity_of(0) # => 'mf'
|
|
59
|
+
# velocity_of(4) # => 'fff'
|
|
60
|
+
#
|
|
61
|
+
# @api private
|
|
13
62
|
def velocity_of(x)
|
|
14
63
|
%w[ppp pp p mp mf f ff fff][x + 3]
|
|
15
64
|
end
|
|
16
65
|
|
|
66
|
+
# Formats modifier with parameters for Neuma notation.
|
|
67
|
+
#
|
|
68
|
+
# Converts modifier keys and their parameters into Neuma string format.
|
|
69
|
+
#
|
|
70
|
+
# @param modificator [Symbol] modifier key name
|
|
71
|
+
# @param parameter_or_parameters [Boolean, Array, Object] modifier parameters
|
|
72
|
+
# @return [String] formatted modifier string
|
|
73
|
+
#
|
|
74
|
+
# @example Boolean modifier (flag)
|
|
75
|
+
# modificator_string(:staccato, true) # => 'staccato'
|
|
76
|
+
#
|
|
77
|
+
# @example Single parameter
|
|
78
|
+
# modificator_string(:pedal, 'down') # => 'pedal("down")'
|
|
79
|
+
#
|
|
80
|
+
# @example Multiple parameters
|
|
81
|
+
# modificator_string(:bend, [2, 'up']) # => 'bend(2, "up")'
|
|
82
|
+
#
|
|
83
|
+
# @api private
|
|
17
84
|
def modificator_string(modificator, parameter_or_parameters)
|
|
18
85
|
case parameter_or_parameters
|
|
19
86
|
when true
|
|
@@ -27,6 +94,14 @@ module Musa::Datasets
|
|
|
27
94
|
|
|
28
95
|
private
|
|
29
96
|
|
|
97
|
+
# Converts parameter to string representation.
|
|
98
|
+
#
|
|
99
|
+
# Handles different parameter types for Neuma notation.
|
|
100
|
+
#
|
|
101
|
+
# @param parameter [String, Numeric, Symbol] parameter value
|
|
102
|
+
# @return [String] formatted parameter
|
|
103
|
+
#
|
|
104
|
+
# @api private
|
|
30
105
|
def parameter_to_string(parameter)
|
|
31
106
|
case parameter
|
|
32
107
|
when String
|