musa-dsl 0.22.5 → 0.23.3

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -1
  3. data/lib/musa-dsl.rb +14 -8
  4. data/lib/musa-dsl/core-ext/deep-copy.rb +12 -1
  5. data/lib/musa-dsl/core-ext/inspect-nice.rb +1 -2
  6. data/lib/musa-dsl/core-ext/smart-proc-binder.rb +13 -11
  7. data/lib/musa-dsl/datasets/p.rb +38 -15
  8. data/lib/musa-dsl/datasets/score/to-mxml/process-pdv.rb +14 -12
  9. data/lib/musa-dsl/datasets/score/to-mxml/process-ps.rb +32 -6
  10. data/lib/musa-dsl/datasets/score/to-mxml/to-mxml.rb +24 -10
  11. data/lib/musa-dsl/generative/backboner.rb +6 -11
  12. data/lib/musa-dsl/generative/generative-grammar.rb +1 -3
  13. data/lib/musa-dsl/generative/markov.rb +10 -6
  14. data/lib/musa-dsl/logger/logger.rb +6 -1
  15. data/lib/musa-dsl/midi/midi-voices.rb +8 -7
  16. data/lib/musa-dsl/music/scales.rb +1 -1
  17. data/lib/musa-dsl/neumalang/neumalang.rb +1 -1
  18. data/lib/musa-dsl/neumas/array-to-neumas.rb +1 -1
  19. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +2 -2
  20. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-timed.rb +2 -1
  21. data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +2 -0
  22. data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +4 -2
  23. data/lib/musa-dsl/sequencer/sequencer-dsl.rb +6 -6
  24. data/lib/musa-dsl/series/base-series.rb +293 -144
  25. data/lib/musa-dsl/series/buffer-serie.rb +236 -0
  26. data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +145 -115
  27. data/lib/musa-dsl/series/main-serie-constructors.rb +249 -156
  28. data/lib/musa-dsl/series/main-serie-operations.rb +331 -318
  29. data/lib/musa-dsl/series/proxy-serie.rb +25 -41
  30. data/lib/musa-dsl/series/quantizer-serie.rb +38 -38
  31. data/lib/musa-dsl/series/queue-serie.rb +39 -43
  32. data/lib/musa-dsl/series/series-composer.rb +316 -0
  33. data/lib/musa-dsl/series/series.rb +5 -1
  34. data/lib/musa-dsl/series/timed-serie.rb +119 -130
  35. data/musa-dsl.gemspec +13 -3
  36. metadata +9 -9
  37. data/.ruby-version +0 -1
  38. data/lib/musa-dsl/series/holder-serie.rb +0 -87
@@ -1,69 +1,53 @@
1
- module Musa
2
- module Series
3
- # TODO: adapt to series prototyping
1
+ require_relative 'base-series'
4
2
 
3
+ module Musa
4
+ module Series::Constructors
5
5
  def PROXY(serie = nil)
6
6
  ProxySerie.new(serie)
7
7
  end
8
8
 
9
9
  class ProxySerie
10
- include Serie
11
-
12
- attr_reader :target
10
+ include Series::Serie.with(source: true, source_as: :proxy_source)
13
11
 
14
12
  def initialize(serie)
15
- @target = serie.instance if serie
16
- mark_as_instance!
17
- end
18
-
19
- def target=(target)
20
- @target = target.instance
21
- end
22
-
23
- def _prototype!
24
- raise PrototypingSerieError, 'Cannot get prototype of a proxy serie'
13
+ self.proxy_source = serie
14
+ init
25
15
  end
26
16
 
27
- def restart
28
- @target.restart if @target
17
+ private def _restart
18
+ @source.restart if @source
29
19
  end
30
20
 
31
- def current_value
32
- @target.current_value if @target
33
- end
34
-
35
- def next_value
36
- @target.next_value if @target
37
- end
38
-
39
- def peek_next_value
40
- @target.peek_next_value if @target
21
+ private def _next_value
22
+ @source.next_value if @source
41
23
  end
42
24
 
43
25
  def infinite?
