musa-dsl 0.14.32 → 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (129) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/README.md +5 -1
  4. data/lib/musa-dsl.rb +54 -11
  5. data/lib/musa-dsl/core-ext.rb +7 -13
  6. data/lib/musa-dsl/core-ext/array-explode-ranges.rb +15 -23
  7. data/lib/musa-dsl/core-ext/arrayfy.rb +30 -12
  8. data/lib/musa-dsl/core-ext/attribute-builder.rb +194 -0
  9. data/lib/musa-dsl/core-ext/deep-copy.rb +185 -0
  10. data/lib/musa-dsl/core-ext/dynamic-proxy.rb +44 -40
  11. data/lib/musa-dsl/core-ext/inspect-nice.rb +40 -22
  12. data/lib/musa-dsl/core-ext/smart-proc-binder.rb +108 -0
  13. data/lib/musa-dsl/core-ext/with.rb +26 -0
  14. data/lib/musa-dsl/datasets.rb +8 -3
  15. data/lib/musa-dsl/datasets/dataset.rb +3 -0
  16. data/lib/musa-dsl/datasets/delta-d.rb +12 -0
  17. data/lib/musa-dsl/datasets/e.rb +61 -0
  18. data/lib/musa-dsl/datasets/gdv.rb +51 -411
  19. data/lib/musa-dsl/datasets/gdvd.rb +179 -0
  20. data/lib/musa-dsl/datasets/helper.rb +41 -0
  21. data/lib/musa-dsl/datasets/p.rb +68 -0
  22. data/lib/musa-dsl/datasets/packed-v.rb +19 -0
  23. data/lib/musa-dsl/datasets/pdv.rb +22 -15
  24. data/lib/musa-dsl/datasets/ps.rb +113 -0
  25. data/lib/musa-dsl/datasets/score.rb +210 -0
  26. data/lib/musa-dsl/datasets/score/queriable.rb +48 -0
  27. data/lib/musa-dsl/datasets/score/render.rb +31 -0
  28. data/lib/musa-dsl/datasets/score/to-mxml/process-pdv.rb +160 -0
  29. data/lib/musa-dsl/datasets/score/to-mxml/process-ps.rb +51 -0
  30. data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +153 -0
  31. data/lib/musa-dsl/datasets/score/to-mxml/to-mxml.rb +158 -0
  32. data/lib/musa-dsl/datasets/v.rb +23 -0
  33. data/lib/musa-dsl/generative.rb +5 -5
  34. data/lib/musa-dsl/generative/backboner.rb +274 -0
  35. data/lib/musa-dsl/generative/darwin.rb +102 -96
  36. data/lib/musa-dsl/generative/generative-grammar.rb +182 -187
  37. data/lib/musa-dsl/generative/markov.rb +56 -53
  38. data/lib/musa-dsl/generative/variatio.rb +234 -222
  39. data/lib/musa-dsl/logger.rb +1 -0
  40. data/lib/musa-dsl/logger/logger.rb +31 -0
  41. data/lib/musa-dsl/matrix.rb +1 -0
  42. data/lib/musa-dsl/matrix/matrix.rb +210 -0
  43. data/lib/musa-dsl/midi.rb +2 -2
  44. data/lib/musa-dsl/midi/midi-recorder.rb +54 -52
  45. data/lib/musa-dsl/midi/midi-voices.rb +183 -182
  46. data/lib/musa-dsl/music.rb +5 -5
  47. data/lib/musa-dsl/music/chord-definition.rb +54 -50
  48. data/lib/musa-dsl/music/chord-definitions.rb +13 -9
  49. data/lib/musa-dsl/music/chords.rb +236 -238
  50. data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +187 -183
  51. data/lib/musa-dsl/music/scales.rb +331 -332
  52. data/lib/musa-dsl/musicxml.rb +1 -0
  53. data/lib/musa-dsl/musicxml/builder/attributes.rb +155 -0
  54. data/lib/musa-dsl/musicxml/builder/backup-forward.rb +45 -0
  55. data/lib/musa-dsl/musicxml/builder/direction.rb +322 -0
  56. data/lib/musa-dsl/musicxml/builder/helper.rb +90 -0
  57. data/lib/musa-dsl/musicxml/builder/measure.rb +137 -0
  58. data/lib/musa-dsl/musicxml/builder/note-complexities.rb +152 -0
  59. data/lib/musa-dsl/musicxml/builder/note.rb +577 -0
  60. data/lib/musa-dsl/musicxml/builder/part-group.rb +44 -0
  61. data/lib/musa-dsl/musicxml/builder/part.rb +67 -0
  62. data/lib/musa-dsl/musicxml/builder/pitched-note.rb +126 -0
  63. data/lib/musa-dsl/musicxml/builder/rest.rb +117 -0
  64. data/lib/musa-dsl/musicxml/builder/score-partwise.rb +120 -0
  65. data/lib/musa-dsl/musicxml/builder/typed-text.rb +43 -0
  66. data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +112 -0
  67. data/lib/musa-dsl/neumalang.rb +1 -1
  68. data/lib/musa-dsl/neumalang/datatypes.citrus +79 -0
  69. data/lib/musa-dsl/neumalang/neuma.citrus +165 -0
  70. data/lib/musa-dsl/neumalang/neumalang.citrus +32 -242
  71. data/lib/musa-dsl/neumalang/neumalang.rb +373 -142
  72. data/lib/musa-dsl/neumalang/process.citrus +21 -0
  73. data/lib/musa-dsl/neumalang/terminals.citrus +67 -0
  74. data/lib/musa-dsl/neumalang/vectors.citrus +23 -0
  75. data/lib/musa-dsl/neumas.rb +5 -0
  76. data/lib/musa-dsl/neumas/array-to-neumas.rb +34 -0
  77. data/lib/musa-dsl/neumas/neuma-decoder.rb +63 -0
  78. data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +57 -0
  79. data/lib/musa-dsl/neumas/neuma-gdvd-decoder.rb +15 -0
  80. data/lib/musa-dsl/neumas/neumas.rb +37 -0
  81. data/lib/musa-dsl/neumas/string-to-neumas.rb +33 -0
  82. data/lib/musa-dsl/repl.rb +1 -1
  83. data/lib/musa-dsl/repl/repl.rb +103 -110
  84. data/lib/musa-dsl/sequencer.rb +1 -1
  85. data/lib/musa-dsl/sequencer/base-sequencer-implementation-control.rb +163 -136
  86. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +301 -286
  87. data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +548 -321
  88. data/lib/musa-dsl/sequencer/base-sequencer-public.rb +198 -176
  89. data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +77 -0
  90. data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +75 -0
  91. data/lib/musa-dsl/sequencer/sequencer-dsl.rb +105 -85
  92. data/lib/musa-dsl/sequencer/timeslots.rb +34 -0
  93. data/lib/musa-dsl/series.rb +1 -1
  94. data/lib/musa-dsl/{core-ext → series}/array-to-serie.rb +1 -1
  95. data/lib/musa-dsl/series/base-series.rb +171 -168
  96. data/lib/musa-dsl/series/hash-serie-splitter.rb +134 -132
  97. data/lib/musa-dsl/series/holder-serie.rb +1 -1
  98. data/lib/musa-dsl/series/main-serie-constructors.rb +6 -1
  99. data/lib/musa-dsl/series/main-serie-operations.rb +807 -797
  100. data/lib/musa-dsl/series/proxy-serie.rb +6 -6
  101. data/lib/musa-dsl/series/queue-serie.rb +5 -5
  102. data/lib/musa-dsl/series/series.rb +2 -0
  103. data/lib/musa-dsl/transcription.rb +4 -0
  104. data/lib/musa-dsl/transcription/from-gdv-to-midi.rb +227 -0
  105. data/lib/musa-dsl/transcription/from-gdv-to-musicxml.rb +36 -0
  106. data/lib/musa-dsl/transcription/from-gdv.rb +17 -0
  107. data/lib/musa-dsl/transcription/transcription.rb +55 -0
  108. data/lib/musa-dsl/transport.rb +6 -6
  109. data/lib/musa-dsl/transport/clock.rb +26 -26
  110. data/lib/musa-dsl/transport/dummy-clock.rb +32 -30
  111. data/lib/musa-dsl/transport/external-tick-clock.rb +21 -20
  112. data/lib/musa-dsl/transport/input-midi-clock.rb +82 -80
  113. data/lib/musa-dsl/transport/timer-clock.rb +72 -71
  114. data/lib/musa-dsl/transport/timer.rb +28 -26
  115. data/lib/musa-dsl/transport/transport.rb +100 -95
  116. data/musa-dsl.gemspec +3 -3
  117. metadata +73 -24
  118. data/lib/musa-dsl/core-ext/array-apply-get.rb +0 -18
  119. data/lib/musa-dsl/core-ext/array-to-neumas.rb +0 -28
  120. data/lib/musa-dsl/core-ext/as-context-run.rb +0 -44
  121. data/lib/musa-dsl/core-ext/duplicate.rb +0 -134
  122. data/lib/musa-dsl/core-ext/key-parameters-procedure-binder.rb +0 -85
  123. data/lib/musa-dsl/core-ext/proc-nice.rb +0 -13
  124. data/lib/musa-dsl/core-ext/send-nice.rb +0 -21
  125. data/lib/musa-dsl/core-ext/string-to-neumas.rb +0 -27
  126. data/lib/musa-dsl/datasets/gdv-decorators.rb +0 -221
  127. data/lib/musa-dsl/generative/rules.rb +0 -282
  128. data/lib/musa-dsl/neuma.rb +0 -1
  129. 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