musalce-server 0.5.1 → 0.8.0

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.
data/lib/bitwig/bitwig.rb CHANGED
@@ -4,8 +4,24 @@ require_relative 'handler'
4
4
  require_relative 'controllers'
5
5
 
6
6
  module MusaLCEServer
7
+ # Bitwig Studio integration module.
8
+ #
9
+ # Provides support for live coding with Bitwig Studio 5+ through
10
+ # the MusaLCE for Bitwig controller extension.
11
+ #
12
+ # @see https://github.com/javier-sy/MusaLCEforBitwig Controller extension
7
13
  module Bitwig
14
+ # DAW controller for Bitwig Studio.
15
+ #
16
+ # Implements the {Daw} interface for Bitwig Studio, providing
17
+ # transport control, track management, and MIDI routing through
18
+ # the MusaLCE for Bitwig controller extension.
19
+ #
20
+ # @example
21
+ # # Started via MusaLCEServer.run('bitwig')
22
+ # daw.track('Bass').out.note(60, velocity: 100, duration: 1)
8
23
  class Bitwig < Daw
24
+ # @api private
9
25
  def daw_initialize(midi_devices:, clock:, osc_server:, osc_client:, logger:)
10
26
  super
11
27
 
@@ -17,6 +33,11 @@ module MusaLCEServer
17
33
  return controllers.tracks, handler
18
34
  end
19
35
 
36
+ # Retrieves a track by name.
37
+ #
38
+ # @param name [String] the track name as configured in Bitwig
39
+ # @param all [Boolean] if true, returns array; otherwise returns single track
40
+ # @return [Track, Array<Track>] the track or array containing the track
20
41
  def track(name, all: false)
21
42
  if all
22
43
  [@tracks[name]]
@@ -25,26 +46,37 @@ module MusaLCEServer
25
46
  end
26
47
  end
27
48
 
49
+ # Starts playback in Bitwig.
50
+ # @return [void]
28
51
  def play
29
52
  @handler.play
30
53
  super
31
54
  end
32
55
 
56
+ # Stops playback in Bitwig.
57
+ # @return [void]
33
58
  def stop
34
59
  @handler.stop
35
60
  super
36
61
  end
37
62
 
63
+ # Continues playback from current position.
64
+ # @return [void]
38
65
  def continue
39
66
  @handler.continue
40
67
  super
41
68
  end
42
69
 
70
+ # Moves playhead to specified bar position.
71
+ # @param position [Numeric] the bar number (1-based)
72
+ # @return [void]
43
73
  def goto(position)
44
74
  @handler.goto(position)
45
75
  super
46
76
  end
47
77
 
78
+ # Starts recording in Bitwig.
79
+ # @return [void]
48
80
  def record
49
81
  @handler.record
50
82
  super
@@ -2,7 +2,19 @@ require_relative 'tracks'
2
2
 
3
3
  module MusaLCEServer
4
4
  module Bitwig
5
+ # Manages Bitwig controller scripts and their MIDI channels.
6
+ #
7
+ # Controllers in Bitwig represent hardware MIDI devices configured
8
+ # through the MusaLCE controller extension. Each controller has
9
+ # 16 channels that can be named and mapped to tracks.
10
+ #
11
+ # @api private
5
12
  class Controllers
13
+ # Creates a new controllers manager.
14
+ #
15
+ # @param midi_devices [MIDIDevices] the MIDI devices manager
16
+ # @param clock [Musa::Clock::InputMidiClock] the MIDI clock
17
+ # @param logger [Logger] the logger
6
18
  def initialize(midi_devices, clock:, logger:)
7
19
  @midi_devices = midi_devices
8
20
  @clock = clock
@@ -11,8 +23,14 @@ module MusaLCEServer
11
23
  @tracks = Tracks.new(logger: logger)
12
24
  end
13
25
 
26
+ # @!attribute [r] tracks
27
+ # @return [Tracks] the tracks collection
14
28
  attr_reader :tracks
15
29
 
30
+ # Registers or updates the list of available controllers.
31
+ #
32
+ # @param controllers [Array<String>] controller names from Bitwig
33
+ # @return [void]
16
34
  def register_controllers(controllers)
17
35
  to_delete = @controllers.keys - controllers
