musa-dsl 0.22.5 → 0.23.3

Sign up to get free protection for your applications and to get access to all the features.
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