44
- @target.infinite? if @target
26
+ @source.infinite? if @source
45
27
  end
46
28
 
47
- private
48
-
49
- def method_missing(method_name, *args, **key_args, &block)
50
- if @target && @target.respond_to?(method_name)
51
- @target.send method_name, *args, **key_args, &block
29
+ private def method_missing(method_name, *args, **key_args, &block)
30
+ if @source
31
+ if @source.respond_to?(method_name)
32
+ @source.send method_name, *args, **key_args, &block
33
+ else
34
+ raise NoMethodError, "undefined method '#{method_name}' for proxied #{@source.to_s}"
35
+ end
52
36
  else
53
37
  super
54
38
  end
55
39
  end
56
40
 
57
- def respond_to_missing?(method_name, include_private)
58
- @target && @target.respond_to?(method_name, include_private) # || super
41
+ private def respond_to_missing?(method_name, include_private)
42
+ @source && @source.respond_to?(method_name, include_private) # || super ??
59
43
  end
60
44
  end
45
+ end
61
46
 
62
- module SerieOperations
63
- # TODO add test case
64
- def proxied
65
- Series::ProxySerie.new self
66
- end
47
+ module Series::Operations
48
+ # TODO add test case
49
+ def proxy
50
+ Series::ProxySerie.new(self)
67
51
  end
68
52
  end
69
53
  end
@@ -1,34 +1,32 @@
1
1
  require_relative '../datasets/e'
2
2
  require_relative '../core-ext/inspect-nice'
3
3
 
4
+ require_relative 'base-series'
5
+
4
6
  # TODO remove debugging puts, intermediate hash comments on :info and InspectNice
5
7
  using Musa::Extension::InspectNice
6
8
 
7
9
  module Musa
8
- module Series
9
- module SerieOperations
10
- def quantize(reference: nil, step: nil,
11
- value_attribute: nil,
12
- stops: nil,
13
- predictive: nil,
14
- left_open: nil,
15
- right_open: nil)
16
-
17
- Series.QUANTIZE(self,
18
- reference: reference,
19
- step: step,
20
- value_attribute: value_attribute,
21
- stops: stops,
22
- predictive: predictive,
23
- left_open: left_open,
24
- right_open: right_open)
25
- end
10
+ module Series::Operations
11
+ def quantize(reference: nil, step: nil,
12
+ value_attribute: nil,
13
+ stops: nil,
14
+ predictive: nil,
15
+ left_open: nil,
16
+ right_open: nil)
17
+
18
+ Series.QUANTIZE(self,
19
+ reference: reference,
20
+ step: step,
21
+ value_attribute: value_attribute,
22
+ stops: stops,
23
+ predictive: predictive,
24
+ left_open: left_open,
25
+ right_open: right_open)
26
26
  end
27
27
  end
28
28
 