18
36
 
@@ -31,6 +49,12 @@ module MusaLCEServer
31
49
  end
32
50
  end
33
51
 
52
+ # Registers a controller with its port and clock settings.
53
+ #
54
+ # @param name [String] the controller name
55
+ # @param port_name [String] the MIDI port name
56
+ # @param is_clock [Boolean] whether this controller provides MIDI clock
57
+ # @return [void]
34
58
  def register_controller(name:, port_name:, is_clock:)
35
59
  controller = @controllers[name]
36
60
  controller.port_name = port_name
@@ -38,6 +62,13 @@ module MusaLCEServer
38
62
  @logger.info "Controller #{name} defined with port_name #{port_name} clock #{is_clock}"
39
63
  end
40
64
 
65
+ # Updates a controller's name and settings.
66
+ #
67
+ # @param old_name [String] the current controller name
68
+ # @param new_name [String] the new controller name
69
+ # @param port_name [String] the MIDI port name
70
+ # @param is_clock [Boolean] whether this controller provides MIDI clock
71
+ # @return [void]
41
72
  def update_controller(old_name:, new_name:, port_name:, is_clock:)
42
73
  controller = @controllers.delete(old_name)
43
74
  @controllers[new_name] = controller
@@ -49,6 +80,11 @@ module MusaLCEServer
49
80
  @logger.info "Controller #{old_name} updated as #{new_name} with port_name #{port_name} clock #{is_clock}"
50
81
  end
51
82
 
83
+ # Registers channel names for a controller.
84
+ #
85
+ # @param controller_name [String] the controller name
86
+ # @param channels [Array<String>] channel names (up to 16)
87
+ # @return [void]
52
88
  def register_channels(controller_name:, channels:)
53
89
  controller = @controllers[controller_name]
54
90
 
@@ -60,7 +96,20 @@ module MusaLCEServer
60
96
  end
61
97
  end
62
98
 
99
+ # Represents a MIDI controller in Bitwig.
100
+ #
101
+ # A controller corresponds to a hardware MIDI device with 16 channels
102
+ # that can be routed to tracks.
103
+ #
104
+ # @api private
63
105
  class Controller
106
+ # Creates a new controller.
107
+ #
108
+ # @param name [String] the controller name
109
+ # @param midi_devices [MIDIDevices] the MIDI devices manager
110
+ # @param clock [Musa::Clock::InputMidiClock] the MIDI clock
111
+ # @param tracks [Tracks] the tracks collection
112
+ # @param logger [Logger] the logger
64
113
  def initialize(name, midi_devices, clock, tracks, logger:)
65
114
  @midi_devices = midi_devices
66
115
  @clock = clock
@@ -73,15 +122,34 @@ module MusaLCEServer
73
122
  @channels = Array.new(16) { |channel| Channel.new(self, tracks, channel, logger: logger) }
74
123
  end
75
124
 
125
+ # @!attribute port_name
126
+ # @return [String] the MIDI port name
76
127
  attr_accessor :port_name
128
+
129
+ # @!attribute [r] name
130
+ # @return [String] the controller name
131
+ # @!attribute [r] midi_device
132
+ # @return [MIDIDevice, nil] the associated MIDI device
133
+ # @!attribute [r] channels
134
+ # @return [Array<Channel>] the 16 MIDI channels
135
+ # @!attribute [r] is_clock
136
+ # @return [Boolean] whether this controller provides MIDI clock
77
137
  attr_reader :name, :midi_device, :channels, :is_clock
78
138
 
139
+ # Sets whether this controller provides MIDI clock.
140
+ #
141
+ # @param new_is_clock [Boolean] the clock setting
142
+ # @return [void]
79
143
  def is_clock=(new_is_clock)
80
144
  # 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
145
  @is_clock = new_is_clock
82
146
  update_clock
83
147
  end
84
148
 
149
+ # Sets the controller name and finds the associated MIDI device.
150
+ #
151
+ # @param new_name [String] the controller name
152
+ # @return [void]
85
153
  def name=(new_name)
86
154
  @name = new_name
87
155
  @midi_device = @midi_devices.find(@name)
@@ -100,7 +168,18 @@ module MusaLCEServer
100
168
  end
