musa-dsl 0.14.29 → 0.21.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -1
- data/Gemfile +0 -1
- data/README.md +5 -1
- data/lib/musa-dsl.rb +54 -11
- data/lib/musa-dsl/core-ext.rb +7 -13
- data/lib/musa-dsl/core-ext/array-explode-ranges.rb +15 -23
- data/lib/musa-dsl/core-ext/arrayfy.rb +30 -12
- data/lib/musa-dsl/core-ext/attribute-builder.rb +194 -0
- data/lib/musa-dsl/core-ext/deep-copy.rb +185 -0
- data/lib/musa-dsl/core-ext/dynamic-proxy.rb +44 -40
- data/lib/musa-dsl/core-ext/inspect-nice.rb +40 -22
- data/lib/musa-dsl/core-ext/smart-proc-binder.rb +108 -0
- data/lib/musa-dsl/core-ext/with.rb +26 -0
- data/lib/musa-dsl/datasets.rb +8 -3
- data/lib/musa-dsl/datasets/dataset.rb +3 -0
- data/lib/musa-dsl/datasets/delta-d.rb +12 -0
- data/lib/musa-dsl/datasets/e.rb +61 -0
- data/lib/musa-dsl/datasets/gdv.rb +51 -411
- data/lib/musa-dsl/datasets/gdvd.rb +179 -0
- data/lib/musa-dsl/datasets/helper.rb +41 -0
- data/lib/musa-dsl/datasets/p.rb +68 -0
- data/lib/musa-dsl/datasets/packed-v.rb +19 -0
- data/lib/musa-dsl/datasets/pdv.rb +22 -15
- data/lib/musa-dsl/datasets/ps.rb +113 -0
- data/lib/musa-dsl/datasets/score.rb +210 -0
- data/lib/musa-dsl/datasets/score/queriable.rb +48 -0
- data/lib/musa-dsl/datasets/score/render.rb +31 -0
- data/lib/musa-dsl/datasets/score/to-mxml/process-pdv.rb +160 -0
- data/lib/musa-dsl/datasets/score/to-mxml/process-ps.rb +51 -0
- data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +153 -0
- data/lib/musa-dsl/datasets/score/to-mxml/to-mxml.rb +158 -0
- data/lib/musa-dsl/datasets/v.rb +23 -0
- data/lib/musa-dsl/generative.rb +5 -5
- data/lib/musa-dsl/generative/backboner.rb +274 -0
- data/lib/musa-dsl/generative/darwin.rb +102 -96
- data/lib/musa-dsl/generative/generative-grammar.rb +182 -187
- data/lib/musa-dsl/generative/markov.rb +56 -53
- data/lib/musa-dsl/generative/variatio.rb +234 -222
- data/lib/musa-dsl/logger.rb +1 -0
- data/lib/musa-dsl/logger/logger.rb +31 -0
- data/lib/musa-dsl/matrix.rb +1 -0
- data/lib/musa-dsl/matrix/matrix.rb +210 -0
- data/lib/musa-dsl/midi.rb +2 -2
- data/lib/musa-dsl/midi/midi-recorder.rb +54 -52
- data/lib/musa-dsl/midi/midi-voices.rb +187 -182
- data/lib/musa-dsl/music.rb +5 -5
- data/lib/musa-dsl/music/chord-definition.rb +54 -50
- data/lib/musa-dsl/music/chord-definitions.rb +13 -9
- data/lib/musa-dsl/music/chords.rb +236 -238
- data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +187 -183
- data/lib/musa-dsl/music/scales.rb +331 -332
- data/lib/musa-dsl/musicxml.rb +1 -0
- data/lib/musa-dsl/musicxml/builder/attributes.rb +155 -0
- data/lib/musa-dsl/musicxml/builder/backup-forward.rb +45 -0
- data/lib/musa-dsl/musicxml/builder/direction.rb +322 -0
- data/lib/musa-dsl/musicxml/builder/helper.rb +90 -0
- data/lib/musa-dsl/musicxml/builder/measure.rb +137 -0
- data/lib/musa-dsl/musicxml/builder/note-complexities.rb +152 -0
- data/lib/musa-dsl/musicxml/builder/note.rb +577 -0
- data/lib/musa-dsl/musicxml/builder/part-group.rb +44 -0
- data/lib/musa-dsl/musicxml/builder/part.rb +67 -0
- data/lib/musa-dsl/musicxml/builder/pitched-note.rb +126 -0
- data/lib/musa-dsl/musicxml/builder/rest.rb +117 -0
- data/lib/musa-dsl/musicxml/builder/score-partwise.rb +120 -0
- data/lib/musa-dsl/musicxml/builder/typed-text.rb +43 -0
- data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +112 -0
- data/lib/musa-dsl/neumalang.rb +1 -1
- data/lib/musa-dsl/neumalang/datatypes.citrus +79 -0
- data/lib/musa-dsl/neumalang/neuma.citrus +165 -0
- data/lib/musa-dsl/neumalang/neumalang.citrus +32 -242
- data/lib/musa-dsl/neumalang/neumalang.rb +373 -142
- data/lib/musa-dsl/neumalang/process.citrus +21 -0
- data/lib/musa-dsl/neumalang/terminals.citrus +67 -0
- data/lib/musa-dsl/neumalang/vectors.citrus +23 -0
- data/lib/musa-dsl/neumas.rb +5 -0
- data/lib/musa-dsl/neumas/array-to-neumas.rb +34 -0
- data/lib/musa-dsl/neumas/neuma-decoder.rb +63 -0
- data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +57 -0
- data/lib/musa-dsl/neumas/neuma-gdvd-decoder.rb +15 -0
- data/lib/musa-dsl/neumas/neumas.rb +37 -0
- data/lib/musa-dsl/neumas/string-to-neumas.rb +34 -0
- data/lib/musa-dsl/repl.rb +1 -1
- data/lib/musa-dsl/repl/repl.rb +128 -105
- data/lib/musa-dsl/sequencer.rb +1 -1
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-control.rb +163 -136
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +301 -286
- data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +552 -308
- data/lib/musa-dsl/sequencer/base-sequencer-public.rb +198 -176
- data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +77 -0
- data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +75 -0
- data/lib/musa-dsl/sequencer/sequencer-dsl.rb +105 -85
- data/lib/musa-dsl/sequencer/timeslots.rb +34 -0
- data/lib/musa-dsl/series.rb +1 -1
- data/lib/musa-dsl/{core-ext → series}/array-to-serie.rb +1 -1
- data/lib/musa-dsl/series/base-series.rb +171 -168
- data/lib/musa-dsl/series/hash-serie-splitter.rb +134 -132
- data/lib/musa-dsl/series/holder-serie.rb +1 -1
- data/lib/musa-dsl/series/main-serie-constructors.rb +6 -1
- data/lib/musa-dsl/series/main-serie-operations.rb +807 -797
- data/lib/musa-dsl/series/proxy-serie.rb +6 -6
- data/lib/musa-dsl/series/queue-serie.rb +5 -5
- data/lib/musa-dsl/series/series.rb +2 -0
- data/lib/musa-dsl/transcription.rb +4 -0
- data/lib/musa-dsl/transcription/from-gdv-to-midi.rb +227 -0
- data/lib/musa-dsl/transcription/from-gdv-to-musicxml.rb +36 -0
- data/lib/musa-dsl/transcription/from-gdv.rb +17 -0
- data/lib/musa-dsl/transcription/transcription.rb +55 -0
- data/lib/musa-dsl/transport.rb +6 -6
- data/lib/musa-dsl/transport/clock.rb +26 -26
- data/lib/musa-dsl/transport/dummy-clock.rb +32 -30
- data/lib/musa-dsl/transport/external-tick-clock.rb +21 -20
- data/lib/musa-dsl/transport/input-midi-clock.rb +82 -80
- data/lib/musa-dsl/transport/timer-clock.rb +72 -71
- data/lib/musa-dsl/transport/timer.rb +28 -26
- data/lib/musa-dsl/transport/transport.rb +111 -93
- data/musa-dsl.gemspec +3 -3
- metadata +73 -24
- data/lib/musa-dsl/core-ext/array-apply-get.rb +0 -18
- data/lib/musa-dsl/core-ext/array-to-neumas.rb +0 -28
- data/lib/musa-dsl/core-ext/as-context-run.rb +0 -44
- data/lib/musa-dsl/core-ext/duplicate.rb +0 -134
- data/lib/musa-dsl/core-ext/key-parameters-procedure-binder.rb +0 -85
- data/lib/musa-dsl/core-ext/proc-nice.rb +0 -13
- data/lib/musa-dsl/core-ext/send-nice.rb +0 -21
- data/lib/musa-dsl/core-ext/string-to-neumas.rb +0 -27
- data/lib/musa-dsl/datasets/gdv-decorators.rb +0 -221
- data/lib/musa-dsl/generative/rules.rb +0 -282
- data/lib/musa-dsl/neuma.rb +0 -1
- data/lib/musa-dsl/neuma/neuma.rb +0 -181
@@ -0,0 +1,210 @@
|
|
1
|
+
require_relative 'e'
|
2
|
+
|
3
|
+
require_relative 'score/queriable'
|
4
|
+
require_relative 'score/to-mxml/to-mxml'
|
5
|
+
require_relative 'score/render'
|
6
|
+
|
7
|
+
require_relative '../core-ext/inspect-nice'
|
8
|
+
|
9
|
+
module Musa::Datasets
|
10
|
+
class Score
|
11
|
+
include Enumerable
|
12
|
+
|
13
|
+
include AbsD
|
14
|
+
NaturalKeys = NaturalKeys.freeze
|
15
|
+
|
16
|
+
include ToMXML
|
17
|
+
include Queriable
|
18
|
+
include Render
|
19
|
+
|
20
|
+
using Musa::Extension::InspectNice
|
21
|
+
|
22
|
+
def initialize(hash = nil)
|
23
|
+
raise ArgumentError, "'hash' parameter should be a Hash with time and events information" unless hash.nil? || hash.is_a?(Hash)
|
24
|
+
|
25
|
+
@score = {}
|
26
|
+
@indexer = []
|
27
|
+
|
28
|
+
if hash
|
29
|
+
hash.sort.each do |k, v|
|
30
|
+
raise ArgumentError, "'hash' values for time #{k} should be an Array of events" unless v.is_a?(Array)
|
31
|
+
|
32
|
+
v.each do |vv|
|
33
|
+
at(k, add: vv)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def reset
|
40
|
+
@score.clear
|
41
|
+
@indexer.clear
|
42
|
+
end
|
43
|
+
|
44
|
+
def [](key)
|
45
|
+
if NaturalKeys.include?(key) && self.respond_to?(key)
|
46
|
+
self.send(key)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def finish
|
51
|
+
@indexer.collect { |i| i[:finish] }.max
|
52
|
+
end
|
53
|
+
|
54
|
+
def duration
|
55
|
+
(finish || 1r) - 1r
|
56
|
+
end
|
57
|
+
|
58
|
+
def at(time, add: nil)
|
59
|
+
time = time.rationalize
|
60
|
+
|
61
|
+
if add
|
62
|
+
raise ArgumentError, "#{add} is not a Abs dataset" unless add&.is_a?(Musa::Datasets::Abs)
|
63
|
+
|
64
|
+
slot = @score[time] ||= [].extend(QueryableByTimeSlot)
|
65
|
+
|
66
|
+
slot << add
|
67
|
+
|
68
|
+
@indexer << { start: time,
|
69
|
+
finish: time + add.duration.rationalize,
|
70
|
+
dataset: add }
|
71
|
+
|
72
|
+
nil
|
73
|
+
else
|
74
|
+
@score[time] ||= [].extend(QueryableByTimeSlot)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def size
|
79
|
+
@score.keys.size
|
80
|
+
end
|
81
|
+
|
82
|
+
def positions
|
83
|
+
@score.keys.sort
|
84
|
+
end
|
85
|
+
|
86
|
+
def each(&block)
|
87
|
+
@score.sort.each(&block)
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_h
|
91
|
+
@score.sort.to_h
|
92
|
+
end
|
93
|
+
|
94
|
+
def between(closed_interval_start, open_interval_finish)
|
95
|
+
@indexer
|
96
|
+
.select { |i| i[:start] < open_interval_finish && i[:finish] > closed_interval_start ||
|
97
|
+
closed_interval_start == open_interval_finish &&
|
98
|
+
i[:start] == i[:finish] &&
|
99
|
+
i[:start] == closed_interval_start }
|
100
|
+
.sort_by { |i| i[:start] }
|
101
|
+
.collect { |i| { start: i[:start],
|
102
|
+
finish: i[:finish],
|
103
|
+
start_in_interval: i[:start] > closed_interval_start ? i[:start] : closed_interval_start,
|
104
|
+
finish_in_interval: i[:finish] < open_interval_finish ? i[:finish] : open_interval_finish,
|
105
|
+
dataset: i[:dataset] } }.extend(QueryableByDataset)
|
106
|
+
end
|
107
|
+
|
108
|
+
# TODO hay que implementar un effective_start y effective_finish con el inicio/fin dentro del bar, no absoluto
|
109
|
+
|
110
|
+
def changes_between(closed_interval_start, open_interval_finish)
|
111
|
+
(
|
112
|
+
#
|
113
|
+
# we have a start event if the element
|
114
|
+
# begins between queried interval start (included) and interval finish (excluded)
|
115
|
+
#
|
116
|
+
@indexer
|
117
|
+
.select { |i| i[:start] >= closed_interval_start && i[:start] < open_interval_finish }
|
118
|
+
.collect { |i| i.clone.merge({ change: :start, time: i[:start] }) } +
|
119
|
+
|
120
|
+
#
|
121
|
+
# we have a finish event if the element interval finishes
|
122
|
+
# between queried interval start (excluded) and queried interval finish (included) or
|
123
|
+
# element interval finishes exactly on queried interval start
|
124
|
+
# but the element interval started before queried interval start
|
125
|
+
# (the element is not an instant)
|
126
|
+
#
|
127
|
+
@indexer
|
128
|
+
.select { |i| ( i[:finish] > closed_interval_start ||
|
129
|
+
i[:finish] == closed_interval_start && i[:finish] == i[:start]) &&
|
130
|
+
( i[:finish] < open_interval_finish ||
|
131
|
+
i[:finish] == open_interval_finish && i[:start] < open_interval_finish) }
|
132
|
+
.collect { |i| i.clone.merge({ change: :finish, time: i[:finish] }) } +
|
133
|
+
|
134
|
+
#
|
135
|
+
# when the queried interval has no duration (it's an instant) we have a start and a finish event
|
136
|
+
# if the element also is an instant exactly coincident with the queried interval
|
137
|
+
#
|
138
|
+
@indexer
|
139
|
+
.select { |i| ( closed_interval_start == open_interval_finish &&
|
140
|
+
i[:start] == closed_interval_start &&
|
141
|
+
i[:finish] == open_interval_finish) }
|
142
|
+
.collect { |i| [i.clone.merge({ change: :start, time: i[:start] }),
|
143
|
+
i.clone.merge({ change: :finish, time: i[:finish] })] }
|
144
|
+
.flatten(1)
|
145
|
+
)
|
146
|
+
.sort_by { |i| [ i[:time],
|
147
|
+
i[:start] < i[:finish] && i[:change] == :finish ? 0 : 1] }
|
148
|
+
.collect { |i| { change: i[:change],
|
149
|
+
time: i[:time],
|
150
|
+
start: i[:start],
|
151
|
+
finish: i[:finish],
|
152
|
+
start_in_interval: i[:start] > closed_interval_start ? i[:start] : closed_interval_start,
|
153
|
+
finish_in_interval: i[:finish] < open_interval_finish ? i[:finish] : open_interval_finish,
|
154
|
+
time_in_interval: if i[:time] < closed_interval_start
|
155
|
+
closed_interval_start
|
156
|
+
elsif i[:time] > open_interval_finish
|
157
|
+
open_interval_finish
|
158
|
+
else
|
159
|
+
i[:time]
|
160
|
+
end,
|
161
|
+
dataset: i[:dataset] } }.extend(QueryableByDataset)
|
162
|
+
end
|
163
|
+
|
164
|
+
def values_of(attribute)
|
165
|
+
values = Set[]
|
166
|
+
@score.each_value do |slot|
|
167
|
+
slot.each { |dataset| values << dataset[attribute] }
|
168
|
+
end
|
169
|
+
values
|
170
|
+
end
|
171
|
+
|
172
|
+
def subset
|
173
|
+
raise ArgumentError, "subset needs a block with the inclusion condition on the dataset" unless block_given?
|
174
|
+
|
175
|
+
filtered_score = Score.new
|
176
|
+
|
177
|
+
@score.each_pair do |time, datasets|
|
178
|
+
datasets.each do |dataset|
|
179
|
+
filtered_score.at time, add: dataset if yield(dataset)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
filtered_score
|
184
|
+
end
|
185
|
+
|
186
|
+
def inspect
|
187
|
+
s = StringIO.new
|
188
|
+
|
189
|
+
first_level1 = true
|
190
|
+
|
191
|
+
s.write "Musa::Datasets::Score.new({\n"
|
192
|
+
|
193
|
+
@score.each do |k, v|
|
194
|
+
s.write "#{ ", \n" unless first_level1 } #{ k.inspect } => [\n"
|
195
|
+
first_level1 = false
|
196
|
+
first_level2 = true
|
197
|
+
|
198
|
+
v.each do |vv|
|
199
|
+
s.write "#{ ", \n" unless first_level2 }\t#{ vv }"
|
200
|
+
first_level2 = false
|
201
|
+
end
|
202
|
+
|
203
|
+
s.write " ]"
|
204
|
+
end
|
205
|
+
s.write "\n})"
|
206
|
+
|
207
|
+
s.string
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Musa::Datasets; class Score
|
2
|
+
module Queriable
|
3
|
+
module QueryableByTimeSlot
|
4
|
+
def group_by_attribute(attribute)
|
5
|
+
group_by { |e| e[attribute] }.transform_values! { |e| e.extend(QueryableByTimeSlot) }
|
6
|
+
end
|
7
|
+
|
8
|
+
def select_by_attribute(attribute, value = nil)
|
9
|
+
if value.nil?
|
10
|
+
select { |e| !e[attribute].nil? }
|
11
|
+
else
|
12
|
+
select { |e| e[attribute] == value }
|
13
|
+
end.extend(QueryableByTimeSlot)
|
14
|
+
end
|
15
|
+
|
16
|
+
def sort_by_attribute(attribute)
|
17
|
+
select_by_attribute(attribute).sort_by { |e| e[attribute] }.extend(QueryableByTimeSlot)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private_constant :QueryableByTimeSlot
|
22
|
+
|
23
|
+
module QueryableByDataset
|
24
|
+
def group_by_attribute(attribute)
|
25
|
+
group_by { |e| e[:dataset][attribute] }.transform_values! { |e| e.extend(QueryableByDataset) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def select_by_attribute(attribute, value = nil)
|
29
|
+
if value.nil?
|
30
|
+
select { |e| !e[:dataset][attribute].nil? }
|
31
|
+
else
|
32
|
+
select { |e| e[:dataset][attribute] == value }
|
33
|
+
end.extend(QueryableByDataset)
|
34
|
+
end
|
35
|
+
|
36
|
+
def subset
|
37
|
+
raise ArgumentError, "subset needs a block with the inclusion condition on the dataset" unless block_given?
|
38
|
+
select { |e| yield e[:dataset] }.extend(QueryableByDataset)
|
39
|
+
end
|
40
|
+
|
41
|
+
def sort_by_attribute(attribute)
|
42
|
+
select_by_attribute(attribute).sort_by { |e| e[:dataset][attribute] }.extend(QueryableByDataset)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private_constant :QueryableByDataset
|
47
|
+
end
|
48
|
+
end; end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative '../../sequencer'
|
2
|
+
require_relative '../../series'
|
3
|
+
|
4
|
+
module Musa::Datasets; class Score
|
5
|
+
module Render
|
6
|
+
def render(on:, &block)
|
7
|
+
@score.keys.each do |score_at|
|
8
|
+
effective_wait = score_at - 1r
|
9
|
+
|
10
|
+
@score[score_at].each do |element|
|
11
|
+
case element
|
12
|
+
when Score
|
13
|
+
on.wait effective_wait do
|
14
|
+
element.render(on: on, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
when Abs
|
18
|
+
on.wait effective_wait do
|
19
|
+
block.call(element)
|
20
|
+
end
|
21
|
+
|
22
|
+
else
|
23
|
+
raise ArgumentError, "Can't sequence #{element} because it's not an Abs dataset"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end; end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'prime'
|
2
|
+
|
3
|
+
module Musa::Datasets::Score::ToMXML
|
4
|
+
private
|
5
|
+
|
6
|
+
def process_pdv(measure, bar, divisions_per_bar, element, pointer, logger, do_log)
|
7
|
+
|
8
|
+
pitch, octave, sharps = pitch_and_octave_and_sharps(element[:dataset])
|
9
|
+
|
10
|
+
continue_from_previous_bar = element[:start] < bar
|
11
|
+
continue_to_next_bar = element[:finish] > bar + 1r
|
12
|
+
|
13
|
+
effective_start = continue_from_previous_bar ? 0r : element[:start] - bar
|
14
|
+
|
15
|
+
effective_duration = continue_to_next_bar ?
|
16
|
+
(1r - effective_start) :
|
17
|
+
(element[:start] + element[:dataset][:duration] - (bar + effective_start))
|
18
|
+
|
19
|
+
effective_duration_decomposition = \
|
20
|
+
integrate_as_dotteable_durations(
|
21
|
+
decompose_as_sum_of_simple_durations(effective_duration))
|
22
|
+
|
23
|
+
if do_log
|
24
|
+
logger.debug "\nprocess_pdv #{element}"
|
25
|
+
logger.debug ""
|
26
|
+
logger.debug " pointer #{pointer} continue_from_previous #{continue_from_previous_bar} continue_to_next #{continue_to_next_bar}"
|
27
|
+
logger.debug " effective_start #{effective_start} effective_duration #{effective_duration}"
|
28
|
+
logger.debug " duration decomposition #{effective_duration_decomposition}"
|
29
|
+
end
|
30
|
+
|
31
|
+
if pointer > effective_start
|
32
|
+
duration_to_go_back = (pointer - effective_start)
|
33
|
+
|
34
|
+
logger.debug "\n -> adding backup #{duration_to_go_back * divisions_per_bar}" if do_log
|
35
|
+
|
36
|
+
measure.add_backup(duration_to_go_back * divisions_per_bar)
|
37
|
+
pointer -= duration_to_go_back
|
38
|
+
|
39
|
+
|
40
|
+
elsif pointer < effective_start
|
41
|
+
warn "\n -> adding start rest duration #{effective_start - pointer} start #{bar + pointer} finish #{bar + effective_start}" if do_log
|
42
|
+
|
43
|
+
pointer = process_pdv(measure, bar, divisions_per_bar,
|
44
|
+
{ start: bar + pointer,
|
45
|
+
finish: bar + effective_start,
|
46
|
+
dataset: { pitch: :silence, duration: effective_start - pointer }.extend(PDV) },
|
47
|
+
pointer,
|
48
|
+
logger, do_log)
|
49
|
+
end
|
50
|
+
|
51
|
+
# TODO generalize articulations and other musicxml elements
|
52
|
+
|
53
|
+
staccato = element[:dataset][:st] == 1 || element[:dataset][:st] == true
|
54
|
+
staccatissimo = element[:dataset][:st].is_a?(Numeric) && element[:dataset][:st] > 1
|
55
|
+
|
56
|
+
trill = !element[:dataset][:tr].nil?
|
57
|
+
|
58
|
+
mordent = [:down, :low].include?(element[:dataset][:mor])
|
59
|
+
inverted_mordent = [:up, true].include?(element[:dataset][:mor])
|
60
|
+
|
61
|
+
turn = [:up, true].include?(element[:dataset][:turn])
|
62
|
+
inverted_turn = [:down, :low].include?(element[:dataset][:turn])
|
63
|
+
|
64
|
+
first = true
|
65
|
+
|
66
|
+
until effective_duration_decomposition.empty?
|
67
|
+
duration = effective_duration_decomposition.shift
|
68
|
+
|
69
|
+
type, dots, tuplet_ratio = type_and_dots_and_tuplet_ratio(duration)
|
70
|
+
|
71
|
+
raise NotImplementedError,
|
72
|
+
"Found irregular time (tuplet ratio #{tuplet_ratio}) on element #{element}. " \
|
73
|
+
"Don't know how to handle on this version. " \
|
74
|
+
unless tuplet_ratio == 1
|
75
|
+
|
76
|
+
tied = if continue_from_previous_bar && first && !effective_duration_decomposition.empty?
|
77
|
+
'continue'
|
78
|
+
elsif continue_to_next_bar && effective_duration_decomposition.empty?
|
79
|
+
'continue'
|
80
|
+
elsif !first && !effective_duration_decomposition.empty?
|
81
|
+
'continue'
|
82
|
+
elsif first && !effective_duration_decomposition.empty?
|
83
|
+
'start'
|
84
|
+
elsif !first && effective_duration_decomposition.empty?
|
85
|
+
'stop'
|
86
|
+
else
|
87
|
+
nil
|
88
|
+
end
|
89
|
+
|
90
|
+
slur = if element[:dataset][:grace]
|
91
|
+
{ type: 'start', number: 2 }
|
92
|
+
elsif element[:dataset][:graced]
|
93
|
+
{ type: 'stop', number: 2 }
|
94
|
+
end
|
95
|
+
|
96
|
+
if pitch == :silence
|
97
|
+
measure.add_rest type: type,
|
98
|
+
dots: dots,
|
99
|
+
duration: (duration * divisions_per_bar).to_i,
|
100
|
+
voice: element[:dataset][:voice]
|
101
|
+
else
|
102
|
+
measure.add_pitch pitch, octave: octave, alter: sharps,
|
103
|
+
type: type,
|
104
|
+
dots: dots,
|
105
|
+
duration: (duration * divisions_per_bar).to_i,
|
106
|
+
grace: element[:dataset][:grace],
|
107
|
+
slur: slur,
|
108
|
+
tied: tied,
|
109
|
+
tie_stop: tied == 'stop' || tied == 'continue',
|
110
|
+
tie_start: tied == 'start' || tied == 'continue',
|
111
|
+
staccato: staccato,
|
112
|
+
staccatissimo: staccatissimo,
|
113
|
+
trill_mark: trill,
|
114
|
+
mordent: mordent,
|
115
|
+
inverted_mordent: inverted_mordent,
|
116
|
+
turn: turn,
|
117
|
+
inverted_turn: inverted_turn,
|
118
|
+
voice: element[:dataset][:voice]
|
119
|
+
end
|
120
|
+
|
121
|
+
first = false
|
122
|
+
pointer += duration unless element[:dataset][:grace]
|
123
|
+
end
|
124
|
+
|
125
|
+
pointer
|
126
|
+
end
|
127
|
+
|
128
|
+
def pitch_and_octave_and_sharps(pdv)
|
129
|
+
if pdv[:pitch] == :silence
|
130
|
+
[:silence, nil, nil]
|
131
|
+
else
|
132
|
+
p, s = [['C', 0], ['C', 1],
|
133
|
+
['D', 0], ['D', 1],
|
134
|
+
['E', 0],
|
135
|
+
['F', 0], ['F', 1],
|
136
|
+
['G', 0], ['G', 1],
|
137
|
+
['A', 0], ['A', 1],
|
138
|
+
['B', 0]][(pdv[:pitch] - 60) % 12]
|
139
|
+
|
140
|
+
o = 4 + ((pdv[:pitch] - 60).rationalize / 12r).floor
|
141
|
+
|
142
|
+
[p, o, s]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def dynamics_index_of(midi_velocity)
|
147
|
+
return nil unless midi_velocity
|
148
|
+
|
149
|
+
# ppp = midi 16 ... fff = midi 127
|
150
|
+
# mp = dynamics index 6; dynamics = 0..10
|
151
|
+
# TODO create a customizable MIDI velocity to score dynamics bidirectional conversor
|
152
|
+
[0..0, 1..1, 2..8, 9..16, 17..33, 34..49, 49..64, 65..80, 81..96, 97..112, 113..127]
|
153
|
+
.index { |r| r.cover? midi_velocity.round.to_i }
|
154
|
+
end
|
155
|
+
|
156
|
+
def dynamics_to_string(dynamics_index)
|
157
|
+
return nil unless dynamics_index
|
158
|
+
['pppppp', 'ppppp', 'pppp', 'ppp', 'pp', 'p', 'mp', 'mf', 'f', 'ff', 'fff'][dynamics_index.round.to_i]
|
159
|
+
end
|
160
|
+
end
|