29
- module Series
30
- extend self
31
-
29
+ module Series::Constructors
32
30
  def QUANTIZE(time_value_serie,
33
31
  reference: nil, step: nil,
34
32
  value_attribute: nil,
@@ -82,28 +80,27 @@ module Musa
82
80
  private_constant :QuantizerTools
83
81
 
84
82
  class RawQuantizer
85
- include Serie
83
+ include Series::Serie.with(source: true)
86
84
  include QuantizerTools
87
85
 
88
86
  attr_reader :source
89
87
 
90
88
  def initialize(reference, step, source, value_attribute, stops, left_open, right_open)
89
+ self.source = source
90
+
91
91
  @reference = reference
92
92
  @step_size = step.abs
93
93
 
94
- @source = source
95
94
  @value_attribute = value_attribute
96
95
 
97
96
  @stops = stops
98
97
  @left_open = left_open
99
98
  @right_open = right_open
100
99
 
101
- _restart false
102
-
103
- mark_regarding! source
100
+ init
104
101
  end
105
102
 
106
- def _restart(restart_sources = true)
103
+ private def _init
107
104
  @last_processed_q_value = nil
108
105
  @last_processed_time = nil
109
106
 
@@ -111,11 +108,13 @@ module Musa
111
108
 
112
109
  @points = []
113
110
  @segments = []
111
+ end
114
112
 
115
- @source.restart if restart_sources
113
+ private def _restart
114
+ @source.restart
116
115
  end
117
116
 
118
- def _next_value
117
+ private def _next_value
119
118
  if @stops
120
119
  i = 2
121
120
 
@@ -348,16 +347,17 @@ module Musa
348
347
  private_constant :RawQuantizer
349
348
 
350
349
  class PredictiveQuantizer
351
- include Serie
350
+ include Series::Serie.with(source: true)
352
351
  include QuantizerTools
353
352
 
354
353
  attr_reader :source
355
354
 
356
355
  def initialize(reference, step, source, value_attribute, include_stops)
356
+ self.source = source
357
+
357
358
  @reference = reference
358
359
  @step_size = step
359
360
 
360
- @source = source
361
361
  @value_attribute = value_attribute
362
362
 
363
363
  @include_stops = include_stops
@@ -365,25 +365,25 @@ module Musa
365
365
  @halfway_offset = step / 2r
366
366
  @crossing_reference = reference - @halfway_offset
367
367
 
368
- _restart false
369
-
370
- mark_regarding! source
368
+ init
371
369
  end
372
370
 
373
- def _restart(restart_sources = true)
374
- @source.restart if restart_sources
375
-
371
+ private def _init
376
372
  @last_time = nil
377
373
  @crossings = []
378
374
 
379
375
  @first = true
380
376
  end
381
377
 
378
+ private def _restart
379
+ @source.restart
380
+ end
381
+
382
382
  def infinite?
383
383
  !!@source.infinite?
384
384
  end
385
385
 
386
- def _next_value
386
+ private def _next_value
387
387
  result = nil
388
388
 
389
389
  first_time, first_value = get_time_value(@source.peek_next_value) if @first
@@ -1,51 +1,53 @@
1
- module Musa
2
- module Series
3
- # TODO: adapt to series prototyping
1
+ require_relative 'base-series'
4
2
 
3
+ module Musa
4
+ module Series::Constructors
5
5
  def QUEUE(*series)
6
6
  QueueSerie.new(series)
7
7
  end
8
8
 
9
9
  class QueueSerie
10
- include Serie
11
-
12
- attr_reader :targets, :target
10
+ include Series::Serie.with(sources: true)
13
11
 
14
12
  def initialize(series)
15
- @targets = series.collect(&:instance)
16
- @targets ||= []
17
-
18
- mark_as_instance!
19
-
20
- _restart
13
+ self.sources = series
14
+ init
21
15
  end
22
16
 
23
17
  def <<(serie)
24
- @targets << serie.instance
25
- check_current
18
+ # when queue is a prototype it is also frozen so no serie can be added (it would raise an Exception if tried).
19
+ # when queue is an instance the added serie should also be an instance (raise an Exception otherwise)
20
+ #
21
+ raise ArgumentError, "Only an instance serie can be queued" unless serie.instance?
22
+
23
+ @sources << serie
24
+ @current ||= @sources[@index]
25
+
26
26
  self
27
27
  end
28
28
 
29
29
  def clear
30
- @targets.clear
31
- restart
30
+ @sources.clear
31
+ init
32
32
  self
33
33
  end
34
34
 
35
- def _prototype!
36
- raise PrototypingSerieError, 'Cannot get prototype of a proxy serie'
35
+ private def _init
36
+ @index = 0
37
+ @current = @sources[@index]
38
+ @restart_sources = false
37
39
  end
38
40
 
39
- def _restart
40
- @index = -1
41
- forward
41
+ private def _restart
42
+ @current.restart
43
+ @restart_sources = true
42
44
  end
43
45
 
44
- def _next_value
46
+ private def _next_value
45
47
  value = nil
46
48
 
47
- if @target
48
- value = @target.next_value
49
+ if @current
50
+ value = @current.next_value
49
51
 
50
52
  if value.nil?
51
53
  forward
@@ -57,38 +59,32 @@ module Musa
57
59
  end
58
60
 
59
61
  def infinite?
60
- !!@targets.find(&:infinite?)
62
+ !!@sources.find(&:infinite?)
61
63
  end
62
64
 
63
- private
64
-
65
- def forward
65
+ private def forward
66
66
  @index += 1
67
- @target = nil
68
- @target = @targets[@index].restart if @index < @targets.size
69
- end
70
-
71
- def check_current
72
- @target = @targets[@index].restart unless @target
67
+ @current = @sources[@index]
68
+ @current&.restart if @restart_sources
73
69
  end
74
70
 
75
- def method_missing(method_name, *args, **key_args, &block)
76
- if @target && @target.respond_to?(method_name)
77
- @target.send method_name, *args, **key_args, &block
71
+ private def method_missing(method_name, *args, **key_args, &block)
72
+ if @current&.respond_to?(method_name)
73
+ @current.send method_name, *args, **key_args, &block
78
74
  else
79
75
  super
80
76
  end
81
77
  end
82
78
 
83
- def respond_to_missing?(method_name, include_private)
84
- @target && @target.respond_to?(method_name, include_private) # || super
79
+ private def respond_to_missing?(method_name, include_private)
80
+ @current&.respond_to?(method_name, include_private) # || super
85
81
  end
86
82
  end
83
+ end
87
84
 
88
- module SerieOperations
89
- def queued
90
- Series::QueueSerie.new [self]
91
- end
85
+ module Series::Operations
86
+ def queued
87
+ Series::Constructors.QUEUE(self)
92
88
  end
93
89
  end
94
90
  end
@@ -0,0 +1,316 @@
1
+ require_relative 'base-series'
2
+
3
+ require_relative '../core-ext/with'
4
+
5
+ module Musa
6
+ module Series
7
+ module Composer
8
+ class Composer
9
+ using Musa::Extension::Arrayfy
10
+
11
+ def initialize(inputs: [:input], outputs: [:output], auto_commit: nil, &block)
12
+ auto_commit = true if auto_commit.nil?
13
+
14
+ @pipelines = {}
15
+
16
+ def @pipelines.[]=(name, pipeline)
17
+ pipeline_to_add = @commited ? pipeline.commit! : pipeline
18
+ super(name, pipeline_to_add)
19
+ end
20
+
21
+ @dsl = DSLContext.new(@pipelines)
22
+ @inputs = {}
23
+ @outputs = {}
24
+
25
+ inputs&.each do |input|
26
+ p = PROXY()
27
+ @inputs[input] = @pipelines[input] = Pipeline.new(input, input: p, output: p.buffered, pipelines: @pipelines)
28
+
29
+ @dsl.define_singleton_method(input) { input }
30
+ end
31
+
32
+ outputs&.each do |output|
33
+ p = PROXY()
34
+ @outputs[output] = @pipelines[output] = Pipeline.new(output, is_output: true, input: p, output: p, pipelines: @pipelines)
35
+
36
+ @dsl.define_singleton_method(output) { output }
37
+ end
38
+
39
+ @dsl.with &block if block
40
+ commit! if auto_commit
41
+ end
42
+
43
+ def input(name = nil)
44
+ name ||= :input
45
+ @inputs[name].input
46
+ end
47
+
48
+ def output(name = nil)
49
+ raise "Can't access output if the Composer is uncommited. Call '.commit' first." unless @commited
50
+
51
+ name ||= :output
52
+ @outputs[name].output
53
+ end
54
+
55
+ def route(from, to:, on: nil, as: nil)
56
+ @dsl.route(from, to: to, on: on, as: as)
57
+ end
58
+
59
+ def pipeline(name, *elements)
60
+ @dsl.pipeline(name, elements)
61
+ end
62
+
63
+ def update(&block)
64
+ @dsl.with &block
65
+ end
66
+
67
+ def commit!
68
+ raise 'Already commited' if @commited
69
+
70
+ @outputs.each_value do |pipeline|
71
+ pipeline.commit!
72
+ end
73
+
74
+ @commited = true
75
+ end
76
+
77
+ class Pipeline
78
+ def initialize(name, is_output: false, input: nil, output: nil, first_proc: nil, chain_proc: nil, pipelines:)
79
+ @name = name
80
+ @is_output = is_output
81
+ @input = input
82
+ @output = output
83
+ @first_proc = first_proc
84
+ @chain_proc = chain_proc
85
+ @routes = {}
86
+ @pipelines = pipelines
87
+ end
88
+
89
+ attr_reader :name, :is_output
90
+ attr_accessor :input, :output, :proc
91
+
92
+ def [](on, as)
93
+ @routes[[on, as]]
94
+ end
95
+
96
+ def []=(on, as, source)
97
+ @routes[[on, as]] = Route.new(on, as, source)
98
+ end
99
+
100
+ def commit!
101
+ first_serie_operation = @first_proc&.call(NIL())
102
+ @input ||= first_serie_operation
103
+
104
+ @routes.each_value do |route|
105
+ route.source.commit!
106
+
107
+ if @is_output
108
+ @input.proxy_source = route.source.output.buffer
109
+ elsif route.as
110
+ @input.send(route.on)[route.as] = route.source.output.buffer
111
+ else
112
+ @input.send("#{route.on.to_s}=".to_sym, route.source.output.buffer)
113
+ end
114
+ end
115
+
116
+ chain_serie_operation = @chain_proc&.call(@input) || @input
117
+ @output ||= chain_serie_operation.buffered
118
+
119
+ self
120
+ end
121
+ end
122
+
123
+ class Route
124
+ def initialize(on, as, source)
125
+ @on = on
126
+ @as = as
127
+ @source = source
128
+ end
129
+ attr_accessor :on, :as, :source
130
+ end
131
+
132
+ class DSLContext
133
+ include Musa::Extension::With
134
+
135
+ def initialize(pipelines)
136
+ @pipelines = pipelines
137
+ end
138
+
139
+ def route(from, to:, on: nil, as: nil)
140
+ from_pipeline = @pipelines[from]
141
+ to_pipeline = @pipelines[to]
142
+
143
+ raise ArgumentError, "Pipeline '#{from}' not found." unless from_pipeline
144
+ raise ArgumentError, "Pipeline '#{to}' not found." unless to_pipeline
145
+
146
+ if to_pipeline.is_output && (on || as)
147
+ raise ArgumentError, "Output pipeline #{to_pipeline.name} only allows default routing"
148
+ end
149
+
150
+ on ||= (as ? :sources : :source)
151
+
152
+ raise ArgumentError,
153
+ "Source of pipeline #{to} on #{on} as #{as} already connected to #{to_pipeline[on, as].source.name}" \
154
+ unless to_pipeline[on, as].nil?
155
+
156
+
157
+ to_pipeline[on, as] = from_pipeline
158
+ end
159
+
160
+ def pipeline(name, elements)
161
+ first, chain = parse(elements)
162
+ @pipelines[name] = Pipeline.new(name, first_proc: first, chain_proc: chain, pipelines: @pipelines)
163
+
164
+ define_singleton_method(name) { name }
165
+ end
166
+
167
+ private def parse(thing)
168
+ case thing
169
+ when Array
170
+ first = chain = nil
171
+
172
+ thing.each do |element|
173
+ case element
174
+ when Hash
175
+ new_chain = parse(element)
176
+ when Symbol
177
+ new_chain = operation_as_chained_proc(element, nil)
178
+ when Proc
179
+ new_chain = operation_as_chained_proc(:map, element)
180
+ else
181
+ raise ArgumentError, "Syntax error: don't know how to handle #{element}"
182
+ end
183
+
184
+ if first.nil?
185
+ first = new_chain unless first
186
+ else
187
+ chain = chain ? chain >> new_chain : new_chain
188
+ end
189
+ end
190
+
191
+ [first, chain]
192
+
193
+ when Hash
194
+ if thing.size == 1
195
+ operation = thing.first[0] # key
196
+ parameter = thing.first[1] # value
197
+
198
+ if is_a_series_constructor?(operation)
199
+ operation_as_chained_proc(operation, parameter)
200
+ else
201
+ operation_as_chained_proc(operation, parse(parameter))
202
+ end
203
+ else
204
+ raise ArgumentError, "Syntax error: don't know how to handle #{element}"
205
+ end
206
+
207
+ when Symbol
208
+ operation_as_chained_proc(operation)
209
+
210
+ when Proc
211
+ thing
212
+
213
+ else
214
+ thing
215
+ end
216
+ end
217
+
218
+ private def operation_as_chained_proc(operation, parameter = nil)
219
+ if is_a_series_constructor?(operation)
220
+ proc do |last|
221
+ call_constructor_according_to_last_and_parameter(last, operation, parameter)
222
+ end
223
+
224
+ elsif is_a_series_operation?(operation)
225
+ proc { |last| call_operation_according_to_parameter(last, operation, parameter) }
226
+
227
+ else
228
+ # non-series operation
229
+ proc { |last| call_operation_according_to_parameter(last, operation, parameter) }
230
+ end
231
+ end
232
+
233
+ private def call_constructor_according_to_last_and_parameter(last, constructor, parameter)
234
+ case last
235
+ when Proc
236
+ call_constructor_according_to_last_and_parameter(last.call, constructor, parameter)
237
+
238
+ when Serie
239
+ # TODO: ignoring last, should make an error?
240
+ Musa::Series::Constructors.method(constructor).call(*parameter)
241
+
242
+ when nil
243
+ Musa::Series::Constructors.method(constructor).call(*parameter)
244
+
245
+ when Array
246
+ raise "Unexpected parameter #{parameter} for constructor #{constructor} " \
247
+ "because the previous operation on the pipeline chain returned non-nil #{last}" \
248
+ unless parameter.nil?
249
+
250
+ Musa::Series::Constructors.method(constructor).call(*last)
251
+
252
+ when Hash
253
+ raise "Unexpected parameter #{parameter} for constructor #{constructor} " \
254
+ "because the previous operation on the pipeline chain returned non-nil #{last}" \
255
+ unless parameter.nil?
256
+
257
+ Musa::Series::Constructors.method(constructor).call(**last)
258
+
259
+ else
260
+ raise ArgumentError, "Don't know how to handle last #{last}"
261
+ end
262
+ end
263
+
264
+ private def call_operation_according_to_parameter(target, operation, parameter)
265
+ case parameter
266
+ when nil
267
+ target.send(operation)
268
+ when Symbol
269
+ target.send(operation).send(parameter)
270
+ when Proc
271
+ target.send(operation, &parameter)
272
+ when Array
273
+ unless parameter.size == 2 && parameter.all? { |_| _.is_a?(Proc) }
274
+ raise ArgumentError, "Don't know how to handle parameter #{parameter}"
275
+ end
276
+
277
+ target.send(operation, &(parameter.first >> parameter.last))
278
+ else
279
+ target.send(operation, parameter)
280
+ end
281
+ end
282
+
283
+ private def is_a_series_constructor?(operation)
284
+ Musa::Series::Constructors.instance_methods.include?(operation)
285
+ end
286
+
287
+ private def is_a_series_operation?(operation)
288
+ Musa::Series::Operations.instance_methods.include?(operation)
289
+ end
290
+
291
+ private def method_missing(symbol, *args, &block)
292
+ if is_a_series_constructor?(symbol) || is_a_series_operation?(symbol)
293
+ symbol
294
+ elsif args.any? || block
295
+ args += [block] if block
296
+ pipeline(symbol, args)
297
+ else # for non-series methods
298
+ symbol
299
+ end
300
+ end
301
+
302
+ private def respond_to_missing?(method_name, include_private = false)
303
+ Musa::Series::Operations.instance_methods.include?(method_name) ||
304
+ Musa::Series::Constructors.instance_methods.include?(method_name) ||
305
+ @pipelines.key?(method_name) ||
306
+ super
307
+ end
308
+ end
309
+
310
+ private_constant :Pipeline
311
+ private_constant :Route
312
+ private_constant :DSLContext
313
+ end
314
+ end
315
+ end
316
+ end