101
169
  end
102
170
 
171
+ # Represents a MIDI channel on a controller.
172
+ #
173
+ # Each channel can be named and mapped to a track for MIDI output.
174
+ #
175
+ # @api private
103
176
  class Channel
177
+ # Creates a new channel.
178
+ #
179
+ # @param controller [Controller] the parent controller
180
+ # @param tracks [Tracks] the tracks collection
181
+ # @param channel_number [Integer] the MIDI channel (0-15)
182
+ # @param logger [Logger] the logger
104
183
  def initialize(controller, tracks, channel_number, logger:)
105
184
  @controller = controller
106
185
  @tracks = tracks
@@ -108,8 +187,16 @@ module MusaLCEServer
108
187
  @logger = logger
109
188
  end
110
189
 
190
+ # @!attribute [r] channel_number
191
+ # @return [Integer] the MIDI channel number (0-15)
192
+ # @!attribute [r] name
193
+ # @return [String, nil] the channel name
111
194
  attr_reader :channel_number, :name
112
195
 
196
+ # Sets the channel name and associates it with a track.
197
+ #
198
+ # @param new_name [String] the channel name
199
+ # @return [void]
113
200
  def name=(new_name)
114
201
  @tracks[@name]&._forget_channel
115
202
  @name = new_name
@@ -117,10 +204,16 @@ module MusaLCEServer
117
204
  @tracks[@name]._channel = self
118
205
  end
119
206
 
207
+ # Returns the MIDI voice for this channel.
208
+ #
209
+ # @return [Musa::MIDIVoices::MIDIVoice] the MIDI voice for output
120
210
  def output
121
211
  @controller.midi_device.channels[@channel_number]
122
212
  end
123
213
 
214
+ # Returns a string representation of this channel.
215
+ #
216
+ # @return [String] description including channel number, name, port, and controller
124
217
  def to_s
125
218
  "<Channel #{@channel_number} '#{@name}' on port '#{@controller.port_name}' (controller '#{@controller.name}')>"
126
219
  end
@@ -2,7 +2,21 @@ require_relative '../daw'
2
2
 
3
3
  module MusaLCEServer
4
4
  module Bitwig
5
+ # OSC message handler for Bitwig Studio.
6
+ #
7
+ # Handles communication with the MusaLCE for Bitwig controller extension,
8
+ # processing incoming OSC messages for controller and channel registration,
9
+ # and sending transport commands.
10
+ #
11
+ # @api private
5
12
  class Handler < ::MusaLCEServer::Handler
13
+ # Creates a new Bitwig handler.
14
+ #
15
+ # @param osc_server [OSC::EMServer] the OSC server for receiving messages
16
+ # @param osc_client [OSC::Client] the OSC client for sending messages
17
+ # @param controllers [Controllers] the controllers manager
18
+ # @param sequencer [Musa::Sequencer::Sequencer] the sequencer instance
19
+ # @param logger [Logger] the logger
6
20
  def initialize(osc_server, osc_client, controllers, sequencer, logger:)
7
21
  super()
8
22
 
@@ -44,36 +58,52 @@ module MusaLCEServer
44
58
  end
45
59
  end
46
60
 
61
+ # Requests synchronization of controllers and channels from Bitwig.
62
+ # @return [void]
47
63
  def sync
48
64
  @logger.info 'Asking sync'
49
65
  send_osc '/musalce4bitwig/sync'
50
66
  end
51
67
 
68
+ # Sends play command to Bitwig.
69
+ # @return [void]
52
70
  def play
53
71
  @logger.info 'Asking play'
54
72
  send_osc '/musalce4bitwig/play'
55
73
  end
56
74
 
75
+ # Sends stop command to Bitwig.
76
+ # @return [void]
57
77
  def stop
58
78
  @logger.info 'Asking stop'
59
79
  send_osc '/musalce4bitwig/stop'
60
80
  end
61
81
 
82
+ # Sends continue command to Bitwig.
83
+ # @return [void]
62
84
  def continue
63
85
  @logger.info 'Asking continue'
64
86
  send_osc '/musalce4bitwig/continue'
65
87
  end
66
88
 
