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