musa-dsl 0.14.16
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 +7 -0
- data/.gitignore +10 -0
- data/Gemfile +20 -0
- data/LICENSE.md +157 -0
- data/README.md +8 -0
- data/lib/musa-dsl/core-ext/array-apply-get.rb +18 -0
- data/lib/musa-dsl/core-ext/array-explode-ranges.rb +29 -0
- data/lib/musa-dsl/core-ext/array-to-neumas.rb +28 -0
- data/lib/musa-dsl/core-ext/array-to-serie.rb +20 -0
- data/lib/musa-dsl/core-ext/arrayfy.rb +15 -0
- data/lib/musa-dsl/core-ext/as-context-run.rb +44 -0
- data/lib/musa-dsl/core-ext/duplicate.rb +134 -0
- data/lib/musa-dsl/core-ext/dynamic-proxy.rb +55 -0
- data/lib/musa-dsl/core-ext/inspect-nice.rb +28 -0
- data/lib/musa-dsl/core-ext/key-parameters-procedure-binder.rb +85 -0
- data/lib/musa-dsl/core-ext/proc-nice.rb +13 -0
- data/lib/musa-dsl/core-ext/send-nice.rb +21 -0
- data/lib/musa-dsl/core-ext/string-to-neumas.rb +27 -0
- data/lib/musa-dsl/core-ext.rb +13 -0
- data/lib/musa-dsl/datasets/gdv-decorators.rb +221 -0
- data/lib/musa-dsl/datasets/gdv.rb +499 -0
- data/lib/musa-dsl/datasets/pdv.rb +44 -0
- data/lib/musa-dsl/datasets.rb +5 -0
- data/lib/musa-dsl/generative/darwin.rb +145 -0
- data/lib/musa-dsl/generative/generative-grammar.rb +294 -0
- data/lib/musa-dsl/generative/markov.rb +78 -0
- data/lib/musa-dsl/generative/rules.rb +282 -0
- data/lib/musa-dsl/generative/variatio.rb +331 -0
- data/lib/musa-dsl/generative.rb +5 -0
- data/lib/musa-dsl/midi/midi-recorder.rb +83 -0
- data/lib/musa-dsl/midi/midi-voices.rb +274 -0
- data/lib/musa-dsl/midi.rb +2 -0
- data/lib/musa-dsl/music/chord-definition.rb +99 -0
- data/lib/musa-dsl/music/chord-definitions.rb +13 -0
- data/lib/musa-dsl/music/chords.rb +326 -0
- data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +204 -0
- data/lib/musa-dsl/music/scales.rb +584 -0
- data/lib/musa-dsl/music.rb +6 -0
- data/lib/musa-dsl/neuma/neuma.rb +181 -0
- data/lib/musa-dsl/neuma.rb +1 -0
- data/lib/musa-dsl/neumalang/neumalang.citrus +294 -0
- data/lib/musa-dsl/neumalang/neumalang.rb +179 -0
- data/lib/musa-dsl/neumalang.rb +3 -0
- data/lib/musa-dsl/repl/repl.rb +143 -0
- data/lib/musa-dsl/repl.rb +1 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-control.rb +189 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +354 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +382 -0
- data/lib/musa-dsl/sequencer/base-sequencer-public.rb +261 -0
- data/lib/musa-dsl/sequencer/sequencer-dsl.rb +94 -0
- data/lib/musa-dsl/sequencer/sequencer.rb +3 -0
- data/lib/musa-dsl/sequencer.rb +1 -0
- data/lib/musa-dsl/series/base-series.rb +245 -0
- data/lib/musa-dsl/series/hash-serie-splitter.rb +194 -0
- data/lib/musa-dsl/series/holder-serie.rb +87 -0
- data/lib/musa-dsl/series/main-serie-constructors.rb +726 -0
- data/lib/musa-dsl/series/main-serie-operations.rb +1151 -0
- data/lib/musa-dsl/series/proxy-serie.rb +69 -0
- data/lib/musa-dsl/series/queue-serie.rb +94 -0
- data/lib/musa-dsl/series/series.rb +8 -0
- data/lib/musa-dsl/series.rb +1 -0
- data/lib/musa-dsl/transport/clock.rb +36 -0
- data/lib/musa-dsl/transport/dummy-clock.rb +47 -0
- data/lib/musa-dsl/transport/external-tick-clock.rb +31 -0
- data/lib/musa-dsl/transport/input-midi-clock.rb +124 -0
- data/lib/musa-dsl/transport/timer-clock.rb +102 -0
- data/lib/musa-dsl/transport/timer.rb +40 -0
- data/lib/musa-dsl/transport/transport.rb +137 -0
- data/lib/musa-dsl/transport.rb +9 -0
- data/lib/musa-dsl.rb +17 -0
- data/musa-dsl.gemspec +17 -0
- metadata +174 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
module Musa
|
2
|
+
module Series
|
3
|
+
# TODO: adapt to series prototyping
|
4
|
+
|
5
|
+
def PROXY(serie = nil)
|
6
|
+
ProxySerie.new(serie)
|
7
|
+
end
|
8
|
+
|
9
|
+
class ProxySerie
|
10
|
+
include Serie
|
11
|
+
|
12
|
+
attr_reader :target
|
13
|
+
|
14
|
+
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'
|
25
|
+
end
|
26
|
+
|
27
|
+
def restart
|
28
|
+
@target.restart if @target
|
29
|
+
end
|
30
|
+
|
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
|
41
|
+
end
|
42
|
+
|
43
|
+
def infinite?
|
44
|
+
@target.infinite? if @target
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def method_missing(method_name, *args, **key_args, &block)
|
50
|
+
if @target && @target.respond_to?(method_name)
|
51
|
+
@target.send_nice method_name, *args, **key_args, &block
|
52
|
+
else
|
53
|
+
super
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def respond_to_missing?(method_name, include_private)
|
58
|
+
@target && @target.respond_to?(method_name, include_private) # || super
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
module SerieOperations
|
64
|
+
# TODO add test case
|
65
|
+
def proxied
|
66
|
+
Series::ProxySerie.new self
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Musa
|
2
|
+
module Series
|
3
|
+
# TODO: adapt to series prototyping
|
4
|
+
|
5
|
+
def QUEUE(*series)
|
6
|
+
QueueSerie.new(series)
|
7
|
+
end
|
8
|
+
|
9
|
+
class QueueSerie
|
10
|
+
include Serie
|
11
|
+
|
12
|
+
attr_reader :targets, :target
|
13
|
+
|
14
|
+
def initialize(series)
|
15
|
+
@targets = series.collect(&:instance)
|
16
|
+
@targets ||= []
|
17
|
+
|
18
|
+
mark_as_instance!
|
19
|
+
|
20
|
+
_restart
|
21
|
+
end
|
22
|
+
|
23
|
+
def <<(serie)
|
24
|
+
@targets << serie.instance
|
25
|
+
check_current
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def clear
|
30
|
+
@targets.clear
|
31
|
+
restart
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def _prototype
|
36
|
+
raise PrototypingSerieError, 'Cannot get prototype of a proxy serie'
|
37
|
+
end
|
38
|
+
|
39
|
+
def _restart
|
40
|
+
@index = -1
|
41
|
+
forward
|
42
|
+
end
|
43
|
+
|
44
|
+
def _next_value
|
45
|
+
value = nil
|
46
|
+
|
47
|
+
if @target
|
48
|
+
value = @target.next_value
|
49
|
+
|
50
|
+
if value.nil?
|
51
|
+
forward
|
52
|
+
value = next_value
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
value
|
57
|
+
end
|
58
|
+
|
59
|
+
def infinite?
|
60
|
+
!!@targets.find(&:infinite?)
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def forward
|
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
|
73
|
+
end
|
74
|
+
|
75
|
+
def method_missing(method_name, *args, **key_args, &block)
|
76
|
+
if @target && @target.respond_to?(method_name)
|
77
|
+
@target.send_nice method_name, *args, **key_args, &block
|
78
|
+
else
|
79
|
+
super
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def respond_to_missing?(method_name, include_private)
|
84
|
+
@target && @target.respond_to?(method_name, include_private) # || super
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
module SerieOperations
|
90
|
+
def queued
|
91
|
+
Series::QueueSerie.new [self]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'musa-dsl/series/series'
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'nibbler'
|
2
|
+
|
3
|
+
module Musa
|
4
|
+
class Clock
|
5
|
+
def initialize
|
6
|
+
@run = nil
|
7
|
+
@on_start = []
|
8
|
+
@on_stop = []
|
9
|
+
@on_song_position_pointer = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def running?
|
13
|
+
@run
|
14
|
+
end
|
15
|
+
|
16
|
+
def on_start(&block)
|
17
|
+
@on_start << block
|
18
|
+
end
|
19
|
+
|
20
|
+
def on_stop(&block)
|
21
|
+
@on_stop << block
|
22
|
+
end
|
23
|
+
|
24
|
+
def on_song_position_pointer(&block)
|
25
|
+
@on_song_position_pointer << block
|
26
|
+
end
|
27
|
+
|
28
|
+
def run
|
29
|
+
raise NotImplementedError
|
30
|
+
end
|
31
|
+
|
32
|
+
def terminate
|
33
|
+
raise NotImplementedError
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'musa-dsl/transport/clock'
|
2
|
+
|
3
|
+
module Musa
|
4
|
+
class DummyClock < Clock
|
5
|
+
def initialize(ticks = nil, do_log: nil, &block)
|
6
|
+
do_log ||= false
|
7
|
+
|
8
|
+
super()
|
9
|
+
|
10
|
+
raise ArgumentError, 'Cannot initialize with ticks and block. You can only use one of the parameters.' if ticks && block
|
11
|
+
|
12
|
+
@ticks = ticks
|
13
|
+
@do_log = do_log
|
14
|
+
@block = block
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_accessor :block, :ticks
|
18
|
+
|
19
|
+
def run
|
20
|
+
@on_start.each(&:call)
|
21
|
+
@run = true
|
22
|
+
|
23
|
+
while @run && eval_condition
|
24
|
+
yield if block_given?
|
25
|
+
|
26
|
+
Thread.pass
|
27
|
+
end
|
28
|
+
|
29
|
+
@on_stop.each(&:call)
|
30
|
+
end
|
31
|
+
|
32
|
+
def terminate
|
33
|
+
@run = false
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def eval_condition
|
39
|
+
if @ticks
|
40
|
+
@ticks -= 1
|
41
|
+
@ticks > 0
|
42
|
+
else
|
43
|
+
@block.call
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'musa-dsl/transport/clock'
|
2
|
+
require 'nibbler'
|
3
|
+
|
4
|
+
module Musa
|
5
|
+
class ExternalTickClock < Clock
|
6
|
+
def initialize(do_log: nil)
|
7
|
+
do_log ||= false
|
8
|
+
|
9
|
+
super()
|
10
|
+
|
11
|
+
@do_log = do_log
|
12
|
+
end
|
13
|
+
|
14
|
+
def run(&block)
|
15
|
+
@on_start.each(&:call)
|
16
|
+
@run = true
|
17
|
+
@block = block
|
18
|
+
end
|
19
|
+
|
20
|
+
def tick
|
21
|
+
if @run
|
22
|
+
@block.call if @block
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def terminate
|
27
|
+
@on_stop.each(&:call)
|
28
|
+
@run = false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'musa-dsl/transport/clock'
|
2
|
+
require 'nibbler'
|
3
|
+
|
4
|
+
module Musa
|
5
|
+
class InputMidiClock < Clock
|
6
|
+
def initialize(input, do_log: nil)
|
7
|
+
do_log ||= false
|
8
|
+
|
9
|
+
super()
|
10
|
+
|
11
|
+
@input = input
|
12
|
+
@do_log = do_log
|
13
|
+
|
14
|
+
@nibbler = Nibbler.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
@run = true
|
19
|
+
|
20
|
+
while @run
|
21
|
+
raw_messages = @input.gets
|
22
|
+
@input.buffer.clear
|
23
|
+
|
24
|
+
messages = []
|
25
|
+
stop_index = nil
|
26
|
+
|
27
|
+
raw_messages.each do |message|
|
28
|
+
mm = @nibbler.parse message[:data]
|
29
|
+
|
30
|
+
if mm
|
31
|
+
if mm.is_a? Array
|
32
|
+
mm.each do |m|
|
33
|
+
stop_index = messages.size if m.name == 'Stop' && !stop_index
|
34
|
+
messages << m
|
35
|
+
end
|
36
|
+
else
|
37
|
+
stop_index = messages.size if mm.name == 'Stop' && !stop_index
|
38
|
+
messages << mm
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
@nibbler.processed.clear
|
44
|
+
@nibbler.rejected.clear
|
45
|
+
@nibbler.messages.clear
|
46
|
+
|
47
|
+
size = messages.size
|
48
|
+
index = 0
|
49
|
+
while index < size
|
50
|
+
if index == stop_index && size >= index + 3 &&
|
51
|
+
messages[index + 1].name == 'Song Position Pointer' &&
|
52
|
+
messages[index + 2].name == 'Continue'
|
53
|
+
|
54
|
+
warn 'InputMidiClock: processing Stop + Song Position Pointer + Continue...' if @do_log
|
55
|
+
|
56
|
+
process_start unless @started
|
57
|
+
|
58
|
+
process_message messages[index + 1] do
|
59
|
+
yield if block_given?
|
60
|
+
end
|
61
|
+
|
62
|
+
index += 2
|
63
|
+
|
64
|
+
warn 'InputMidiClock: processing Stop + Song Position Pointer + Continue... done' if @do_log
|
65
|
+
|
66
|
+
else
|
67
|
+
process_message messages[index] do
|
68
|
+
yield if block_given?
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
index += 1
|
73
|
+
end
|
74
|
+
|
75
|
+
Thread.pass
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def terminate
|
80
|
+
@run = false
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def process_start
|
86
|
+
warn 'InputMidiClock: processing Start...' if @do_log
|
87
|
+
|
88
|
+
@on_start.each(&:call)
|
89
|
+
@started = true
|
90
|
+
|
91
|
+
warn 'InputMidiClock: processing Start... done' if @do_log
|
92
|
+
end
|
93
|
+
|
94
|
+
def process_message(m)
|
95
|
+
case m.name
|
96
|
+
when 'Start'
|
97
|
+
process_start
|
98
|
+
|
99
|
+
when 'Stop'
|
100
|
+
warn 'InputMidiClock: processing Stop...' if @do_log
|
101
|
+
|
102
|
+
@on_stop.each(&:call)
|
103
|
+
@started = false
|
104
|
+
|
105
|
+
warn 'InputMidiClock: processing Stop... done' if @do_log
|
106
|
+
|
107
|
+
when 'Continue'
|
108
|
+
warn 'InputMidiClock: processing Continue...' if @do_log
|
109
|
+
warn 'InputMidiClock: processing Continue... done' if @do_log
|
110
|
+
|
111
|
+
when 'Clock'
|
112
|
+
yield if block_given? && @started
|
113
|
+
|
114
|
+
when 'Song Position Pointer'
|
115
|
+
midi_beat_position =
|
116
|
+
m.data[0] & 0x7F | ((m.data[1] & 0x7F) << 7)
|
117
|
+
|
118
|
+
warn "InputMidiClock: processing Song Position Pointer midi_beat_position #{midi_beat_position}..." if @do_log
|
119
|
+
@on_song_position_pointer.each { |block| block.call midi_beat_position }
|
120
|
+
warn "InputMidiClock: processing Song Position Pointer midi_beat_position #{midi_beat_position}... done" if @do_log
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'musa-dsl/transport/clock'
|
2
|
+
require 'nibbler'
|
3
|
+
|
4
|
+
module Musa
|
5
|
+
class TimerClock < Clock
|
6
|
+
def initialize(period = nil, ticks_per_beat: nil, bpm: nil, correction: nil, do_log: nil)
|
7
|
+
do_log ||= false
|
8
|
+
|
9
|
+
super()
|
10
|
+
|
11
|
+
@correction = correction
|
12
|
+
|
13
|
+
self.period = period if period
|
14
|
+
self.ticks_per_beat = ticks_per_beat if ticks_per_beat
|
15
|
+
self.bpm = bpm if bpm
|
16
|
+
|
17
|
+
self.bpm ||= 120
|
18
|
+
self.ticks_per_beat ||= 24
|
19
|
+
|
20
|
+
@started = false
|
21
|
+
@paused = false
|
22
|
+
|
23
|
+
@do_log = do_log
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :period, :ticks_per_beat, :bpm
|
27
|
+
|
28
|
+
def period=(period_in_seconds)
|
29
|
+
@period = period_in_seconds.rationalize
|
30
|
+
@bpm = 60r / (@period * @ticks_per_beat) if @period && @ticks_per_beat
|
31
|
+
@timer.period = @period if @timer
|
32
|
+
end
|
33
|
+
|
34
|
+
def ticks_per_beat=(ticks)
|
35
|
+
@ticks_per_beat = ticks.rationalize
|
36
|
+
@period = 60r / (@bpm * @ticks_per_beat) if @bpm && @ticks_per_beat
|
37
|
+
@timer.period = @period if @timer && @period
|
38
|
+
end
|
39
|
+
|
40
|
+
def bpm=(bpm)
|
41
|
+
@bpm = bpm.rationalize
|
42
|
+
@period = 60r / (@bpm * @ticks_per_beat) if @bpm && @ticks_per_beat
|
43
|
+
@timer.period = @period if @timer && @period
|
44
|
+
end
|
45
|
+
|
46
|
+
def started?
|
47
|
+
@started
|
48
|
+
end
|
49
|
+
|
50
|
+
def paused?
|
51
|
+
@paused
|
52
|
+
end
|
53
|
+
|
54
|
+
def run
|
55
|
+
@run = true
|
56
|
+
|
57
|
+
while @run
|
58
|
+
@timer = Timer.new(@period, correction: @correction, stop: true)
|
59
|
+
|
60
|
+
@timer.run do
|
61
|
+
yield if block_given?
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def start
|
67
|
+
unless @started
|
68
|
+
@on_start.each(&:call)
|
69
|
+
@started = true
|
70
|
+
@paused = false
|
71
|
+
@timer.continue
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def stop
|
76
|
+
if @started
|
77
|
+
@timer.stop
|
78
|
+
@started = false
|
79
|
+
@paused = false
|
80
|
+
@on_stop.each(&:call)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def pause
|
85
|
+
if @started && !@paused
|
86
|
+
@timer.stop
|
87
|
+
@paused = true
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def continue
|
92
|
+
if @started && @paused
|
93
|
+
@paused = false
|
94
|
+
@timer.continue
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def terminate
|
99
|
+
@run = false
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Musa
|
2
|
+
class Timer
|
3
|
+
attr_accessor :period
|
4
|
+
|
5
|
+
def initialize(period_in_seconds, correction: nil, stop: nil)
|
6
|
+
@period = period_in_seconds.rationalize
|
7
|
+
@correction = (correction || 0r).rationalize
|
8
|
+
@stop ||= false
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
@thread = Thread.current
|
13
|
+
|
14
|
+
@next_moment = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
15
|
+
|
16
|
+
loop do
|
17
|
+
unless @stop
|
18
|
+
yield
|
19
|
+
|
20
|
+
@next_moment += @period
|
21
|
+
to_sleep = (@next_moment + @correction) - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
22
|
+
|
23
|
+
sleep to_sleep if to_sleep > 0.0
|
24
|
+
end
|
25
|
+
|
26
|
+
sleep if @stop
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def stop
|
31
|
+
@stop = true
|
32
|
+
end
|
33
|
+
|
34
|
+
def continue
|
35
|
+
@stop = false
|
36
|
+
@next_moment = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
37
|
+
@thread.run
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'musa-dsl/sequencer'
|
2
|
+
|
3
|
+
require 'musa-dsl/core-ext/key-parameters-procedure-binder'
|
4
|
+
|
5
|
+
module Musa
|
6
|
+
class Transport
|
7
|
+
attr_reader :sequencer
|
8
|
+
|
9
|
+
def initialize(clock,
|
10
|
+
beats_per_bar = nil,
|
11
|
+
ticks_per_beat = nil,
|
12
|
+
before_begin: nil,
|
13
|
+
on_start: nil,
|
14
|
+
after_stop: nil,
|
15
|
+
before_each_tick: nil,
|
16
|
+
on_position_change: nil,
|
17
|
+
do_log: nil)
|
18
|
+
|
19
|
+
beats_per_bar ||= 4
|
20
|
+
ticks_per_beat ||= 24
|
21
|
+
do_log ||= false
|
22
|
+
|
23
|
+
@clock = clock
|
24
|
+
|
25
|
+
@before_begin = []
|
26
|
+
@before_begin << KeyParametersProcedureBinder.new(before_begin) if before_begin
|
27
|
+
|
28
|
+
@on_start = []
|
29
|
+
@on_start << KeyParametersProcedureBinder.new(on_start) if on_start
|
30
|
+
|
31
|
+
@before_each_tick = []
|
32
|
+
@before_each_tick << KeyParametersProcedureBinder.new(before_each_tick) if before_each_tick
|
33
|
+
|
34
|
+
@on_position_change = []
|
35
|
+
@on_position_change << KeyParametersProcedureBinder.new(on_position_change) if on_position_change
|
36
|
+
|
37
|
+
@after_stop = []
|
38
|
+
@after_stop << KeyParametersProcedureBinder.new(after_stop) if after_stop
|
39
|
+
|
40
|
+
@do_log = do_log
|
41
|
+
|
42
|
+
@sequencer = Sequencer.new beats_per_bar, ticks_per_beat, do_log: @do_log
|
43
|
+
|
44
|
+
@clock.on_start do
|
45
|
+
do_on_start
|
46
|
+
end
|
47
|
+
|
48
|
+
@clock.on_stop do
|
49
|
+
do_stop
|
50
|
+
end
|
51
|
+
|
52
|
+
@clock.on_song_position_pointer do |midi_beat_position|
|
53
|
+
position = Rational(midi_beat_position, 4 * beats_per_bar) + 1
|
54
|
+
tick_before_position = position - Rational(1, beats_per_bar * ticks_per_beat)
|
55
|
+
|
56
|
+
warn "Transport: received message position change to #{position}" if @do_log
|
57
|
+
|
58
|
+
start_again_later = false
|
59
|
+
|
60
|
+
if @sequencer.position > tick_before_position
|
61
|
+
do_stop
|
62
|
+
start_again_later = true
|
63
|
+
end
|
64
|
+
|
65
|
+
warn "Transport: setting sequencer position #{tick_before_position}" if @do_log
|
66
|
+
@sequencer.position = tick_before_position
|
67
|
+
|
68
|
+
@sequencer.raw_at position, force_first: true do
|
69
|
+
@on_position_change.each { |block| block.call @sequencer }
|
70
|
+
end
|
71
|
+
|
72
|
+
do_on_start if start_again_later
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def before_begin(&block)
|
77
|
+
@before_begin << KeyParametersProcedureBinder.new(block)
|
78
|
+
end
|
79
|
+
|
80
|
+
def on_start(&block)
|
81
|
+
@on_start << KeyParametersProcedureBinder.new(block)
|
82
|
+
end
|
83
|
+
|
84
|
+
def before_each_tick(&block)
|
85
|
+
@before_each_tick << KeyParametersProcedureBinder.new(block)
|
86
|
+
end
|
87
|
+
|
88
|
+
def after_stop(&block)
|
89
|
+
@after_stop << KeyParametersProcedureBinder.new(block)
|
90
|
+
end
|
91
|
+
|
92
|
+
def on_position_change(&block)
|
93
|
+
@on_position_change << KeyParametersProcedureBinder.new(block)
|
94
|
+
end
|
95
|
+
|
96
|
+
def start
|
97
|
+
do_before_begin unless @before_begin_already_done
|
98
|
+
|
99
|
+
@clock.run do
|
100
|
+
@before_begin_already_done = false
|
101
|
+
@before_each_tick.each { |block| block.call @sequencer }
|
102
|
+
@sequencer.tick
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def stop
|
107
|
+
@clock.terminate
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def do_before_begin
|
113
|
+
warn 'Transport: doing before_begin initialization...' unless @before_begin.empty? || !@do_log
|
114
|
+
@before_begin.each { |block| block.call @sequencer }
|
115
|
+
warn 'Transport: doing before_begin initialization... done' unless @before_begin.empty? || !@do_log
|
116
|
+
end
|
117
|
+
|
118
|
+
def do_on_start
|
119
|
+
warn 'Transport: starting...' unless @on_start.empty? || !@do_log
|
120
|
+
@on_start.each { |block| block.call @sequencer }
|
121
|
+
warn 'Transport: starting... done' unless @on_start.empty? || !@do_log
|
122
|
+
end
|
123
|
+
|
124
|
+
def do_stop
|
125
|
+
warn 'Transport: stoping...' unless @after_stop.empty? || !@do_log
|
126
|
+
@after_stop.each { |block| block.call @sequencer }
|
127
|
+
warn 'Transport: stoping... done' unless @after_stop.empty? || !@do_log
|
128
|
+
|
129
|
+
warn 'Transport: resetting sequencer...' if @do_log
|
130
|
+
@sequencer.reset
|
131
|
+
warn 'Transport: resetting sequencer... done' if @do_log
|
132
|
+
|
133
|
+
do_before_begin
|
134
|
+
@before_begin_already_done = true
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'musa-dsl/transport/transport'
|
2
|
+
|
3
|
+
require 'musa-dsl/transport/timer'
|
4
|
+
|
5
|
+
require 'musa-dsl/transport/input-midi-clock'
|
6
|
+
require 'musa-dsl/transport/timer-clock'
|
7
|
+
require 'musa-dsl/transport/dummy-clock'
|
8
|
+
require 'musa-dsl/transport/external-tick-clock'
|
9
|
+
|