89
+ # Moves playhead to specified position.
90
+ #
91
+ # @param position [Numeric] the bar number (1-based)
92
+ # @return [void]
67
93
  def goto(position)
68
94
  @logger.info "Asking goto #{position}"
69
95
  send_osc '/musalce4bitwig/goto', OSC::OSCDouble64.new(((position - 1) * @sequencer.beats_per_bar).to_f)
70
96
  end
71
97
 
98
+ # Sends record command to Bitwig.
99
+ # @return [void]
72
100
  def record
73
101
  @logger.info 'Asking record'
74
102
  send_osc '/musalce4bitwig/record'
75
103
  end
76
104
 
105
+ # Sends panic to all tracks.
106
+ # @return [void]
77
107
  def panic!
78
108
  @controllers.tracks.each(:panic!)
79
109
  end
data/lib/bitwig/tracks.rb CHANGED
@@ -2,18 +2,35 @@ require 'musa-dsl/core-ext/dynamic-proxy'
2
2
 
3
3
  module MusaLCEServer
4
4
  module Bitwig
5
+ # Collection of tracks for Bitwig.
6
+ #
7
+ # Tracks are created dynamically based on channel names received
8
+ # from the Bitwig controller extension.
9
+ #
10
+ # @api private
5
11
  class Tracks
6
12
  include Enumerable
7
13
 
14
+ # Creates a new tracks collection.
15
+ #
16
+ # @param logger [Logger] the logger
8
17
  def initialize(logger:)
9
18
  @logger = logger
10
19
  @tracks = {}
11
20
  end
12
21
 
22
+ # Creates a new track with the given name.
23
+ #
24
+ # @param name [String] the track name
25
+ # @return [Track] the created track
13
26
  def create(name)
14
27
  @tracks[name] = Track.new(name, logger: @logger)
15
28
  end
16
29
 
30
+ # Iterates over all tracks.
31
+ #
32
+ # @yield [Track] each track
33
+ # @return [Enumerator] if no block given
17
34
  def each(&block)
18
35
  if block_given?
19
36
  @tracks.values.each(&block)
@@ -22,16 +39,33 @@ module MusaLCEServer
22
39
  end
23
40
  end
24
41
 
42
+ # Retrieves a track by name.
43
+ #
44
+ # @param name [String] the track name
45
+ # @return [Track, nil] the track or nil if not found
25
46
  def [](name)
26
47
  @tracks[name]
27
48
  end
28
49
 
50
+ # Sets a track by name.
51
+ #
52
+ # @param name [String] the track name
53
+ # @param track [Track] the track
54
+ # @return [Track] the track
29
55
  def []=(name, track)
30
56
  @tracks[name] = track
31
57
  end
32
58
  end
33
59
 
60
+ # Represents a track in Bitwig with dynamic MIDI output.
61
+ #
62
+ # Uses DynamicProxy to allow the output to be reassigned
63
+ # when channel mappings change.
34
64
  class Track
65
+ # Creates a new track.
66
+ #
67
+ # @param name [String] the track name
68
+ # @param logger [Logger] the logger
35
69
  def initialize(name, logger:)
36
70
  @name = name
37
71
  @logger = logger
@@ -39,17 +73,29 @@ module MusaLCEServer
39
73
  @output = Musa::Extension::DynamicProxy::DynamicProxy.new
40
74
  end
41
75
 
76
+ # @!attribute [r] name
77
+ # @return [String] the track name
42
78
  attr_reader :name
43
79
 
80
+ # Disconnects the current channel from this track.
81
+ # @api private
82
+ # @return [void]
44
83
  def _forget_channel
45
84
  @output.receiver = nil
46
85
  end
47
86
 
87
+ # Sets the channel for this track.
88
+ # @api private
89
+ # @param new_channel [Channel] the channel to assign
90
+ # @return [void]
48
91
  def _channel=(new_channel)
49
92
  @logger.info "Assigning #{new_channel} to track '#{@name}'"
50
93
  @output.receiver = new_channel.output
51
94
  end
52
95
 
96
+ # Returns the MIDI output for this track.
97
+ #
98
+ # @return [Musa::Extension::DynamicProxy::DynamicProxy] proxy to the MIDI voice
53
99
  def out
54
100
  @output
55
101
  end