musa-dsl 0.14.31 → 0.21.4
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 +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 +122 -110
- 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 +554 -308
- data/lib/musa-dsl/sequencer/base-sequencer-public.rb +198 -176
- data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +75 -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 +89 -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,51 @@
|
|
1
|
+
module Musa::Datasets::Score::ToMXML
|
2
|
+
private
|
3
|
+
|
4
|
+
DynamicsContext = Struct.new(:last_dynamics)
|
5
|
+
private_constant :DynamicsContext
|
6
|
+
|
7
|
+
def process_ps(measure, element, context, logger, do_log)
|
8
|
+
context ||= DynamicsContext.new
|
9
|
+
|
10
|
+
logger.debug "\nprocess_ps #{element}" if do_log
|
11
|
+
|
12
|
+
case element[:dataset][:type]
|
13
|
+
when :crescendo, :diminuendo
|
14
|
+
if element[:change] == :start
|
15
|
+
dynamics = dynamics_to_string(element[:dataset][:from])
|
16
|
+
|
17
|
+
if dynamics != context.last_dynamics
|
18
|
+
measure.add_dynamics dynamics, placement: 'below' if dynamics && element[:dataset][:from] > 0
|
19
|
+
context.last_dynamics = dynamics
|
20
|
+
end
|
21
|
+
|
22
|
+
measure.add_wedge element[:dataset][:type],
|
23
|
+
niente: element[:dataset][:type] == :crescendo && element[:dataset][:from] == 0,
|
24
|
+
placement: 'below'
|
25
|
+
else
|
26
|
+
measure.add_wedge 'stop',
|
27
|
+
niente: element[:dataset][:type] == :diminuendo && element[:dataset][:to] == 0,
|
28
|
+
placement: 'below'
|
29
|
+
|
30
|
+
dynamics = dynamics_to_string(element[:dataset][:to])
|
31
|
+
|
32
|
+
measure.add_dynamics dynamics, placement: 'below' if dynamics && element[:dataset][:to] > 0
|
33
|
+
context.last_dynamics = dynamics
|
34
|
+
end
|
35
|
+
|
36
|
+
when :dynamics
|
37
|
+
dynamics = dynamics_to_string(element[:dataset][:from])
|
38
|
+
|
39
|
+
if dynamics != context.last_dynamics
|
40
|
+
measure.add_dynamics dynamics, placement: 'below'
|
41
|
+
context.last_dynamics = dynamics
|
42
|
+
end
|
43
|
+
|
44
|
+
else
|
45
|
+
# ignored
|
46
|
+
end
|
47
|
+
|
48
|
+
context
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'prime'
|
2
|
+
|
3
|
+
module Musa::Datasets::Score::ToMXML
|
4
|
+
private
|
5
|
+
|
6
|
+
class ElementDurationDecomposition
|
7
|
+
def initialize(element, bar, bar_size = 1r) # TODO remove (unused because of bad strategy to time groups)
|
8
|
+
@continue_from_previous_bar = element[:start] < bar
|
9
|
+
@continue_to_next_bar = element[:finish] >= bar + bar_size
|
10
|
+
|
11
|
+
@start = continue_from_previous_bar ? 0r : element[:start] - bar
|
12
|
+
|
13
|
+
@duration = continue_to_next_bar ?
|
14
|
+
(1r - @start) :
|
15
|
+
(element[:start] + element[:dataset][:duration] - (bar + start))
|
16
|
+
|
17
|
+
@duration_decomposition = integrate_as_dotteable_durations(decompose_as_sum_of_simple_durations(@duration))
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :continue_from_previous_bar, :continue_to_next_bar, :start, :duration, :duration_decomposition
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
"ElementDurationDecomposition(#{@duration}) = [#{@duration_decomposition}]"
|
24
|
+
end
|
25
|
+
|
26
|
+
alias inspect to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
private_constant :ElementDurationDecomposition
|
30
|
+
|
31
|
+
def time_and_tuplet_optimize(elements, bar, bar_size = 1r) # TODO remove (unused because of bad strategy to time groups)
|
32
|
+
decompositions = elements.collect { |pdv| ElementDurationDecomposition.new(pdv, bar, bar_size) }
|
33
|
+
|
34
|
+
denominators = decompositions.collect { |g| g.duration_decomposition.collect { |d| d.to_r.denominator } }.flatten.uniq
|
35
|
+
|
36
|
+
lcm_denominators = denominators.reduce(:lcm)
|
37
|
+
|
38
|
+
primes = Prime.prime_division(lcm_denominators)
|
39
|
+
|
40
|
+
factors = primes.collect { |base, exp| [base] * exp }.flatten
|
41
|
+
|
42
|
+
refactors = all_combinations(factors).collect { |a| a.reduce(&:*) }
|
43
|
+
|
44
|
+
# Y no se puede seguir con la descomposición
|
45
|
+
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def decompose_as_sum_of_simple_durations(duration)
|
50
|
+
return [] if duration.zero?
|
51
|
+
|
52
|
+
# TODO mejorar esta descomposición para que tenga menos factores redundantes
|
53
|
+
pd = Prime.prime_division(duration.to_r.denominator).collect { |base, exp| (1..exp).collect { |i| base ** i } }.flatten
|
54
|
+
|
55
|
+
divisors = ([[1]] + all_combinations(pd)).collect { |combination| combination.inject(:*) }
|
56
|
+
|
57
|
+
summands = []
|
58
|
+
|
59
|
+
while divisor = divisors.shift
|
60
|
+
c = Rational(1, divisor)
|
61
|
+
f = (duration / c).floor
|
62
|
+
n = f * c
|
63
|
+
summands << n unless n.zero?
|
64
|
+
duration -= n
|
65
|
+
end
|
66
|
+
|
67
|
+
raise ArgumentError, "#{duration} cannot be further decomposed" unless duration.zero?
|
68
|
+
|
69
|
+
summands
|
70
|
+
end
|
71
|
+
|
72
|
+
def all_combinations(numbers)
|
73
|
+
all_combinations = []
|
74
|
+
i = 1
|
75
|
+
until (combinations = numbers.combination(i).to_a).empty?
|
76
|
+
all_combinations += combinations
|
77
|
+
i += 1
|
78
|
+
end
|
79
|
+
|
80
|
+
all_combinations.uniq
|
81
|
+
end
|
82
|
+
|
83
|
+
def integrate_as_dotteable_durations(simple_durations)
|
84
|
+
integrated_durations = []
|
85
|
+
last = nil
|
86
|
+
simple_durations.each do |duration|
|
87
|
+
if last && duration == last / 2
|
88
|
+
integrated_durations[integrated_durations.size-1] += duration
|
89
|
+
else
|
90
|
+
integrated_durations << duration
|
91
|
+
end
|
92
|
+
last = duration
|
93
|
+
end
|
94
|
+
integrated_durations
|
95
|
+
end
|
96
|
+
|
97
|
+
def type_and_dots_and_tuplet_ratio(noteable_duration)
|
98
|
+
r = decompose_as_sum_of_simple_durations(noteable_duration)
|
99
|
+
n = r.shift
|
100
|
+
|
101
|
+
tuplet_ratio = Rational(n.denominator, nearest_lower_power_of_2(n.denominator))
|
102
|
+
|
103
|
+
type = type_of(nearest_upper_power_of_2(n))
|
104
|
+
dots = 0
|
105
|
+
|
106
|
+
while nn = r.shift
|
107
|
+
if nn == n / 2
|
108
|
+
dots += 1
|
109
|
+
n = nn
|
110
|
+
else
|
111
|
+
break
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
raise ArgumentError, "#{noteable_duration} cannot be decomposed as a duration with dots" unless r.empty?
|
116
|
+
|
117
|
+
return type, dots, tuplet_ratio
|
118
|
+
end
|
119
|
+
|
120
|
+
def nearest_upper_power_of_2(number)
|
121
|
+
return 0 if number.zero?
|
122
|
+
|
123
|
+
exp = Math.log2(number)
|
124
|
+
exp_floor = exp.floor
|
125
|
+
plus = exp > exp_floor ? 1 : 0
|
126
|
+
|
127
|
+
2 ** (exp_floor + plus)
|
128
|
+
end
|
129
|
+
|
130
|
+
def nearest_lower_power_of_2(number)
|
131
|
+
return 0 if number.zero?
|
132
|
+
|
133
|
+
exp_floor = Math.log2(number).floor
|
134
|
+
|
135
|
+
2 ** exp_floor
|
136
|
+
end
|
137
|
+
|
138
|
+
def type_of(base_type_duration)
|
139
|
+
duration_log2i = Math.log2(base_type_duration)
|
140
|
+
|
141
|
+
raise ArgumentError, "#{base_type_duration} is not a inverse power of 2 (i.e. 2, 1, 1/2, 1/4, 1/8, 1/64, etc)" \
|
142
|
+
unless base_type_duration == 2 ** duration_log2i
|
143
|
+
|
144
|
+
raise ArgumentError, "#{base_type_duration} is not between 1024th and maxima accepted durations" \
|
145
|
+
unless duration_log2i >= -10 && duration_log2i <= 3
|
146
|
+
|
147
|
+
['1024th', '512th', '256th', '128th',
|
148
|
+
'64th', '32nd', '16th', 'eighth',
|
149
|
+
'quarter', 'half', 'whole', 'breve',
|
150
|
+
'long', 'maxima'][duration_log2i + 10]
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require_relative '../../../logger'
|
2
|
+
require_relative '../../../musicxml'
|
3
|
+
|
4
|
+
require_relative 'process-time'
|
5
|
+
require_relative 'process-pdv'
|
6
|
+
require_relative 'process-ps'
|
7
|
+
|
8
|
+
module Musa::Datasets; class Score
|
9
|
+
module ToMXML
|
10
|
+
include Musa::MusicXML::Builder
|
11
|
+
include Musa::Datasets
|
12
|
+
|
13
|
+
def to_mxml(beats_per_bar, ticks_per_beat,
|
14
|
+
bpm: nil,
|
15
|
+
title: nil,
|
16
|
+
creators: nil,
|
17
|
+
encoding_date: nil,
|
18
|
+
parts:,
|
19
|
+
logger: nil,
|
20
|
+
do_log: nil)
|
21
|
+
|
22
|
+
bpm ||= 90
|
23
|
+
title ||= 'Untitled'
|
24
|
+
creators ||= { composer: 'Unknown' }
|
25
|
+
logger ||= Musa::Logger::Logger.new
|
26
|
+
do_log ||= nil
|
27
|
+
|
28
|
+
mxml = ScorePartwise.new do |_|
|
29
|
+
_.work_title title
|
30
|
+
_.creators **creators
|
31
|
+
_.encoding_date encoding_date if encoding_date
|
32
|
+
|
33
|
+
parts.each_pair do |id, part_info|
|
34
|
+
_.part id,
|
35
|
+
name: part_info&.[](:name),
|
36
|
+
abbreviation: part_info&.[](:abbreviation) do |_|
|
37
|
+
|
38
|
+
_.measure do |_|
|
39
|
+
_.attributes do |_|
|
40
|
+
_.divisions ticks_per_beat
|
41
|
+
|
42
|
+
i = 0
|
43
|
+
(part_info&.[](:clefs) || { g: 2 }).each_pair do |clef, line|
|
44
|
+
i += 1
|
45
|
+
_.clef i, sign: clef.upcase, line: line
|
46
|
+
_.time i, beats: beats_per_bar, beat_type: 4
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
_.metronome placement: 'above', beat_unit: 'quarter', per_minute: bpm
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
if do_log
|
57
|
+
logger.debug"\nscore.to_mxl log:"
|
58
|
+
logger.debug "-----------------"
|
59
|
+
end
|
60
|
+
|
61
|
+
parts.each_key do |part_id|
|
62
|
+
fill_part mxml.parts[part_id], beats_per_bar * ticks_per_beat, (parts.size > 1 ? part_id : nil), logger, do_log
|
63
|
+
end
|
64
|
+
|
65
|
+
mxml
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def fill_part(part, divisions_per_bar, instrument, logger, do_log)
|
71
|
+
measure = nil
|
72
|
+
dynamics_context = nil
|
73
|
+
|
74
|
+
(1..finish || 0).each do |bar|
|
75
|
+
if do_log
|
76
|
+
logger.debug ""
|
77
|
+
logger.debug msg = "filling part #{part.name} (#{instrument}): processing bar #{bar}"
|
78
|
+
logger.debug "-" * msg.size
|
79
|
+
end
|
80
|
+
|
81
|
+
measure = part.add_measure if measure
|
82
|
+
measure ||= part.measures.last
|
83
|
+
|
84
|
+
pointer = 0r
|
85
|
+
|
86
|
+
instrument_score = subset { |dataset| dataset[:instrument] == instrument }
|
87
|
+
|
88
|
+
bar_elements = \
|
89
|
+
(instrument_score.changes_between(bar, bar + 1).select { |p| p[:dataset].is_a?(PS) } +
|
90
|
+
(pdvs = instrument_score.between(bar, bar + 1).select { |p| p[:dataset].is_a?(PDV) }))
|
91
|
+
.sort_by { |e| [ e[:time_in_interval] || e[:start_in_interval],
|
92
|
+
e[:dataset].is_a?(PS) ? 0 : 1 ] }
|
93
|
+
|
94
|
+
if pdvs.empty?
|
95
|
+
logger.debug "\nadded full bar silence" if do_log
|
96
|
+
|
97
|
+
process_pdv(measure, bar, divisions_per_bar,
|
98
|
+
{ start: bar,
|
99
|
+
finish: bar + 1,
|
100
|
+
dataset: { pitch: :silence, duration: 1 }.extend(PDV) },
|
101
|
+
pointer,
|
102
|
+
logger,
|
103
|
+
do_log)
|
104
|
+
else
|
105
|
+
first = bar_elements.first
|
106
|
+
|
107
|
+
logger.debug "\nfirst element #{first}" if do_log
|
108
|
+
|
109
|
+
# TODO habrá que arreglar el cálculo de pointer cuando haya avances y retrocesos para que
|
110
|
+
# TODO no añada silencios incorrectos al principio o al final
|
111
|
+
|
112
|
+
if (first[:time_in_interval] || first[:start_in_interval]) > bar
|
113
|
+
|
114
|
+
silence_duration = first[:start_in_interval] - bar
|
115
|
+
|
116
|
+
logger.debug "\nadded initial silence for duration #{silence_duration}" if do_log
|
117
|
+
|
118
|
+
pointer = process_pdv(measure, bar, divisions_per_bar,
|
119
|
+
{ start: bar,
|
120
|
+
finish: first[:start_in_interval],
|
121
|
+
dataset: { pitch: :silence, duration: silence_duration }.extend(PDV) },
|
122
|
+
pointer,
|
123
|
+
logger,
|
124
|
+
do_log)
|
125
|
+
end
|
126
|
+
|
127
|
+
bar_elements.each do |element|
|
128
|
+
case element[:dataset]
|
129
|
+
when PDV
|
130
|
+
pointer = process_pdv(measure, bar, divisions_per_bar, element, pointer, logger, do_log)
|
131
|
+
|
132
|
+
when PS
|
133
|
+
dynamics_context = process_ps(measure, element, dynamics_context, logger, do_log)
|
134
|
+
|
135
|
+
else
|
136
|
+
# ignored
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
if pointer < 1r
|
141
|
+
silence_duration = 1r - pointer
|
142
|
+
|
143
|
+
logger.debug "\nadded ending silence for duration #{silence_duration}" if do_log
|
144
|
+
|
145
|
+
process_pdv(measure, bar, divisions_per_bar,
|
146
|
+
{ start: bar + pointer,
|
147
|
+
finish: bar + 1 - Rational(1, divisions_per_bar),
|
148
|
+
dataset: { pitch: :silence, duration: silence_duration }.extend(PDV) },
|
149
|
+
pointer,
|
150
|
+
logger,
|
151
|
+
do_log)
|
152
|
+
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end; end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative 'dataset'
|
2
|
+
require_relative 'packed-v'
|
3
|
+
|
4
|
+
module Musa::Datasets
|
5
|
+
module V
|
6
|
+
include AbsI
|
7
|
+
|
8
|
+
def to_packed_V(mapper)
|
9
|
+
case mapper
|
10
|
+
when Hash
|
11
|
+
pv = {}.extend(PackedV)
|
12
|
+
each_index { |i| pv[mapper.keys[i]] = self[i] unless self[i] == mapper.values[i] }
|
13
|
+
pv
|
14
|
+
when Array
|
15
|
+
pv = {}.extend(PackedV)
|
16
|
+
each_index { |i| pv[mapper[i]] = self[i] if mapper[i] && self[i] }
|
17
|
+
pv
|
18
|
+
else
|
19
|
+
raise ArgumentError, "Expected Hash or Array as mapper but got a #{mapper.class.name}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/musa-dsl/generative.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
require_relative 'generative/variatio'
|
2
|
+
require_relative 'generative/darwin'
|
3
|
+
require_relative 'generative/backboner'
|
4
|
+
require_relative 'generative/markov'
|
5
|
+
require_relative 'generative/generative-grammar'
|
@@ -0,0 +1,274 @@
|
|
1
|
+
require_relative '../core-ext/smart-proc-binder'
|
2
|
+
require_relative '../core-ext/with'
|
3
|
+
|
4
|
+
using Musa::Extension::Arrayfy
|
5
|
+
|
6
|
+
# incluir With -> hecho
|
7
|
+
# eliminar method_missing
|
8
|
+
# crear rama tb debe recibir la serie de la history -> ya lo hace
|
9
|
+
# crear rama puede repetirse (hasta terminar según ended_when) -> no
|
10
|
+
#
|
11
|
+
# hacer que pueda funcionar en tiempo real? le vas suministrando seeds y le vas diciendo qué opción has elegido (p.ej. para hacer un armonizador en tiempo real)
|
12
|
+
# esto mismo sería aplicable en otros generadores? variatio/darwin? generative-grammar? markov?
|
13
|
+
|
14
|
+
module Musa
|
15
|
+
module Backboner
|
16
|
+
class Backboner
|
17
|
+
include Musa::Extension::With
|
18
|
+
|
19
|
+
def initialize(&block)
|
20
|
+
@context = RulesEvalContext.new(&block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def generate_possibilities(object, confirmed_node = nil, node = nil, grow_rules = nil)
|
24
|
+
node ||= Node.new
|
25
|
+
grow_rules ||= @context._grow_rules
|
26
|
+
|
27
|
+
history = confirmed_node.history if confirmed_node
|
28
|
+
history ||= []
|
29
|
+
|
30
|
+
grow_rules = grow_rules.clone
|
31
|
+
grow_rule = grow_rules.shift
|
32
|
+
|
33
|
+
if grow_rule
|
34
|
+
grow_rule.generate_possibilities(object, history).each do |new_object|
|
35
|
+
new_node = Node.new new_object, node
|
36
|
+
new_node.mark_as_ended! if @context._ended? new_object
|
37
|
+
|
38
|
+
rejection = @context._cut_rules.find { |cut_rule| cut_rule.rejects?(new_object, history) }
|
39
|
+
# TODO: include rejection secondary reasons in rejection message
|
40
|
+
|
41
|
+
new_node.reject! rejection if rejection
|
42
|
+
|
43
|
+
node.children << new_node
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
unless grow_rules.empty?
|
48
|
+
node.children.each do |node|
|
49
|
+
generate_possibilities node.object, confirmed_node, node, grow_rules unless node.rejected || node.ended?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
node
|
54
|
+
end
|
55
|
+
|
56
|
+
def apply(object_or_list, node = nil)
|
57
|
+
list = object_or_list.arrayfy.clone
|
58
|
+
|
59
|
+
node ||= Node.new
|
60
|
+
|
61
|
+
seed = list.shift
|
62
|
+
|
63
|
+
if seed
|
64
|
+
result = generate_possibilities seed, node
|
65
|
+
|
66
|
+
fished = result.fish
|
67
|
+
|
68
|
+
node.reject! 'All children are rejected' if fished.empty?
|
69
|
+
|
70
|
+
fished.each do |object|
|
71
|
+
subnode = node.add(object).mark_as_ended!
|
72
|
+
apply list, subnode
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
node
|
77
|
+
end
|
78
|
+
|
79
|
+
class RulesEvalContext
|
80
|
+
include Musa::Extension::With
|
81
|
+
|
82
|
+
attr_reader :_grow_rules, :_ended_when, :_cut_rules
|
83
|
+
|
84
|
+
def initialize(&block)
|
85
|
+
with &block
|
86
|
+
end
|
87
|
+
|
88
|
+
def grow(name, &block)
|
89
|
+
@_grow_rules ||= []
|
90
|
+
@_grow_rules << GrowRule.new(name, &block)
|
91
|
+
self
|
92
|
+
end
|
93
|
+
|
94
|
+
def ended_when(&block)
|
95
|
+
@_ended_when = block
|
96
|
+
self
|
97
|
+
end
|
98
|
+
|
99
|
+
def cut(reason, &block)
|
100
|
+
@_cut_rules ||= []
|
101
|
+
@_cut_rules << CutRule.new(reason, &block)
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
def _ended?(object)
|
106
|
+
instance_exec object, &@_ended_when
|
107
|
+
end
|
108
|
+
|
109
|
+
class GrowRule
|
110
|
+
attr_reader :name
|
111
|
+
|
112
|
+
def initialize(name, &block)
|
113
|
+
@name = name
|
114
|
+
@block = block
|
115
|
+
end
|
116
|
+
|
117
|
+
def generate_possibilities(object, history)
|
118
|
+
# TODO: optimize context using only one instance for all genereate_possibilities calls
|
119
|
+
context = GrowRuleEvalContext.new
|
120
|
+
context.with object, history, &@block
|
121
|
+
|
122
|
+
context._branches
|
123
|
+
end
|
124
|
+
|
125
|
+
class GrowRuleEvalContext
|
126
|
+
include Musa::Extension::With
|
127
|
+
|
128
|
+
attr_reader :_branches
|
129
|
+
|
130
|
+
def initialize
|
131
|
+
@_branches = []
|
132
|
+
end
|
133
|
+
|
134
|
+
def branch(object)
|
135
|
+
@_branches << object
|
136
|
+
self
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
private_constant :GrowRuleEvalContext
|
141
|
+
end
|
142
|
+
|
143
|
+
private_constant :GrowRule
|
144
|
+
|
145
|
+
class CutRule
|
146
|
+
attr_reader :reason
|
147
|
+
|
148
|
+
def initialize(reason, &block)
|
149
|
+
@reason = reason
|
150
|
+
@block = block
|
151
|
+
end
|
152
|
+
|
153
|
+
def rejects?(object, history)
|
154
|
+
# TODO: optimize context using only one instance for all rejects? checks
|
155
|
+
context = CutRuleEvalContext.new
|
156
|
+
context.with object, history, &@block
|
157
|
+
|
158
|
+
reasons = context._secondary_reasons.collect { |_| ("#{@reason} (#{_})" if _) || @reason }
|
159
|
+
|
160
|
+
reasons.empty? ? nil : reasons
|
161
|
+
end
|
162
|
+
|
163
|
+
class CutRuleEvalContext
|
164
|
+
include Musa::Extension::With
|
165
|
+
|
166
|
+
attr_reader :_secondary_reasons
|
167
|
+
|
168
|
+
def initialize
|
169
|
+
@_secondary_reasons = []
|
170
|
+
end
|
171
|
+
|
172
|
+
def prune(secondary_reason = nil)
|
173
|
+
@_secondary_reasons << secondary_reason
|
174
|
+
self
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
private_constant :CutRuleEvalContext
|
179
|
+
end
|
180
|
+
|
181
|
+
private_constant :CutRule
|
182
|
+
end
|
183
|
+
|
184
|
+
private_constant :RulesEvalContext
|
185
|
+
|
186
|
+
class Node
|
187
|
+
attr_reader :parent, :children, :object, :rejected
|
188
|
+
|
189
|
+
def initialize(object = nil, parent = nil)
|
190
|
+
@parent = parent
|
191
|
+
@children = []
|
192
|
+
@object = object
|
193
|
+
|
194
|
+
@ended = false
|
195
|
+
@rejected = nil
|
196
|
+
end
|
197
|
+
|
198
|
+
def add(object)
|
199
|
+
Node.new(object, self).tap { |n| @children << n }
|
200
|
+
end
|
201
|
+
|
202
|
+
def reject!(rejection)
|
203
|
+
@rejected = rejection
|
204
|
+
self
|
205
|
+
end
|
206
|
+
|
207
|
+
def mark_as_ended!
|
208
|
+
@children.each(&:update_rejection_by_children!)
|
209
|
+
|
210
|
+
if !@children.empty? && !@children.find { |n| !n.rejected }
|
211
|
+
reject! "Node rejected because all children are rejected"
|
212
|
+
end
|
213
|
+
|
214
|
+
@ended = true
|
215
|
+
|
216
|
+
self
|
217
|
+
end
|
218
|
+
|
219
|
+
def ended?
|
220
|
+
@ended
|
221
|
+
end
|
222
|
+
|
223
|
+
def history
|
224
|
+
objects = []
|
225
|
+
n = self
|
226
|
+
while n && n.object
|
227
|
+
objects << n.object
|
228
|
+
n = n.parent
|
229
|
+
end
|
230
|
+
|
231
|
+
objects.reverse
|
232
|
+
end
|
233
|
+
|
234
|
+
def fish
|
235
|
+
fished = []
|
236
|
+
|
237
|
+
@children.each do |node|
|
238
|
+
unless node.rejected
|
239
|
+
if node.ended?
|
240
|
+
fished << node.object
|
241
|
+
else
|
242
|
+
fished += node.fish
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
fished
|
248
|
+
end
|
249
|
+
|
250
|
+
def combinations(parent_combination = nil)
|
251
|
+
parent_combination ||= []
|
252
|
+
|
253
|
+
combinations = []
|
254
|
+
|
255
|
+
unless rejected
|
256
|
+
if @children.empty?
|
257
|
+
combinations << parent_combination
|
258
|
+
else
|
259
|
+
@children.each do |node|
|
260
|
+
node.combinations(parent_combination + [node.object]).each do |object|
|
261
|
+
combinations << object
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
combinations
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
private_constant :Node
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|