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
data/lib/musa-dsl/datasets/p.rb
CHANGED
|
@@ -5,9 +5,113 @@ require_relative '../series'
|
|
|
5
5
|
require_relative '../sequencer'
|
|
6
6
|
|
|
7
7
|
module Musa::Datasets
|
|
8
|
+
# Point series: sequential points in time with durations.
|
|
9
|
+
#
|
|
10
|
+
# P (Point series) represents sequential points in time as arrays with alternating
|
|
11
|
+
# structure: [point, duration, point, duration, point, ...].
|
|
12
|
+
#
|
|
13
|
+
# ## Structure
|
|
14
|
+
#
|
|
15
|
+
# The array alternates between points and durations:
|
|
16
|
+
#
|
|
17
|
+
# [point₀, duration₀, point₁, duration₁, point₂]
|
|
18
|
+
#
|
|
19
|
+
# - **Points** (odd positions): Any data (numbers, hashes, complex structures, etc.)
|
|
20
|
+
# - **Durations** (even positions): Time between points (numbers)
|
|
21
|
+
# - **Last point**: Final point has no duration (sequence end)
|
|
22
|
+
#
|
|
23
|
+
# This compact format efficiently represents timed sequences without
|
|
24
|
+
# repeating time information.
|
|
25
|
+
#
|
|
26
|
+
# ## Conversions
|
|
27
|
+
#
|
|
28
|
+
# P can be converted to two different representations:
|
|
29
|
+
#
|
|
30
|
+
# ### 1. Timed Series (to_timed_serie)
|
|
31
|
+
#
|
|
32
|
+
# Converts to series of {AbsTimed} events with absolute time and value.
|
|
33
|
+
# Each value gets a timestamp based on cumulative durations.
|
|
34
|
+
#
|
|
35
|
+
# p = [60, 4, 64, 8, 67].extend(P)
|
|
36
|
+
# serie = p.to_timed_serie
|
|
37
|
+
# # Yields:
|
|
38
|
+
# # { time: 0, value: 60 }
|
|
39
|
+
# # { time: 1.0, value: 64 } (4 * base_duration = 1.0)
|
|
40
|
+
# # { time: 3.0, value: 67 } (8 * base_duration = 2.0)
|
|
41
|
+
#
|
|
42
|
+
# ### 2. Parameter Segment Series (to_ps_serie)
|
|
43
|
+
#
|
|
44
|
+
# Converts to series of {PS} (Parameter Segment) objects representing
|
|
45
|
+
# continuous changes between consecutive points.
|
|
46
|
+
#
|
|
47
|
+
# p = [60, 4, 64, 8, 67].extend(P)
|
|
48
|
+
# serie = p.to_ps_serie
|
|
49
|
+
# # Yields PS objects:
|
|
50
|
+
# # { from: 60, to: 64, duration: 1.0, right_open: true }
|
|
51
|
+
# # { from: 64, to: 67, duration: 2.0, right_open: false }
|
|
52
|
+
#
|
|
53
|
+
# ## Point Transformation
|
|
54
|
+
#
|
|
55
|
+
# The {#map} method transforms points while preserving durations:
|
|
56
|
+
#
|
|
57
|
+
# p = [60, 4, 64, 8, 67].extend(P)
|
|
58
|
+
# p2 = p.map { |point| point + 12 }
|
|
59
|
+
# # => [72, 4, 76, 8, 79]
|
|
60
|
+
# # Durations unchanged, points transformed
|
|
61
|
+
#
|
|
62
|
+
# @example Basic point series (MIDI pitches)
|
|
63
|
+
# # MIDI pitches with durations in quarter notes
|
|
64
|
+
# p = [60, 4, 64, 8, 67].extend(Musa::Datasets::P)
|
|
65
|
+
# # 60 (C4) for 4 quarters → 64 (E4) for 8 quarters → 67 (G4)
|
|
66
|
+
#
|
|
67
|
+
# @example Hash points (complex data structures)
|
|
68
|
+
# p = [
|
|
69
|
+
# { pitch: 60, velocity: 64 }, 4,
|
|
70
|
+
# { pitch: 64, velocity: 80 }, 8,
|
|
71
|
+
# { pitch: 67, velocity: 64 }
|
|
72
|
+
# ].extend(Musa::Datasets::P)
|
|
73
|
+
#
|
|
74
|
+
# @example Convert to timed serie
|
|
75
|
+
# p = [60, 4, 64, 8, 67].extend(Musa::Datasets::P)
|
|
76
|
+
# serie = p.to_timed_serie(base_duration: 1/4r)
|
|
77
|
+
# # base_duration: quarter note = 1/4 beat
|
|
78
|
+
#
|
|
79
|
+
# @example Start at specific time
|
|
80
|
+
# serie = p.to_timed_serie(time_start: 10)
|
|
81
|
+
# # First event at time 10
|
|
82
|
+
#
|
|
83
|
+
# @example Start time from component
|
|
84
|
+
# p = [{ time: 100, pitch: 60 }, 4, { time: 200, pitch: 64 }].extend(P)
|
|
85
|
+
# serie = p.to_timed_serie(time_start_component: :time)
|
|
86
|
+
# # First event at time 100 (from first point's :time)
|
|
87
|
+
#
|
|
88
|
+
# @example Transform points
|
|
89
|
+
# p = [60, 4, 64, 8, 67].extend(Musa::Datasets::P)
|
|
90
|
+
# p2 = p.map { |point| point + 12 }
|
|
91
|
+
# # Transform each point (e.g., transpose pitches up one octave)
|
|
92
|
+
#
|
|
93
|
+
# @see PS Parameter segments
|
|
94
|
+
# @see AbsTimed Timed events
|
|
95
|
+
# @see Dataset Parent dataset module
|
|
8
96
|
module P
|
|
9
97
|
include Dataset
|
|
10
98
|
|
|
99
|
+
# Converts to series of parameter segments.
|
|
100
|
+
#
|
|
101
|
+
# Creates {PS} objects representing continuous changes from each point
|
|
102
|
+
# to the next. Useful for glissandi, parameter sweeps, or any continuous
|
|
103
|
+
# interpolation between points.
|
|
104
|
+
#
|
|
105
|
+
# @param base_duration [Rational] duration unit multiplier (default: 1/4r)
|
|
106
|
+
# Durations in P are multiplied by this to get actual time
|
|
107
|
+
#
|
|
108
|
+
# @return [Musa::Series::Serie<PS>] series of parameter segments
|
|
109
|
+
#
|
|
110
|
+
# @example Create parameter segments
|
|
111
|
+
# p = [60, 4, 64, 8, 67].extend(P)
|
|
112
|
+
# serie = p.to_ps_serie
|
|
113
|
+
# segment1 = serie.next_value
|
|
114
|
+
# # => { from: 60, to: 64, duration: 1.0, right_open: true }
|
|
11
115
|
def to_ps_serie(base_duration: nil)
|
|
12
116
|
base_duration ||= 1/4r # TODO review incoherence between neumalang 1/4r base duration for quarter notes and general 1r size of bar
|
|
13
117
|
|
|
@@ -22,6 +126,33 @@ module Musa::Datasets
|
|
|
22
126
|
end
|
|
23
127
|
end
|
|
24
128
|
|
|
129
|
+
# Converts to series of timed events (AbsTimed).
|
|
130
|
+
#
|
|
131
|
+
# Creates series yielding {AbsTimed} events with absolute time and value.
|
|
132
|
+
# Each value is emitted at its calculated time point based on cumulative durations.
|
|
133
|
+
#
|
|
134
|
+
# @param time_start [Numeric] starting time offset (default: 0)
|
|
135
|
+
# @param time_start_component [Symbol] key in first value to use as time offset
|
|
136
|
+
# If provided, adds first[time_start_component] to time_start
|
|
137
|
+
# @param base_duration [Rational] duration unit multiplier (default: 1/4r)
|
|
138
|
+
#
|
|
139
|
+
# @return [PtoTimedSerie] series of timed events
|
|
140
|
+
#
|
|
141
|
+
# @example Basic timed serie
|
|
142
|
+
# p = [60, 4, 64, 8, 67].extend(P)
|
|
143
|
+
# serie = p.to_timed_serie
|
|
144
|
+
# serie.next_value # => { time: 0, value: 60 }
|
|
145
|
+
# serie.next_value # => { time: 1.0, value: 64 }
|
|
146
|
+
# serie.next_value # => { time: 3.0, value: 67 }
|
|
147
|
+
#
|
|
148
|
+
# @example Custom start time
|
|
149
|
+
# serie = p.to_timed_serie(time_start: 10)
|
|
150
|
+
# # First event at time 10
|
|
151
|
+
#
|
|
152
|
+
# @example Start time from component
|
|
153
|
+
# p = [{ time: 100, pitch: 60 }, 4, { pitch: 64 }].extend(P)
|
|
154
|
+
# serie = p.to_timed_serie(time_start_component: :time)
|
|
155
|
+
# # First event at time 100
|
|
25
156
|
def to_timed_serie(time_start: nil, time_start_component: nil, base_duration: nil)
|
|
26
157
|
time_start ||= 0r
|
|
27
158
|
time_start += self.first[time_start_component] if time_start_component
|
|
@@ -31,11 +162,30 @@ module Musa::Datasets
|
|
|
31
162
|
PtoTimedSerie.new(self, base_duration, time_start)
|
|
32
163
|
end
|
|
33
164
|
|
|
165
|
+
# Maps over points, preserving durations.
|
|
166
|
+
#
|
|
167
|
+
# Transforms each point (odd positions) using the block while
|
|
168
|
+
# keeping durations (even positions) unchanged.
|
|
169
|
+
#
|
|
170
|
+
# @yieldparam point [Object] each point in the series
|
|
171
|
+
# @yieldreturn [Object] transformed point
|
|
172
|
+
#
|
|
173
|
+
# @return [P] new P with transformed points
|
|
174
|
+
#
|
|
175
|
+
# @example Transform points (e.g., transpose pitches)
|
|
176
|
+
# p = [60, 4, 64, 8, 67].extend(P)
|
|
177
|
+
# p.map { |point| point + 12 }
|
|
178
|
+
# # => [72, 4, 76, 8, 79]
|
|
179
|
+
#
|
|
180
|
+
# @example Transform hash points
|
|
181
|
+
# p = [{ pitch: 60 }, 4, { pitch: 64 }].extend(P)
|
|
182
|
+
# p.map { |point| point.merge(velocity: 80) }
|
|
183
|
+
# # Adds velocity to each point
|
|
34
184
|
def map(&block)
|
|
35
185
|
i = 0
|
|
36
186
|
clone.map! do |element|
|
|
37
|
-
# Process with block only the
|
|
38
|
-
# structure is <
|
|
187
|
+
# Process with block only the points (points are the alternating elements because P
|
|
188
|
+
# structure is <point> <duration> <point> <duration> <point>)
|
|
39
189
|
#
|
|
40
190
|
if (i += 1) % 2 == 1
|
|
41
191
|
block.call(element)
|
|
@@ -45,9 +195,26 @@ module Musa::Datasets
|
|
|
45
195
|
end
|
|
46
196
|
end
|
|
47
197
|
|
|
198
|
+
# Series adapter for P to AbsTimed conversion.
|
|
199
|
+
#
|
|
200
|
+
# PtoTimedSerie is a {Musa::Series::Serie} that converts a {P} (point series)
|
|
201
|
+
# into a series of {AbsTimed} events. It reads the alternating point/duration
|
|
202
|
+
# structure and emits timed events.
|
|
203
|
+
#
|
|
204
|
+
# This class is created by {P#to_timed_serie} and should not be instantiated
|
|
205
|
+
# directly.
|
|
206
|
+
#
|
|
207
|
+
# @api private
|
|
48
208
|
class PtoTimedSerie
|
|
49
209
|
include Musa::Series::Serie.base
|
|
50
210
|
|
|
211
|
+
# Creates new timed serie adapter.
|
|
212
|
+
#
|
|
213
|
+
# @param origin [P] source point series
|
|
214
|
+
# @param base_duration [Rational] duration unit multiplier
|
|
215
|
+
# @param time_start [Numeric] starting time offset
|
|
216
|
+
#
|
|
217
|
+
# @api private
|
|
51
218
|
def initialize(origin, base_duration, time_start)
|
|
52
219
|
@origin = origin
|
|
53
220
|
@base_duration = base_duration
|
|
@@ -58,8 +225,16 @@ module Musa::Datasets
|
|
|
58
225
|
mark_as_prototype!
|
|
59
226
|
end
|
|
60
227
|
|
|
228
|
+
# Source point series.
|
|
229
|
+
# @return [P]
|
|
61
230
|
attr_accessor :origin
|
|
231
|
+
|
|
232
|
+
# Duration unit multiplier.
|
|
233
|
+
# @return [Rational]
|
|
62
234
|
attr_accessor :base_duration
|
|
235
|
+
|
|
236
|
+
# Starting time offset.
|
|
237
|
+
# @return [Numeric]
|
|
63
238
|
attr_accessor :time_start
|
|
64
239
|
|
|
65
240
|
private def _init
|
|
@@ -2,9 +2,100 @@ require_relative 'e'
|
|
|
2
2
|
require_relative 'v'
|
|
3
3
|
|
|
4
4
|
module Musa::Datasets
|
|
5
|
+
# Hash-based dataset with array conversion.
|
|
6
|
+
#
|
|
7
|
+
# PackedV (Packed Value) represents datasets stored as hashes (named key-value pairs).
|
|
8
|
+
# Extends {AbsI} for absolute indexed events.
|
|
9
|
+
#
|
|
10
|
+
# ## Purpose
|
|
11
|
+
#
|
|
12
|
+
# PackedV provides named key-value storage for musical data and conversion
|
|
13
|
+
# to indexed arrays ({V}). This is useful for:
|
|
14
|
+
#
|
|
15
|
+
# - Semantic naming of values (pitch, duration, velocity)
|
|
16
|
+
# - Sparse data (only store non-default values)
|
|
17
|
+
# - Converting between hash and array representations
|
|
18
|
+
# - Serialization to readable formats
|
|
19
|
+
#
|
|
20
|
+
# ## Conversion to V
|
|
21
|
+
#
|
|
22
|
+
# The {#to_V} method converts hashes to arrays using a mapper that
|
|
23
|
+
# defines the correspondence between hash keys and array positions.
|
|
24
|
+
#
|
|
25
|
+
# ### Array Mapper
|
|
26
|
+
#
|
|
27
|
+
# Array mapper defines the order of keys in resulting array.
|
|
28
|
+
# Position i contains value for key mapper[i].
|
|
29
|
+
#
|
|
30
|
+
# pv = { pitch: 60, duration: 1.0 }.extend(Musa::Datasets::PackedV)
|
|
31
|
+
# v = pv.to_V([:pitch, :duration, :velocity])
|
|
32
|
+
# # => [60, 1.0, nil]
|
|
33
|
+
# # velocity missing, becomes nil
|
|
34
|
+
#
|
|
35
|
+
# - Missing keys become `nil` in array
|
|
36
|
+
#
|
|
37
|
+
# ### Hash Mapper
|
|
38
|
+
#
|
|
39
|
+
# Hash mapper defines both key order (keys) and default values (values).
|
|
40
|
+
# Position i contains value for key mapper.keys[i], using mapper.values[i]
|
|
41
|
+
# as default if key is missing or value is nil.
|
|
42
|
+
#
|
|
43
|
+
# pv = { pitch: 60 }.extend(Musa::Datasets::PackedV)
|
|
44
|
+
# v = pv.to_V({ pitch: 60, duration: 1.0, velocity: 64 })
|
|
45
|
+
# # => [60, 1.0, 64]
|
|
46
|
+
# # duration and velocity use defaults
|
|
47
|
+
#
|
|
48
|
+
# Defaults fill in missing or nil values.
|
|
49
|
+
#
|
|
50
|
+
# @example Basic hash to array conversion
|
|
51
|
+
# pv = { pitch: 60, duration: 1.0, velocity: 64 }.extend(Musa::Datasets::PackedV)
|
|
52
|
+
# v = pv.to_V([:pitch, :duration, :velocity])
|
|
53
|
+
# # => [60, 1.0, 64]
|
|
54
|
+
#
|
|
55
|
+
# @example Missing keys become nil (array mapper)
|
|
56
|
+
# pv = { a: 1, c: 3 }.extend(Musa::Datasets::PackedV)
|
|
57
|
+
# v = pv.to_V([:c, :b, :a])
|
|
58
|
+
# # => [3, nil, 1]
|
|
59
|
+
# # b missing, becomes nil
|
|
60
|
+
#
|
|
61
|
+
# @example Hash mapper with defaults
|
|
62
|
+
# pv = { a: 1, b: nil, c: 3 }.extend(Musa::Datasets::PackedV)
|
|
63
|
+
# v = pv.to_V({ c: 100, b: 200, a: 300, d: 400 })
|
|
64
|
+
# # => [3, 200, 1, 400]
|
|
65
|
+
# # b nil → uses default 200
|
|
66
|
+
# # d missing → uses default 400
|
|
67
|
+
#
|
|
68
|
+
# @example Partial mapper (fewer keys in mapper)
|
|
69
|
+
# pv = { a: 1, b: 2, c: 3 }.extend(Musa::Datasets::PackedV)
|
|
70
|
+
# v = pv.to_V([:c, :b])
|
|
71
|
+
# # => [3, 2]
|
|
72
|
+
# # Only c and b extracted
|
|
73
|
+
#
|
|
74
|
+
# @example Key order matters
|
|
75
|
+
# pv = { a: 1, b: 2, c: 3 }.extend(Musa::Datasets::PackedV)
|
|
76
|
+
# v = pv.to_V([:c, :b, :a])
|
|
77
|
+
# # => [3, 2, 1]
|
|
78
|
+
#
|
|
79
|
+
# @see V Array-based dataset (inverse)
|
|
80
|
+
# @see AbsI Parent absolute indexed module
|
|
5
81
|
module PackedV
|
|
6
82
|
include AbsI
|
|
7
83
|
|
|
84
|
+
# Converts packed hash to array (V).
|
|
85
|
+
#
|
|
86
|
+
# @param mapper [Array<Symbol>, Hash{Symbol => Object}] key mapping
|
|
87
|
+
# - Array: maps keys to indices (order matters)
|
|
88
|
+
# - Hash: maps keys (keys) to indices with defaults (values)
|
|
89
|
+
#
|
|
90
|
+
# @return [V] array dataset
|
|
91
|
+
#
|
|
92
|
+
# @raise [ArgumentError] if mapper is not Array or Hash
|
|
93
|
+
#
|
|
94
|
+
# @example Array mapper
|
|
95
|
+
# pv.to_V([:pitch, :duration, :velocity])
|
|
96
|
+
#
|
|
97
|
+
# @example Hash mapper with defaults
|
|
98
|
+
# pv.to_V({ pitch: 60, duration: 1.0, velocity: 64 })
|
|
8
99
|
def to_V(mapper)
|
|
9
100
|
case mapper
|
|
10
101
|
when Hash
|
|
@@ -4,15 +4,150 @@ require_relative 'gdv'
|
|
|
4
4
|
require_relative 'helper'
|
|
5
5
|
|
|
6
6
|
module Musa::Datasets
|
|
7
|
+
# MIDI-style musical events with absolute pitches.
|
|
8
|
+
#
|
|
9
|
+
# PDV (Pitch/Duration/Velocity) represents musical events using MIDI-like
|
|
10
|
+
# absolute pitch numbers. Extends {AbsD} for duration support.
|
|
11
|
+
#
|
|
12
|
+
# ## Purpose
|
|
13
|
+
#
|
|
14
|
+
# PDV is the MIDI representation layer of the dataset framework:
|
|
15
|
+
#
|
|
16
|
+
# - Uses absolute MIDI pitch numbers (0-127)
|
|
17
|
+
# - Uses MIDI velocity values (0-127)
|
|
18
|
+
# - Direct mapping to MIDI messages
|
|
19
|
+
# - Machine-oriented (not human-readable)
|
|
20
|
+
#
|
|
21
|
+
# Contrast with {GDV} which uses score notation (scale degrees, dynamics).
|
|
22
|
+
#
|
|
23
|
+
# ## Natural Keys
|
|
24
|
+
#
|
|
25
|
+
# - **:pitch**: MIDI pitch number (0-127) or :silence for rests
|
|
26
|
+
# - **:velocity**: MIDI velocity (0-127)
|
|
27
|
+
# - **:duration**: Event duration (from {AbsD})
|
|
28
|
+
# - **:note_duration**, **:forward_duration**: Additional duration keys (from {AbsD})
|
|
29
|
+
#
|
|
30
|
+
# ## Conversions
|
|
31
|
+
#
|
|
32
|
+
# ### To GDV (Score Notation)
|
|
33
|
+
#
|
|
34
|
+
# Converts MIDI pitches to scale degrees using a scale reference:
|
|
35
|
+
#
|
|
36
|
+
# pdv = { pitch: 60, duration: 1.0, velocity: 64 }.extend(PDV)
|
|
37
|
+
# scale = Musa::Scales::Scales.et12[440.0].major[60]
|
|
38
|
+
# gdv = pdv.to_gdv(scale)
|
|
39
|
+
# # => { grade: 0, octave: 0, duration: 1.0, velocity: 0 }
|
|
40
|
+
#
|
|
41
|
+
# - **Pitch → Grade**: Finds closest scale degree
|
|
42
|
+
# - **Chromatic notes**: Represented as grade + sharps
|
|
43
|
+
# - **Velocity**: Maps MIDI 0-127 to dynamics -5 to +4 (ppp to fff)
|
|
44
|
+
#
|
|
45
|
+
# ### Velocity Mapping
|
|
46
|
+
#
|
|
47
|
+
# MIDI velocities are mapped to musical dynamics:
|
|
48
|
+
#
|
|
49
|
+
# MIDI 1-1 → velocity -5 (ppp)
|
|
50
|
+
# MIDI 2-8 → velocity -4 (pp)
|
|
51
|
+
# MIDI 9-16 → velocity -3 (p)
|
|
52
|
+
# MIDI 17-33 → velocity -2 (mp)
|
|
53
|
+
# MIDI 34-48 → velocity -1 (mf-)
|
|
54
|
+
# MIDI 49-64 → velocity 0 (mf)
|
|
55
|
+
# MIDI 65-80 → velocity +1 (f)
|
|
56
|
+
# MIDI 81-96 → velocity +2 (ff)
|
|
57
|
+
# MIDI 97-112 → velocity +3 (fff-)
|
|
58
|
+
# MIDI 113-127 → velocity +4 (fff)
|
|
59
|
+
#
|
|
60
|
+
# ## Base Duration
|
|
61
|
+
#
|
|
62
|
+
# The `base_duration` attribute defines the unit for duration values,
|
|
63
|
+
# typically 1/4r (quarter note).
|
|
64
|
+
#
|
|
65
|
+
# @example Basic MIDI event
|
|
66
|
+
# pdv = { pitch: 60, duration: 1.0, velocity: 64 }.extend(Musa::Datasets::PDV)
|
|
67
|
+
# pdv.base_duration = 1/4r
|
|
68
|
+
# # C4 (middle C) for 1 beat at mf dynamics
|
|
69
|
+
#
|
|
70
|
+
# @example Silence (rest)
|
|
71
|
+
# pdv = { pitch: :silence, duration: 1.0 }.extend(PDV)
|
|
72
|
+
# # Rest for 1 beat
|
|
73
|
+
#
|
|
74
|
+
# @example With articulation
|
|
75
|
+
# pdv = {
|
|
76
|
+
# pitch: 64,
|
|
77
|
+
# duration: 1.0,
|
|
78
|
+
# note_duration: 0.5, # Staccato
|
|
79
|
+
# velocity: 80
|
|
80
|
+
# }.extend(PDV)
|
|
81
|
+
#
|
|
82
|
+
# @example Convert to score notation
|
|
83
|
+
# pdv = { pitch: 60, duration: 1.0, velocity: 64 }.extend(PDV)
|
|
84
|
+
# pdv.base_duration = 1/4r
|
|
85
|
+
# scale = Musa::Scales::Scales.et12[440.0].major[60]
|
|
86
|
+
# gdv = pdv.to_gdv(scale)
|
|
87
|
+
# # => { grade: 0, octave: 0, duration: 1.0, velocity: 0 }
|
|
88
|
+
#
|
|
89
|
+
# @example Chromatic pitch
|
|
90
|
+
# pdv = { pitch: 61, duration: 1.0, velocity: 64 }.extend(PDV)
|
|
91
|
+
# scale = Musa::Scales::Scales.et12[440.0].major[60]
|
|
92
|
+
# gdv = pdv.to_gdv(scale)
|
|
93
|
+
# # => { grade: 0, octave: 0, sharps: 1, duration: 1.0, velocity: 0 }
|
|
94
|
+
# # C# represented as C (grade 0) + 1 sharp
|
|
95
|
+
#
|
|
96
|
+
# @example Preserve additional keys
|
|
97
|
+
# pdv = {
|
|
98
|
+
# pitch: 60,
|
|
99
|
+
# duration: 1.0,
|
|
100
|
+
# velocity: 64,
|
|
101
|
+
# custom_key: :value
|
|
102
|
+
# }.extend(PDV)
|
|
103
|
+
# scale = Musa::Scales::Scales.et12[440.0].major[60]
|
|
104
|
+
# gdv = pdv.to_gdv(scale)
|
|
105
|
+
# # custom_key copied to GDV (not a natural key)
|
|
106
|
+
#
|
|
107
|
+
# @see GDV Score-style representation
|
|
108
|
+
# @see AbsD Absolute duration events
|
|
109
|
+
# @see Helper String formatting utilities
|
|
7
110
|
module PDV
|
|
8
111
|
include AbsD
|
|
9
112
|
|
|
10
113
|
include Helper
|
|
11
114
|
|
|
115
|
+
# Natural keys for MIDI events.
|
|
116
|
+
# @return [Array<Symbol>]
|
|
12
117
|
NaturalKeys = (NaturalKeys + [:pitch, :velocity]).freeze
|
|
13
118
|
|
|
119
|
+
# Base duration for time calculations.
|
|
120
|
+
# @return [Rational]
|
|
14
121
|
attr_accessor :base_duration
|
|
15
122
|
|
|
123
|
+
# Converts to GDV (score notation).
|
|
124
|
+
#
|
|
125
|
+
# Translates MIDI representation to score notation using a scale:
|
|
126
|
+
# - MIDI pitch → scale degree (grade + octave + sharps)
|
|
127
|
+
# - MIDI velocity → dynamics (-5 to +4)
|
|
128
|
+
# - Duration values copied
|
|
129
|
+
# - Additional keys preserved
|
|
130
|
+
#
|
|
131
|
+
# @param scale [Musa::Scales::Scale] reference scale for pitch conversion
|
|
132
|
+
#
|
|
133
|
+
# @return [GDV] score notation dataset
|
|
134
|
+
#
|
|
135
|
+
# @example Basic conversion
|
|
136
|
+
# pdv = { pitch: 60, duration: 1.0, velocity: 64 }.extend(PDV)
|
|
137
|
+
# scale = Musa::Scales::Scales.et12[440.0].major[60]
|
|
138
|
+
# gdv = pdv.to_gdv(scale)
|
|
139
|
+
#
|
|
140
|
+
# @example Chromatic note
|
|
141
|
+
# pdv = { pitch: 61, duration: 1.0 }.extend(PDV)
|
|
142
|
+
# scale = Musa::Scales::Scales.et12[440.0].major[60]
|
|
143
|
+
# gdv = pdv.to_gdv(scale)
|
|
144
|
+
# # => { grade: 0, octave: 0, sharps: 1, duration: 1.0 }
|
|
145
|
+
#
|
|
146
|
+
# @example Silence
|
|
147
|
+
# pdv = { pitch: :silence, duration: 1.0 }.extend(PDV)
|
|
148
|
+
# scale = Musa::Scales::Scales.et12[440.0].major[60]
|
|
149
|
+
# gdv = pdv.to_gdv(scale)
|
|
150
|
+
# # => { grade: :silence, duration: 1.0 }
|
|
16
151
|
def to_gdv(scale)
|
|
17
152
|
gdv = {}.extend GDV
|
|
18
153
|
gdv.base_duration = @base_duration
|
|
@@ -39,7 +174,7 @@ module Musa::Datasets
|
|
|
39
174
|
if self[:velocity]
|
|
40
175
|
# ppp = 16 ... fff = 127
|
|
41
176
|
# TODO create a customizable MIDI velocity to score dynamics bidirectional conversor
|
|
42
|
-
gdv[:velocity] = [1..1, 2..8, 9..16, 17..33, 34..
|
|
177
|
+
gdv[:velocity] = [1..1, 2..8, 9..16, 17..33, 34..48, 49..64, 65..80, 81..96, 97..112, 113..127].index { |r| r.cover? self[:velocity] } - 5
|
|
43
178
|
end
|
|
44
179
|
|
|
45
180
|
(keys - NaturalKeys).each { |k| gdv[k] = self[k] }
|
data/lib/musa-dsl/datasets/ps.rb
CHANGED
|
@@ -4,31 +4,161 @@ require_relative 'score'
|
|
|
4
4
|
require_relative '../sequencer'
|
|
5
5
|
|
|
6
6
|
module Musa::Datasets
|
|
7
|
+
# Parameter segments for continuous changes between multidimensional points.
|
|
8
|
+
#
|
|
9
|
+
# PS (Parameter Segment) represents a continuous change from one point
|
|
10
|
+
# to another over a duration. Extends {AbsD} for duration support.
|
|
11
|
+
#
|
|
12
|
+
# ## Purpose
|
|
13
|
+
#
|
|
14
|
+
# PS is used to represent:
|
|
15
|
+
#
|
|
16
|
+
# - **Glissandi**: Continuous pitch slides (portamento)
|
|
17
|
+
# - **Parameter sweeps**: Gradual changes in any sonic parameter
|
|
18
|
+
# - **Interpolations**: Smooth transitions between multidimensional states
|
|
19
|
+
#
|
|
20
|
+
# Unlike discrete events that jump from one value to another, PS represents
|
|
21
|
+
# the continuous path between values.
|
|
22
|
+
#
|
|
23
|
+
# ## Natural Keys
|
|
24
|
+
#
|
|
25
|
+
# - **:from**: Starting value (number, array, or hash)
|
|
26
|
+
# - **:to**: Ending value (must match :from type and structure)
|
|
27
|
+
# - **:right_open**: Whether endpoint is included (true = open interval)
|
|
28
|
+
# - **:duration**: Duration of the change (from {AbsD})
|
|
29
|
+
# - **:note_duration**, **:forward_duration**: Additional duration keys (from {AbsD})
|
|
30
|
+
#
|
|
31
|
+
# ## Value Types
|
|
32
|
+
#
|
|
33
|
+
# ### Single Values
|
|
34
|
+
#
|
|
35
|
+
# { from: 60, to: 72, duration: 1.0 }
|
|
36
|
+
# # Single value glissando
|
|
37
|
+
#
|
|
38
|
+
# ### Arrays (parallel interpolation)
|
|
39
|
+
#
|
|
40
|
+
# { from: [60, 64], to: [72, 76], duration: 1.0 }
|
|
41
|
+
# # Both values interpolate in parallel
|
|
42
|
+
# # Arrays must be same size
|
|
43
|
+
#
|
|
44
|
+
# ### Hashes (named parameters)
|
|
45
|
+
#
|
|
46
|
+
# { from: { pitch: 60, velocity: 64 },
|
|
47
|
+
# to: { pitch: 72, velocity: 80 },
|
|
48
|
+
# duration: 1.0 }
|
|
49
|
+
# # Multiple parameters interpolate together
|
|
50
|
+
# # Hashes must have same keys
|
|
51
|
+
#
|
|
52
|
+
# ## Right Open Intervals
|
|
53
|
+
#
|
|
54
|
+
# The :right_open flag determines if the ending value is reached:
|
|
55
|
+
#
|
|
56
|
+
# - **false** (closed): Interpolation reaches :to value
|
|
57
|
+
# - **true** (open): Interpolation stops just before :to value
|
|
58
|
+
#
|
|
59
|
+
# This is important for consecutive segments where you don't want
|
|
60
|
+
# discontinuities at the boundaries.
|
|
61
|
+
#
|
|
62
|
+
# @example Basic parameter segment (pitch glissando)
|
|
63
|
+
# ps = { from: 60, to: 72, duration: 2.0 }.extend(Musa::Datasets::PS)
|
|
64
|
+
# # Continuous slide from C4 to C5 over 2 beats
|
|
65
|
+
#
|
|
66
|
+
# @example Parallel interpolation (multidimensional)
|
|
67
|
+
# ps = {
|
|
68
|
+
# from: [60, 64], # C4 and E4
|
|
69
|
+
# to: [72, 76], # C5 and E5
|
|
70
|
+
# duration: 1.0
|
|
71
|
+
# }.extend(PS)
|
|
72
|
+
# # Both parameters move in parallel
|
|
73
|
+
#
|
|
74
|
+
# @example Multiple parameters (sonic gesture)
|
|
75
|
+
# ps = {
|
|
76
|
+
# from: { pitch: 60, velocity: 64, pan: -1.0 },
|
|
77
|
+
# to: { pitch: 72, velocity: 80, pan: 1.0 },
|
|
78
|
+
# duration: 2.0
|
|
79
|
+
# }.extend(PS)
|
|
80
|
+
# # Pitch, velocity, and pan all change smoothly
|
|
81
|
+
#
|
|
82
|
+
# @example Right open interval
|
|
83
|
+
# ps1 = { from: 60, to: 64, duration: 1.0, right_open: true }.extend(PS)
|
|
84
|
+
# ps2 = { from: 64, to: 67, duration: 1.0, right_open: false }.extend(PS)
|
|
85
|
+
# # ps1 stops just before 64, ps2 starts at 64 - no discontinuity
|
|
86
|
+
#
|
|
87
|
+
# @example Created from P point series
|
|
88
|
+
# p = [60, 4, 64, 8, 67].extend(P)
|
|
89
|
+
# serie = p.to_ps_serie
|
|
90
|
+
# ps1 = serie.next_value
|
|
91
|
+
# # => { from: 60, to: 64, duration: 1.0, right_open: true }
|
|
92
|
+
#
|
|
93
|
+
# @see AbsD Parent absolute duration module
|
|
94
|
+
# @see P Point series (source of PS)
|
|
95
|
+
# @see Helper String formatting utilities
|
|
7
96
|
module PS
|
|
8
97
|
include AbsD
|
|
9
98
|
|
|
10
99
|
include Helper
|
|
11
100
|
|
|
101
|
+
# Natural keys including segment endpoints.
|
|
102
|
+
# @return [Array<Symbol>]
|
|
12
103
|
NaturalKeys = (NaturalKeys + [:from, :to, :right_open]).freeze
|
|
13
104
|
|
|
105
|
+
# Base duration for time calculations.
|
|
106
|
+
# @return [Rational]
|
|
14
107
|
attr_accessor :base_duration
|
|
15
108
|
|
|
109
|
+
# Converts to Neuma notation string.
|
|
110
|
+
#
|
|
111
|
+
# @return [String] Neuma notation
|
|
112
|
+
# @todo Not yet implemented
|
|
16
113
|
def to_neuma
|
|
17
|
-
|
|
114
|
+
raise NotImplementedError, 'PS to_neuma conversion is not yet implemented'
|
|
18
115
|
end
|
|
19
116
|
|
|
117
|
+
# Converts to PDV (Pitch/Duration/Velocity).
|
|
118
|
+
#
|
|
119
|
+
# @return [PDV] PDV dataset
|
|
120
|
+
# @todo Not yet implemented
|
|
20
121
|
def to_pdv
|
|
21
|
-
|
|
122
|
+
raise NotImplementedError, 'PS to_pdv conversion is not yet implemented'
|
|
22
123
|
end
|
|
23
124
|
|
|
125
|
+
# Converts to GDV (Grade/Duration/Velocity).
|
|
126
|
+
#
|
|
127
|
+
# @return [GDV] GDV dataset
|
|
128
|
+
# @todo Not yet implemented
|
|
24
129
|
def to_gdv
|
|
25
|
-
|
|
130
|
+
raise NotImplementedError, 'PS to_gdv conversion is not yet implemented'
|
|
26
131
|
end
|
|
27
132
|
|
|
133
|
+
# Converts to absolute indexed format.
|
|
134
|
+
#
|
|
135
|
+
# @return [AbsI] indexed dataset
|
|
136
|
+
# @todo Not yet implemented
|
|
28
137
|
def to_absI
|
|
29
|
-
|
|
138
|
+
raise NotImplementedError, 'PS to_absI conversion is not yet implemented'
|
|
30
139
|
end
|
|
31
140
|
|
|
141
|
+
# Validates PS structure.
|
|
142
|
+
#
|
|
143
|
+
# Checks that:
|
|
144
|
+
# - :from and :to have compatible types
|
|
145
|
+
# - Arrays have same size
|
|
146
|
+
# - Hashes have same keys
|
|
147
|
+
# - Duration is positive numeric
|
|
148
|
+
#
|
|
149
|
+
# @return [Boolean] true if valid
|
|
150
|
+
#
|
|
151
|
+
# @example Valid array segment
|
|
152
|
+
# ps = { from: [60, 64], to: [72, 76], duration: 1.0 }.extend(PS)
|
|
153
|
+
# ps.valid? # => true
|
|
154
|
+
#
|
|
155
|
+
# @example Invalid - mismatched array sizes
|
|
156
|
+
# ps = { from: [60, 64], to: [72], duration: 1.0 }.extend(PS)
|
|
157
|
+
# ps.valid? # => false
|
|
158
|
+
#
|
|
159
|
+
# @example Invalid - mismatched hash keys
|
|
160
|
+
# ps = { from: { a: 1 }, to: { b: 2 }, duration: 1.0 }.extend(PS)
|
|
161
|
+
# ps.valid? # => false
|
|
32
162
|
def valid?
|
|
33
163
|
case self[:from]
|
|
34
164
|
when Array
|