musalce-server 0.4.10
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 +3 -0
- data/LICENSE +674 -0
- data/README.md +22 -0
- data/bin/musalce-server +5 -0
- data/lib/bitwig/bitwig.rb +57 -0
- data/lib/bitwig/controllers.rb +129 -0
- data/lib/bitwig/handler.rb +82 -0
- data/lib/bitwig/tracks.rb +58 -0
- data/lib/daw.rb +114 -0
- data/lib/live/handler.rb +55 -0
- data/lib/live/live.rb +40 -0
- data/lib/live/tracks.rb +142 -0
- data/lib/midi-devices.rb +76 -0
- data/lib/musalce-server.rb +57 -0
- data/musalce-server.gemspec +32 -0
- metadata +181 -0
data/README.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# Musa Live Coding Environment (Server for Ableton Live and Bitwig)
|
2
|
+
|
3
|
+
Musa-DSL Live Coding Environment for Ableton Live and Bitwig Studio (Server part)
|
4
|
+
|
5
|
+
**TODO: complete README**
|
6
|
+
|
7
|
+
## Install
|
8
|
+
**TODO**
|
9
|
+
|
10
|
+
## Usage
|
11
|
+
**TODO**
|
12
|
+
|
13
|
+
## Documentation
|
14
|
+
**TODO**
|
15
|
+
|
16
|
+
## Author
|
17
|
+
|
18
|
+
* [Javier Sánchez Yeste](https://github.com/javier-sy)
|
19
|
+
|
20
|
+
## License
|
21
|
+
|
22
|
+
[MusaLCE-Server](https://github.com/javier-sy/MusaLCE-Server) Copyright (c) 2021-2023 [Javier Sánchez Yeste](https://yeste.studio), licensed under GPL 3.0 License
|
data/bin/musalce-server
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require_relative '../daw'
|
2
|
+
|
3
|
+
require_relative 'handler'
|
4
|
+
require_relative 'controllers'
|
5
|
+
|
6
|
+
module MusaLCEServer
|
7
|
+
module Bitwig
|
8
|
+
class Bitwig < Daw
|
9
|
+
def daw_initialize(midi_devices:, clock:, osc_server:, osc_client:, logger:)
|
10
|
+
super
|
11
|
+
|
12
|
+
controllers = Controllers.new(midi_devices, clock: clock, logger: logger)
|
13
|
+
handler = Handler.new(osc_server, osc_client, controllers, @sequencer, logger: logger)
|
14
|
+
|
15
|
+
logger.info('Loaded Bitwig Studio driver')
|
16
|
+
|
17
|
+
return controllers.tracks, handler
|
18
|
+
end
|
19
|
+
|
20
|
+
def track(name, all: false)
|
21
|
+
if all
|
22
|
+
[@tracks[name]]
|
23
|
+
else
|
24
|
+
@tracks[name]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def play
|
29
|
+
@handler.play
|
30
|
+
super
|
31
|
+
end
|
32
|
+
|
33
|
+
def stop
|
34
|
+
@handler.stop
|
35
|
+
super
|
36
|
+
end
|
37
|
+
|
38
|
+
def continue
|
39
|
+
@handler.continue
|
40
|
+
super
|
41
|
+
end
|
42
|
+
|
43
|
+
def goto(position)
|
44
|
+
@handler.goto(position)
|
45
|
+
super
|
46
|
+
end
|
47
|
+
|
48
|
+
def record
|
49
|
+
@handler.record
|
50
|
+
super
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
Daw.register :bitwig, Bitwig
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require_relative 'tracks'
|
2
|
+
|
3
|
+
module MusaLCEServer
|
4
|
+
module Bitwig
|
5
|
+
class Controllers
|
6
|
+
def initialize(midi_devices, clock:, logger:)
|
7
|
+
@midi_devices = midi_devices
|
8
|
+
@clock = clock
|
9
|
+
@logger = logger
|
10
|
+
@controllers = {}
|
11
|
+
@tracks = Tracks.new(logger: logger)
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :tracks
|
15
|
+
|
16
|
+
def register_controllers(controllers)
|
17
|
+
to_delete = @controllers.keys - controllers
|
18
|
+
|
19
|
+
controllers.each do |controller_name|
|
20
|
+
if @controllers.key?(controller_name)
|
21
|
+
@logger.info "Controller #{controller_name} already exists"
|
22
|
+
else
|
23
|
+
@logger.info "Added controller #{controller_name}"
|
24
|
+
@controllers[controller_name] = Controller.new(controller_name, @midi_devices, @clock, @tracks, logger: @logger)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
to_delete.each do |controller_name|
|
29
|
+
@controllers.delete(controller_name)
|
30
|
+
@logger.info "Deleted controller #{controller_name}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def register_controller(name:, port_name:, is_clock:)
|
35
|
+
controller = @controllers[name]
|
36
|
+
controller.port_name = port_name
|
37
|
+
controller.is_clock = is_clock
|
38
|
+
@logger.info "Controller #{name} defined with port_name #{port_name} clock #{is_clock}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def update_controller(old_name:, new_name:, port_name:, is_clock:)
|
42
|
+
controller = @controllers.delete(old_name)
|
43
|
+
@controllers[new_name] = controller
|
44
|
+
controller.name = new_name
|
45
|
+
|
46
|
+
controller.port_name = port_name
|
47
|
+
controller.is_clock = is_clock
|
48
|
+
|
49
|
+
@logger.info "Controller #{old_name} updated as #{new_name} with port_name #{port_name} clock #{is_clock}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def register_channels(controller_name:, channels:)
|
53
|
+
controller = @controllers[controller_name]
|
54
|
+
|
55
|
+
@logger.info "Channels for controller #{controller_name} named #{channels}"
|
56
|
+
|
57
|
+
channels.each.with_index do |name, i|
|
58
|
+
controller.channels[i].name = name
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Controller
|
64
|
+
def initialize(name, midi_devices, clock, tracks, logger:)
|
65
|
+
@midi_devices = midi_devices
|
66
|
+
@clock = clock
|
67
|
+
@tracks = tracks
|
68
|
+
@logger = logger
|
69
|
+
|
70
|
+
@midi_device = nil
|
71
|
+
|
72
|
+
self.name = name
|
73
|
+
@channels = Array.new(16) { |channel| Channel.new(self, tracks, channel, logger: logger) }
|
74
|
+
end
|
75
|
+
|
76
|
+
attr_accessor :port_name
|
77
|
+
attr_reader :name, :midi_device, :channels, :is_clock
|
78
|
+
|
79
|
+
def is_clock=(new_is_clock)
|
80
|
+
# TODO when new_is_clock is false look if another controller is true, else leave clock input as nil (the user has not selected any clock!)
|
81
|
+
@is_clock = new_is_clock
|
82
|
+
update_clock
|
83
|
+
end
|
84
|
+
|
85
|
+
def name=(new_name)
|
86
|
+
@name = new_name
|
87
|
+
@midi_device = @midi_devices.find(@name)
|
88
|
+
|
89
|
+
update_clock
|
90
|
+
|
91
|
+
if @midi_device
|
92
|
+
@logger.info "Found midi device #{@midi_device.to_s} for #{@name}"
|
93
|
+
else
|
94
|
+
@logger.warn "Not found midi device for #{@name}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
private def update_clock
|
99
|
+
@clock.input = MIDICommunications::Input.find_by_name(@midi_device.low_level_device.name) if @is_clock
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class Channel
|
104
|
+
def initialize(controller, tracks, channel_number, logger:)
|
105
|
+
@controller = controller
|
106
|
+
@tracks = tracks
|
107
|
+
@channel_number = channel_number
|
108
|
+
@logger = logger
|
109
|
+
end
|
110
|
+
|
111
|
+
attr_reader :channel_number, :name
|
112
|
+
|
113
|
+
def name=(new_name)
|
114
|
+
@tracks[@name]&._forget_channel
|
115
|
+
@name = new_name
|
116
|
+
@tracks.create(@name)
|
117
|
+
@tracks[@name]._channel = self
|
118
|
+
end
|
119
|
+
|
120
|
+
def output
|
121
|
+
@controller.midi_device.channels[@channel_number]
|
122
|
+
end
|
123
|
+
|
124
|
+
def to_s
|
125
|
+
"<Channel #{@channel_number} '#{@name}' on port '#{@controller.port_name}' (controller '#{@controller.name}')>"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require_relative '../daw'
|
2
|
+
|
3
|
+
module MusaLCEServer
|
4
|
+
module Bitwig
|
5
|
+
class Handler < ::MusaLCEServer::Handler
|
6
|
+
def initialize(osc_server, osc_client, controllers, sequencer, logger:)
|
7
|
+
super()
|
8
|
+
|
9
|
+
@server = osc_server
|
10
|
+
@client = osc_client
|
11
|
+
|
12
|
+
@controllers = controllers
|
13
|
+
@sequencer = sequencer
|
14
|
+
|
15
|
+
@logger = logger
|
16
|
+
|
17
|
+
@server.add_method '/hello' do |message|
|
18
|
+
@logger.info "Received /hello #{message.to_a}!"
|
19
|
+
version
|
20
|
+
sync
|
21
|
+
end
|
22
|
+
|
23
|
+
@server.add_method '/musalce4bitwig/controllers' do |message|
|
24
|
+
@logger.info("Received /musalce4bitwig/controllers #{message.to_a}")
|
25
|
+
@controllers.register_controllers(message.to_a)
|
26
|
+
end
|
27
|
+
|
28
|
+
@server.add_method '/musalce4bitwig/controller' do |message|
|
29
|
+
@logger.info("Received /musalce4bitwig/controller #{message.to_a}")
|
30
|
+
a = message.to_a
|
31
|
+
@controllers.register_controller(name: a[0], port_name: a[1], is_clock: a[2] == 1)
|
32
|
+
end
|
33
|
+
|
34
|
+
@server.add_method '/musalce4bitwig/controller/update' do |message|
|
35
|
+
@logger.info("Received /musalce4bitwig/controller/update #{message.to_a}")
|
36
|
+
a = message.to_a
|
37
|
+
@controllers.update_controller(old_name: a[0], new_name: a[1], port_name: a[2], is_clock: a[3] == 1)
|
38
|
+
end
|
39
|
+
|
40
|
+
@server.add_method '/musalce4bitwig/channels' do |message|
|
41
|
+
@logger.info("Received /musalce4bitwig/channels #{message.to_a}")
|
42
|
+
a = message.to_a
|
43
|
+
@controllers.register_channels(controller_name: a[0], channels: a[1..])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def sync
|
48
|
+
@logger.info 'Asking sync'
|
49
|
+
send_osc '/musalce4bitwig/sync'
|
50
|
+
end
|
51
|
+
|
52
|
+
def play
|
53
|
+
@logger.info 'Asking play'
|
54
|
+
send_osc '/musalce4bitwig/play'
|
55
|
+
end
|
56
|
+
|
57
|
+
def stop
|
58
|
+
@logger.info 'Asking stop'
|
59
|
+
send_osc '/musalce4bitwig/stop'
|
60
|
+
end
|
61
|
+
|
62
|
+
def continue
|
63
|
+
@logger.info 'Asking continue'
|
64
|
+
send_osc '/musalce4bitwig/continue'
|
65
|
+
end
|
66
|
+
|
67
|
+
def goto(position)
|
68
|
+
@logger.info "Asking goto #{position}"
|
69
|
+
send_osc '/musalce4bitwig/goto', OSC::OSCDouble64.new(((position - 1) * @sequencer.beats_per_bar).to_f)
|
70
|
+
end
|
71
|
+
|
72
|
+
def record
|
73
|
+
@logger.info 'Asking record'
|
74
|
+
send_osc '/musalce4bitwig/record'
|
75
|
+
end
|
76
|
+
|
77
|
+
def panic!
|
78
|
+
@controllers.tracks.each(:panic!)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'musa-dsl/core-ext/dynamic-proxy'
|
2
|
+
|
3
|
+
module MusaLCEServer
|
4
|
+
module Bitwig
|
5
|
+
class Tracks
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
def initialize(logger:)
|
9
|
+
@logger = logger
|
10
|
+
@tracks = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def create(name)
|
14
|
+
@tracks[name] = Track.new(name, logger: @logger)
|
15
|
+
end
|
16
|
+
|
17
|
+
def each(&block)
|
18
|
+
if block_given?
|
19
|
+
@tracks.values.each(&block)
|
20
|
+
else
|
21
|
+
@tracks.values.each
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def [](name)
|
26
|
+
@tracks[name]
|
27
|
+
end
|
28
|
+
|
29
|
+
def []=(name, track)
|
30
|
+
@tracks[name] = track
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Track
|
35
|
+
def initialize(name, logger:)
|
36
|
+
@name = name
|
37
|
+
@logger = logger
|
38
|
+
|
39
|
+
@output = Musa::Extension::DynamicProxy::DynamicProxy.new
|
40
|
+
end
|
41
|
+
|
42
|
+
attr_reader :name
|
43
|
+
|
44
|
+
def _forget_channel
|
45
|
+
@output.receiver = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def _channel=(new_channel)
|
49
|
+
@logger.info "Assigning #{new_channel} to track '#{@name}'"
|
50
|
+
@output.receiver = new_channel.output
|
51
|
+
end
|
52
|
+
|
53
|
+
def out
|
54
|
+
@output
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/daw.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'midi-communications'
|
2
|
+
|
3
|
+
require_relative 'midi-devices'
|
4
|
+
|
5
|
+
module MusaLCEServer
|
6
|
+
class Daw
|
7
|
+
def self.register(daw_id, daw_class)
|
8
|
+
@@daws ||= {}
|
9
|
+
@@daws[daw_id] = daw_class
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.daw_controller_for(daw_id)
|
13
|
+
@@daws[daw_id].new
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
osc_server = OSC::EMServer.new(11_011)
|
18
|
+
osc_client = OSC::Client.new('localhost', 10_001)
|
19
|
+
|
20
|
+
Thread.new { osc_server.run }
|
21
|
+
|
22
|
+
@sequencer = Musa::Sequencer::Sequencer.new 4, 24, dsl_context_class: MusaLCE_Context, do_log: true
|
23
|
+
|
24
|
+
@clock = Musa::Clock::InputMidiClock.new do_log: true, logger: @sequencer.logger
|
25
|
+
transport = Musa::Transport::Transport.new @clock, sequencer: @sequencer
|
26
|
+
|
27
|
+
transport.after_stop do
|
28
|
+
sequencer.reset
|
29
|
+
end
|
30
|
+
|
31
|
+
@midi_devices = MIDIDevices.new(@sequencer)
|
32
|
+
|
33
|
+
@tracks, @handler = daw_initialize(midi_devices: @midi_devices, clock: @clock, osc_server: osc_server, osc_client: osc_client, logger: @sequencer.logger)
|
34
|
+
|
35
|
+
@handler.version
|
36
|
+
@handler.sync
|
37
|
+
|
38
|
+
Thread.new { transport.start }
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_reader :clock, :sequencer, :tracks
|
42
|
+
|
43
|
+
protected def daw_initialize(midi_devices:, clock:, osc_server:, osc_client:, logger:); end
|
44
|
+
|
45
|
+
def track(name, all: false)
|
46
|
+
raise NotImplementedError
|
47
|
+
end
|
48
|
+
|
49
|
+
def play; end
|
50
|
+
|
51
|
+
def stop; end
|
52
|
+
|
53
|
+
def continue; end
|
54
|
+
|
55
|
+
def goto(position); end
|
56
|
+
|
57
|
+
def record; end
|
58
|
+
|
59
|
+
def panic!
|
60
|
+
@tracks.each do |track|
|
61
|
+
track.out.all_notes_off
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def sync
|
66
|
+
@handler.sync
|
67
|
+
end
|
68
|
+
|
69
|
+
def reload
|
70
|
+
@handler.reload
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class Handler
|
75
|
+
def reload
|
76
|
+
@logger.info 'Asking controller reset and reload'
|
77
|
+
send_osc '/reload'
|
78
|
+
end
|
79
|
+
|
80
|
+
def version
|
81
|
+
@logger.info "Sending version #{VERSION}"
|
82
|
+
send_osc '/version', VERSION
|
83
|
+
end
|
84
|
+
|
85
|
+
def panic!
|
86
|
+
raise NotImplementedError
|
87
|
+
end
|
88
|
+
|
89
|
+
private def send_osc(message, *args)
|
90
|
+
counter = 0
|
91
|
+
begin
|
92
|
+
@client.send OSC::Message.new(message, *args)
|
93
|
+
rescue Errno::ECONNREFUSED
|
94
|
+
counter += 1
|
95
|
+
@logger.warn "Errno::ECONNREFUSED when sending message #{message} #{args}. Retrying... (#{counter})"
|
96
|
+
retry if counter < 3
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class MusaLCE_Context < Musa::Sequencer::Sequencer::DSLContext
|
102
|
+
include Musa::REPL::CustomizableDSLContext
|
103
|
+
|
104
|
+
def binder
|
105
|
+
@__binder ||= binding
|
106
|
+
end
|
107
|
+
|
108
|
+
def import(*modules)
|
109
|
+
modules.each do |m|
|
110
|
+
self.class.include(m)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/lib/live/handler.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require_relative '../daw'
|
2
|
+
|
3
|
+
module MusaLCEServer
|
4
|
+
module Live
|
5
|
+
class Handler < ::MusaLCEServer::Handler
|
6
|
+
def initialize(osc_server, osc_client, tracks, logger:)
|
7
|
+
super()
|
8
|
+
|
9
|
+
@server = osc_server
|
10
|
+
@client = osc_client
|
11
|
+
|
12
|
+
@tracks = tracks
|
13
|
+
|
14
|
+
@logger = logger
|
15
|
+
|
16
|
+
@server.add_method '/hello' do |message|
|
17
|
+
@logger.info "Received /hello #{message.to_a}!"
|
18
|
+
sync
|
19
|
+
end
|
20
|
+
|
21
|
+
@server.add_method '/musalce4live/tracks' do |message|
|
22
|
+
@tracks.grant_registry_collection(message.to_a.each_slice(10).to_a)
|
23
|
+
end
|
24
|
+
|
25
|
+
@server.add_method '/musalce4live/track/name' do |message|
|
26
|
+
message.to_a.each_slice(2).to_a.each do |track_data|
|
27
|
+
@tracks.grant_registry(track_data[0], track_data[1])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
@server.add_method '/musalce4live/track/midi' do |message|
|
32
|
+
message.to_a.each_slice(3).to_a.each do |track_data|
|
33
|
+
@tracks.grant_registry(track_data[0], *([nil] * 1), *track_data[1..])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
@server.add_method '/musalce4live/track/audio' do |message|
|
38
|
+
message.to_a.each_slice(3).to_a.each do |track_data|
|
39
|
+
@tracks.grant_registry(track_data[0], *([nil] * 3), *track_data[1..])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
@server.add_method '/musalce4live/track/routings' do |message|
|
44
|
+
message.to_a.each_slice(5).to_a.each do |track_data|
|
45
|
+
@tracks.grant_registry(track_data[0], *([nil] * 5), *track_data[1..])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def sync
|
51
|
+
send_osc '/musalce4live/tracks'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/live/live.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative '../daw'
|
2
|
+
|
3
|
+
require_relative 'handler'
|
4
|
+
require_relative 'tracks'
|
5
|
+
|
6
|
+
module MusaLCEServer
|
7
|
+
module Live
|
8
|
+
class Live < Daw
|
9
|
+
def daw_initialize(midi_devices:, clock:, osc_server:, osc_client:, logger:)
|
10
|
+
super
|
11
|
+
tracks = Tracks.new(midi_devices, logger: logger)
|
12
|
+
handler = Handler.new(osc_server, osc_client, tracks, logger: logger)
|
13
|
+
|
14
|
+
logger.info('Loaded Ableton Live driver')
|
15
|
+
|
16
|
+
return tracks, handler
|
17
|
+
end
|
18
|
+
|
19
|
+
def track(name, all: false)
|
20
|
+
if all
|
21
|
+
@tracks.find_by_name(name)
|
22
|
+
else
|
23
|
+
@tracks.find_by_name(name).first
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def midi_sync(midi_device_name, manufacturer: nil, model: nil, name: nil)
|
28
|
+
name ||= midi_device_name
|
29
|
+
|
30
|
+
@clock.input = MIDICommunications::Input.all.find do |_|
|
31
|
+
(_.manufacturer == manufacturer || manufacturer.nil?) &&
|
32
|
+
(_.model == model || model.nil?) &&
|
33
|
+
(_.name == name || name.nil?)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
Daw.register :live, Live
|
39
|
+
end
|
40
|
+
end
|
data/lib/live/tracks.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'musa-dsl/core-ext/dynamic-proxy'
|
2
|
+
|
3
|
+
module MusaLCEServer
|
4
|
+
module Live
|
5
|
+
class Track
|
6
|
+
def initialize(id, midi_devices, logger:)
|
7
|
+
@id = id
|
8
|
+
@midi_devices = midi_devices
|
9
|
+
@logger = logger
|
10
|
+
|
11
|
+
@output = Musa::Extension::DynamicProxy::DynamicProxy.new
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :id, :name
|
15
|
+
|
16
|
+
def out
|
17
|
+
@output
|
18
|
+
end
|
19
|
+
|
20
|
+
def _update_name(value)
|
21
|
+
@name = value
|
22
|
+
@logger.info "track #{@id} assigned name #{@name}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def _update_has_midi_input(value);
|
26
|
+
@has_midi_input = value == 1;
|
27
|
+
end
|
28
|
+
def _update_has_midi_output(value);
|
29
|
+
@has_midi_output = value == 1;
|
30
|
+
end
|
31
|
+
def _update_has_audio_input(value);
|
32
|
+
@has_audio_input = value == 1;
|
33
|
+
end
|
34
|
+
def _update_has_audio_output(value);
|
35
|
+
@has_audio_output = value == 1;
|
36
|
+
end
|
37
|
+
|
38
|
+
def _update_current_input_routing(value)
|
39
|
+
@current_input_routing = value
|
40
|
+
_update_current_input_sub_routing(@current_input_sub_routing)
|
41
|
+
end
|
42
|
+
|
43
|
+
def _update_current_input_sub_routing(value)
|
44
|
+
@current_input_sub_routing = value
|
45
|
+
|
46
|
+
effective_midi_voice = nil
|
47
|
+
|
48
|
+
if @has_midi_input
|
49
|
+
device = @midi_devices.find(@current_input_routing)
|
50
|
+
|
51
|
+
if device
|
52
|
+
channel = /Ch\. (?<channel>\d+)/.match(@current_input_sub_routing)&.[](:channel)
|
53
|
+
effective_midi_voice = device.channels[channel.to_i - 1] if channel
|
54
|
+
|
55
|
+
@logger.info "track #{@id} assigned new input: device '#{device.name}' #{effective_midi_voice}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
@output.receiver = effective_midi_voice
|
60
|
+
end
|
61
|
+
|
62
|
+
def _update_current_output_routing(value);
|
63
|
+
@current_output_routing = value
|
64
|
+
end
|
65
|
+
|
66
|
+
def _update_current_output_sub_routing(value);
|
67
|
+
@current_output_sub_routing = value
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class Tracks
|
72
|
+
include Enumerable
|
73
|
+
|
74
|
+
def initialize(midi_devices, logger:)
|
75
|
+
@midi_devices = midi_devices
|
76
|
+
@logger = logger
|
77
|
+
@tracks = {}
|
78
|
+
end
|
79
|
+
|
80
|
+
def grant_registry_collection(tracks_data)
|
81
|
+
tracks_to_delete = Set[*@tracks.keys]
|
82
|
+
|
83
|
+
tracks_data.each do |track_data|
|
84
|
+
grant_registry(*track_data)
|
85
|
+
tracks_to_delete.delete track_data[0]
|
86
|
+
end
|
87
|
+
|
88
|
+
tracks_to_delete.each do |id|
|
89
|
+
@tracks.delete(id)
|
90
|
+
@logger.info "deleted track #{id}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def grant_registry(id, name = nil,
|
95
|
+
has_midi_input = nil, has_midi_output = nil,
|
96
|
+
has_audio_input = nil, has_audio_output = nil,
|
97
|
+
current_input_routing = nil, current_input_sub_routing = nil,
|
98
|
+
current_output_routing = nil, current_output_sub_routing = nil)
|
99
|
+
|
100
|
+
track = @tracks[id]
|
101
|
+
|
102
|
+
unless track
|
103
|
+
track = Track.new(id, @midi_devices, logger: @logger)
|
104
|
+
@tracks[id] = track
|
105
|
+
end
|
106
|
+
|
107
|
+
track._update_name(name) if name
|
108
|
+
track._update_has_midi_input(has_midi_input) if has_midi_input
|
109
|
+
track._update_has_midi_output(has_midi_output) if has_midi_output
|
110
|
+
track._update_has_audio_input(has_audio_input) if has_audio_input
|
111
|
+
track._update_has_audio_output(has_audio_output) if has_audio_output
|
112
|
+
track._update_current_input_routing(parse_device_name(current_input_routing)) if current_input_routing
|
113
|
+
track._update_current_input_sub_routing(current_input_sub_routing) if current_input_sub_routing
|
114
|
+
track._update_current_output_routing(parse_device_name(current_output_routing)) if current_output_routing
|
115
|
+
track._update_current_output_sub_routing(current_output_sub_routing) if current_output_sub_routing
|
116
|
+
end
|
117
|
+
|
118
|
+
def each(&block)
|
119
|
+
if block_given?
|
120
|
+
@tracks.values.each(&block)
|
121
|
+
else
|
122
|
+
@tracks.values.each
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def [](id)
|
127
|
+
@tracks[id]
|
128
|
+
end
|
129
|
+
|
130
|
+
# TODO adaptar a contrato y semántica de Bitwig (en bitwig sólo hay un track con un nombre determinado, el id no existe)
|
131
|
+
|
132
|
+
def find_by_name(name)
|
133
|
+
@tracks.values.select { |_| _.name == name }
|
134
|
+
end
|
135
|
+
|
136
|
+
private def parse_device_name(name)
|
137
|
+
match = name.match(/Driver IAC \((?<name>.+)\)/)
|
138
|
+
match ? match[:name] : name
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|