musalce-server 0.5.1 → 0.8.1
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 +4 -4
- data/.github/workflows/notify-plugin.yml +17 -0
- data/.gitignore +3 -0
- data/.version +6 -0
- data/.yardopts +6 -0
- data/README.md +307 -10
- data/docs/architecture.md +178 -0
- data/lib/bitwig/bitwig.rb +32 -0
- data/lib/bitwig/controllers.rb +93 -0
- data/lib/bitwig/handler.rb +30 -0
- data/lib/bitwig/tracks.rb +46 -0
- data/lib/daw.rb +193 -4
- data/lib/live/handler.rb +14 -0
- data/lib/live/live.rb +36 -0
- data/lib/live/tracks.rb +67 -2
- data/lib/midi-devices.rb +58 -1
- data/lib/musalce-server.rb +33 -0
- data/lib/surface-bridge.rb +171 -0
- data/lib/surface.rb +369 -0
- data/lib/version.rb +13 -1
- data/musalce-server.gemspec +20 -13
- metadata +90 -17
data/lib/surface.rb
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
|
|
3
|
+
module MusaLCEServer
|
|
4
|
+
# Authoritative model of the physical control surface (Stream Deck
|
|
5
|
+
# buttons, encoders, …) as seen from the server.
|
|
6
|
+
#
|
|
7
|
+
# The surface is **the abstraction shared with hardware** but
|
|
8
|
+
# surface-agnostic: it knows only about named controls with a type
|
|
9
|
+
# and dynamic state. Each control is keyed by its **event name** — a
|
|
10
|
+
# Symbol (e.g. +:launch_chorus+) that doubles as the identifier used
|
|
11
|
+
# with the sequencer's +on+/+launch+ mechanism when the control
|
|
12
|
+
# fires. In MusaLCE the unit of interaction is always the *event*;
|
|
13
|
+
# the surface is the physical face of one or more events.
|
|
14
|
+
#
|
|
15
|
+
# Ownership of the two data axes:
|
|
16
|
+
#
|
|
17
|
+
# - **Inventory** (which controls exist and their type) flows
|
|
18
|
+
# inbound from Pulso Bridge through the DAW extension; the server
|
|
19
|
+
# trusts what it receives (Pulso validates type consistency
|
|
20
|
+
# across physical instances).
|
|
21
|
+
# - **State** (message, enabled, value, …) is owned by the server:
|
|
22
|
+
# the score writes to +surface[:event]+ and changes propagate
|
|
23
|
+
# outbound on +/musalce/surface/state/<prop>+.
|
|
24
|
+
#
|
|
25
|
+
# Inventory may arrive as a full dump (between +inventory/begin+
|
|
26
|
+
# and +inventory/end+, in which case events absent from the dump
|
|
27
|
+
# are purged at end) or as runtime deltas (+inventory/add+ /
|
|
28
|
+
# +inventory/remove+). Re-adding an event with the same type
|
|
29
|
+
# preserves its state; a type change replaces the control and
|
|
30
|
+
# resets state.
|
|
31
|
+
#
|
|
32
|
+
# All mutating methods are expected to run on the sequencer tick
|
|
33
|
+
# thread (inbound OSC messages are routed there by
|
|
34
|
+
# {SurfaceBridge}). Score code writing +surface[:event].xxx =+ ...
|
|
35
|
+
# also runs on that thread (inside +at+/+every+/+on+ blocks),
|
|
36
|
+
# which keeps access serial without explicit locking.
|
|
37
|
+
class Surface
|
|
38
|
+
# @param bridge [SurfaceBridge] the bridge used to emit state outbound
|
|
39
|
+
# @param logger [Logger] the logger
|
|
40
|
+
def initialize(bridge:, logger:)
|
|
41
|
+
@bridge = bridge
|
|
42
|
+
@logger = logger
|
|
43
|
+
@controls = {}
|
|
44
|
+
@pending_events = nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns the control for the given event, or +nil+ if unknown.
|
|
48
|
+
#
|
|
49
|
+
# A control becomes known once its inventory entry has been
|
|
50
|
+
# received from the surface. Score code that runs before that
|
|
51
|
+
# should use safe navigation (+surface[:foo]&.enabled = true+)
|
|
52
|
+
# or guard with {#known?}.
|
|
53
|
+
#
|
|
54
|
+
# @param event [Symbol, String]
|
|
55
|
+
# @return [Control, nil]
|
|
56
|
+
def [](event)
|
|
57
|
+
@controls[event.to_sym]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @return [Array<Symbol>] all known control events
|
|
61
|
+
def events
|
|
62
|
+
@controls.keys
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @param event [Symbol, String]
|
|
66
|
+
# @return [Boolean] whether a control with this event is in the inventory
|
|
67
|
+
def known?(event)
|
|
68
|
+
@controls.key?(event.to_sym)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Begins a full inventory dump. Events that are not re-added before
|
|
72
|
+
# {#end_inventory} are purged.
|
|
73
|
+
# @return [void]
|
|
74
|
+
# @api private
|
|
75
|
+
def begin_inventory
|
|
76
|
+
@pending_events = Set.new
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Registers (or refreshes) a control in the inventory.
|
|
80
|
+
#
|
|
81
|
+
# If a control with the same event and type already exists, its
|
|
82
|
+
# state is preserved. If the type differs, the existing control
|
|
83
|
+
# is replaced with a fresh instance (state reset).
|
|
84
|
+
#
|
|
85
|
+
# @param event [Symbol, String]
|
|
86
|
+
# @param type [Symbol, String] one of +:toggle+, +:trigger+, +:encoder+
|
|
87
|
+
# @return [Control] the (possibly new) control
|
|
88
|
+
# @api private
|
|
89
|
+
def add_control(event, type)
|
|
90
|
+
event = event.to_sym
|
|
91
|
+
type = type.to_sym
|
|
92
|
+
existing = @controls[event]
|
|
93
|
+
|
|
94
|
+
if existing && existing.class.type_name == type
|
|
95
|
+
ctrl = existing
|
|
96
|
+
else
|
|
97
|
+
ctrl = Control.create(type, event: event, surface: self)
|
|
98
|
+
@controls[event] = ctrl
|
|
99
|
+
@logger.info "Surface: added control #{event} (#{type})"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
@pending_events << event if @pending_events
|
|
103
|
+
ctrl
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Removes a control from the inventory and drops its state.
|
|
107
|
+
# @param event [Symbol, String]
|
|
108
|
+
# @return [Control, nil] the removed control, or nil if unknown
|
|
109
|
+
# @api private
|
|
110
|
+
def remove_control(event)
|
|
111
|
+
event = event.to_sym
|
|
112
|
+
removed = @controls.delete(event)
|
|
113
|
+
@logger.info "Surface: removed control #{event}" if removed
|
|
114
|
+
removed
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Ends a full inventory dump. Any event present before the dump
|
|
118
|
+
# but not re-added between {#begin_inventory} and this call is
|
|
119
|
+
# purged. Re-emits all state so the surface re-syncs after the
|
|
120
|
+
# round-trip.
|
|
121
|
+
# @return [void]
|
|
122
|
+
# @api private
|
|
123
|
+
def end_inventory
|
|
124
|
+
if @pending_events
|
|
125
|
+
stale = @controls.keys - @pending_events.to_a
|
|
126
|
+
stale.each do |event|
|
|
127
|
+
@controls.delete(event)
|
|
128
|
+
@logger.info "Surface: purged stale control #{event}"
|
|
129
|
+
end
|
|
130
|
+
@pending_events = nil
|
|
131
|
+
end
|
|
132
|
+
emit_full_state
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Re-emits state for every known control. Used after an
|
|
136
|
+
# inventory dump or in response to a +state_request+ from the
|
|
137
|
+
# surface side.
|
|
138
|
+
# @return [void]
|
|
139
|
+
# @api private
|
|
140
|
+
def emit_full_state
|
|
141
|
+
@controls.each_value(&:emit_all_state)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Called by a Control when one of its properties changes; relays
|
|
145
|
+
# to the bridge.
|
|
146
|
+
# @param event [Symbol]
|
|
147
|
+
# @param prop [Symbol]
|
|
148
|
+
# @param value [Array<Object>] OSC-serializable values
|
|
149
|
+
# @return [void]
|
|
150
|
+
# @api private
|
|
151
|
+
def emit_state(event, prop, *value)
|
|
152
|
+
@bridge.send_state(event: event, prop: prop, value: value)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Abstract base for all controls on a {Surface}.
|
|
157
|
+
#
|
|
158
|
+
# Subclasses declare a {.type_name} matching the inventory string
|
|
159
|
+
# received from the surface, expose typed state accessors, and
|
|
160
|
+
# implement {#emit_all_state} to push their current state outbound.
|
|
161
|
+
#
|
|
162
|
+
# Setting a property emits exactly one OSC
|
|
163
|
+
# +/musalce/surface/state/<prop>+ message; the setter is therefore
|
|
164
|
+
# the canonical mutation point. Direct manipulation of instance
|
|
165
|
+
# variables bypasses emission.
|
|
166
|
+
class Control
|
|
167
|
+
# @return [Symbol] the event name this control is bound to
|
|
168
|
+
attr_reader :event
|
|
169
|
+
|
|
170
|
+
# @return [String, nil] the displayable message, +nil+ if unset
|
|
171
|
+
attr_reader :message
|
|
172
|
+
|
|
173
|
+
def initialize(event:, surface:)
|
|
174
|
+
@event = event
|
|
175
|
+
@surface = surface
|
|
176
|
+
@message = nil
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Sets the displayable message. Two-line text is allowed; the
|
|
180
|
+
# surface side is responsible for truncation/wrapping.
|
|
181
|
+
# @param value [String, nil]
|
|
182
|
+
# @return [void]
|
|
183
|
+
def message=(value)
|
|
184
|
+
@message = value
|
|
185
|
+
emit(:message, value.to_s)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Sets multiple attributes in a single call. Each key must name a
|
|
189
|
+
# writable attribute of the receiver's type; unknown keys raise
|
|
190
|
+
# +ArgumentError+ so a typo can't silently no-op.
|
|
191
|
+
#
|
|
192
|
+
# Order of assignment follows the kwargs hash insertion order
|
|
193
|
+
# (Ruby >= 1.9 guarantees insertion-ordered iteration). Each
|
|
194
|
+
# assignment goes through the regular setter, so each property
|
|
195
|
+
# emits its own +/musalce/surface/state/<prop>+ message. This is
|
|
196
|
+
# intentional: the wire protocol is per-property, and the plugin
|
|
197
|
+
# merges deltas into the rendered state, so two adjacent
|
|
198
|
+
# +/state/<prop>+ messages render exactly the same as a single
|
|
199
|
+
# batched one would.
|
|
200
|
+
#
|
|
201
|
+
# @example Toggle
|
|
202
|
+
# surface[:launch_chorus].set(enabled: true, message: "Chorus on")
|
|
203
|
+
# @example Encoder
|
|
204
|
+
# surface[:cutoff].set(range: 0..127, value: 64, message: "Cutoff")
|
|
205
|
+
#
|
|
206
|
+
# @param attrs [Hash{Symbol => Object}]
|
|
207
|
+
# @raise [ArgumentError] if a key doesn't correspond to a writer
|
|
208
|
+
# @return [self] for chaining
|
|
209
|
+
def set(**attrs)
|
|
210
|
+
attrs.each do |key, value|
|
|
211
|
+
writer = :"#{key}="
|
|
212
|
+
unless respond_to?(writer)
|
|
213
|
+
raise ArgumentError,
|
|
214
|
+
"#{self.class.type_name} control has no '#{key}' attribute"
|
|
215
|
+
end
|
|
216
|
+
public_send(writer, value)
|
|
217
|
+
end
|
|
218
|
+
self
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Re-emits every state property of this control. Called by the
|
|
222
|
+
# surface during +state_request+ or after inventory dumps.
|
|
223
|
+
# @return [void]
|
|
224
|
+
# @api private
|
|
225
|
+
def emit_all_state
|
|
226
|
+
emit(:message, @message.to_s) unless @message.nil?
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# @return [Symbol] the inventory type identifier
|
|
230
|
+
def self.type_name
|
|
231
|
+
raise NotImplementedError, "#{self} must implement .type_name"
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Instantiates the right subclass for the given type.
|
|
235
|
+
# @param type [Symbol]
|
|
236
|
+
# @return [Control]
|
|
237
|
+
# @raise [ArgumentError] if the type is unknown
|
|
238
|
+
# @api private
|
|
239
|
+
def self.create(type, **kwargs)
|
|
240
|
+
case type.to_sym
|
|
241
|
+
when :toggle then Toggle.new(**kwargs)
|
|
242
|
+
when :trigger then Trigger.new(**kwargs)
|
|
243
|
+
when :encoder then Encoder.new(**kwargs)
|
|
244
|
+
else raise ArgumentError, "Unknown control type: #{type.inspect}"
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
protected def emit(prop, *value)
|
|
249
|
+
@surface.emit_state(@event, prop, *value)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# A stateful on/off control with a three-valued enabled property:
|
|
254
|
+
# +true+ (on), +false+ (off available), +:inactive+ (control is
|
|
255
|
+
# known but currently not actionable, typically rendered dimmed).
|
|
256
|
+
class Toggle < Control
|
|
257
|
+
def self.type_name = :toggle
|
|
258
|
+
|
|
259
|
+
# @return [Boolean, Symbol] +true+, +false+, or +:inactive+
|
|
260
|
+
attr_reader :enabled
|
|
261
|
+
|
|
262
|
+
def initialize(**kwargs)
|
|
263
|
+
super
|
|
264
|
+
@enabled = :inactive
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Sets the enabled state. Accepts +true+, +false+, +:inactive+
|
|
268
|
+
# and their string equivalents.
|
|
269
|
+
# @param value [Boolean, Symbol, String]
|
|
270
|
+
# @raise [ArgumentError] on any other value
|
|
271
|
+
def enabled=(value)
|
|
272
|
+
@enabled = normalize_enabled(value)
|
|
273
|
+
emit(:enabled, @enabled.to_s)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# @return [Boolean] true iff +enabled+ is exactly +true+
|
|
277
|
+
def enabled?
|
|
278
|
+
@enabled == true
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# @return [Boolean] true iff +enabled+ is +:inactive+
|
|
282
|
+
def inactive?
|
|
283
|
+
@enabled == :inactive
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Convenience: set to +true+.
|
|
287
|
+
def on! = (self.enabled = true)
|
|
288
|
+
# Convenience: set to +false+.
|
|
289
|
+
def off! = (self.enabled = false)
|
|
290
|
+
# Convenience: set to +:inactive+.
|
|
291
|
+
def inactive! = (self.enabled = :inactive)
|
|
292
|
+
|
|
293
|
+
# Toggles between +true+ and +false+. From +:inactive+ goes to
|
|
294
|
+
# +true+ (entering active service).
|
|
295
|
+
def toggle!
|
|
296
|
+
case @enabled
|
|
297
|
+
when true then off!
|
|
298
|
+
when false then on!
|
|
299
|
+
else on!
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def emit_all_state
|
|
304
|
+
super
|
|
305
|
+
emit(:enabled, @enabled.to_s)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
private def normalize_enabled(v)
|
|
309
|
+
case v
|
|
310
|
+
when true, :true, 'true' then true
|
|
311
|
+
when false, :false, 'false' then false
|
|
312
|
+
when :inactive, 'inactive', nil then :inactive
|
|
313
|
+
else
|
|
314
|
+
raise ArgumentError,
|
|
315
|
+
"enabled must be true, false or :inactive (got #{v.inspect})"
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# A momentary, stateless control. Pressing it fires the
|
|
321
|
+
# corresponding sequencer event; the control itself carries no
|
|
322
|
+
# persistent on/off state beyond an optional {#message}.
|
|
323
|
+
class Trigger < Control
|
|
324
|
+
def self.type_name = :trigger
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# An absolute-value rotary or fader control with an integer
|
|
328
|
+
# +value+ inside an inclusive +range+. Range defaults to
|
|
329
|
+
# +0..127+ (standard MIDI 7-bit).
|
|
330
|
+
class Encoder < Control
|
|
331
|
+
def self.type_name = :encoder
|
|
332
|
+
|
|
333
|
+
# @return [Integer]
|
|
334
|
+
attr_reader :value
|
|
335
|
+
# @return [Range]
|
|
336
|
+
attr_reader :range
|
|
337
|
+
|
|
338
|
+
def initialize(**kwargs)
|
|
339
|
+
super
|
|
340
|
+
@range = 0..127
|
|
341
|
+
@value = 0
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# @param v [Integer, Numeric] clamped to {#range}
|
|
345
|
+
def value=(v)
|
|
346
|
+
@value = clamp_to_range(v.to_i)
|
|
347
|
+
emit(:value, @value)
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
# @param r [Range] inclusive integer range; +value+ is re-clamped
|
|
351
|
+
def range=(r)
|
|
352
|
+
raise ArgumentError, "range must be a Range (got #{r.inspect})" unless r.is_a?(Range)
|
|
353
|
+
@range = r
|
|
354
|
+
@value = clamp_to_range(@value)
|
|
355
|
+
emit(:range, r.min, r.max)
|
|
356
|
+
emit(:value, @value)
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def emit_all_state
|
|
360
|
+
super
|
|
361
|
+
emit(:range, @range.min, @range.max)
|
|
362
|
+
emit(:value, @value)
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
private def clamp_to_range(v)
|
|
366
|
+
[[v, @range.min].max, @range.max].min
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
end
|
data/lib/version.rb
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
# Musa Live Coding Environment Server.
|
|
2
|
+
#
|
|
3
|
+
# A Ruby server for live coding with Ableton Live and Bitwig Studio DAWs.
|
|
4
|
+
# Provides OSC communication, MIDI device management, and a REPL for
|
|
5
|
+
# interactive music composition using Musa-DSL.
|
|
6
|
+
#
|
|
7
|
+
# @see MusaLCEServer.run Entry point for starting the server
|
|
8
|
+
# @see MusaLCEServer::Daw Base class for DAW controllers
|
|
9
|
+
#
|
|
10
|
+
# @author Javier Sánchez Yeste
|
|
11
|
+
# @since 0.1.0
|
|
1
12
|
module MusaLCEServer
|
|
2
|
-
|
|
13
|
+
# Current version of the musalce-server gem.
|
|
14
|
+
VERSION = '0.8.1'.freeze
|
|
3
15
|
end
|
data/musalce-server.gemspec
CHANGED
|
@@ -3,7 +3,7 @@ require_relative 'lib/version'
|
|
|
3
3
|
Gem::Specification.new do |s|
|
|
4
4
|
s.name = 'musalce-server'
|
|
5
5
|
s.version = MusaLCEServer::VERSION
|
|
6
|
-
s.date = '
|
|
6
|
+
s.date = '2026-05-27'
|
|
7
7
|
s.summary = 'A Musa DSL live coding environment for Ableton Live 11 and Bitwig Studio 5'
|
|
8
8
|
s.description = 'This package implements the Server part of the Musa DSL Live Coding Environment for Ableton Live and Bitwig Studio'
|
|
9
9
|
s.authors = ['Javier Sánchez Yeste']
|
|
@@ -15,20 +15,27 @@ Gem::Specification.new do |s|
|
|
|
15
15
|
|
|
16
16
|
s.required_ruby_version = '>= 2.7'
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
# "changelog_uri" => ""
|
|
24
|
-
#}
|
|
18
|
+
s.metadata = {
|
|
19
|
+
'homepage_uri' => s.homepage,
|
|
20
|
+
'source_code_uri' => s.homepage,
|
|
21
|
+
'documentation_uri' => 'https://www.rubydoc.info/gems/musalce-server'
|
|
22
|
+
}
|
|
25
23
|
|
|
26
|
-
s.add_runtime_dependency 'musa-dsl', '~> 0
|
|
24
|
+
s.add_runtime_dependency 'musa-dsl', '~> 0.40'
|
|
27
25
|
|
|
28
|
-
s.add_runtime_dependency 'midi-communications', '~> 0.
|
|
29
|
-
s.add_runtime_dependency 'midi-events', '~> 0.
|
|
30
|
-
s.add_runtime_dependency 'midi-parser', '~> 0.
|
|
26
|
+
s.add_runtime_dependency 'midi-communications', '~> 0.7'
|
|
27
|
+
s.add_runtime_dependency 'midi-events', '~> 0.7'
|
|
28
|
+
s.add_runtime_dependency 'midi-parser', '~> 0.5'
|
|
31
29
|
|
|
32
|
-
#s.add_runtime_dependency 'eventmachine', '~> 1.2', '>= 1.2.7'
|
|
33
30
|
s.add_runtime_dependency 'osc-ruby', '~> 1.1', '>= 1.1.5'
|
|
31
|
+
# EventMachine is an *optional* dep of osc-ruby (only needed when
|
|
32
|
+
# using OSC::EMServer, which we do in daw.rb to receive OSC from
|
|
33
|
+
# the DAW extension). osc-ruby doesn't declare it, so we must.
|
|
34
|
+
s.add_runtime_dependency 'eventmachine', '~> 1.2'
|
|
35
|
+
|
|
36
|
+
s.add_development_dependency 'rspec', '~> 3'
|
|
37
|
+
|
|
38
|
+
s.add_development_dependency 'yard', '~> 0.9'
|
|
39
|
+
s.add_development_dependency 'redcarpet', '~> 3.6'
|
|
40
|
+
s.add_development_dependency 'webrick', '~> 1.8'
|
|
34
41
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: musalce-server
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Javier Sánchez Yeste
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 2026-05-27 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: musa-dsl
|
|
@@ -15,62 +15,56 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '0'
|
|
19
|
-
- - ">="
|
|
20
|
-
- !ruby/object:Gem::Version
|
|
21
|
-
version: 0.26.0
|
|
18
|
+
version: '0.40'
|
|
22
19
|
type: :runtime
|
|
23
20
|
prerelease: false
|
|
24
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
25
22
|
requirements:
|
|
26
23
|
- - "~>"
|
|
27
24
|
- !ruby/object:Gem::Version
|
|
28
|
-
version: '0'
|
|
29
|
-
- - ">="
|
|
30
|
-
- !ruby/object:Gem::Version
|
|
31
|
-
version: 0.26.0
|
|
25
|
+
version: '0.40'
|
|
32
26
|
- !ruby/object:Gem::Dependency
|
|
33
27
|
name: midi-communications
|
|
34
28
|
requirement: !ruby/object:Gem::Requirement
|
|
35
29
|
requirements:
|
|
36
30
|
- - "~>"
|
|
37
31
|
- !ruby/object:Gem::Version
|
|
38
|
-
version: '0.
|
|
32
|
+
version: '0.7'
|
|
39
33
|
type: :runtime
|
|
40
34
|
prerelease: false
|
|
41
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
42
36
|
requirements:
|
|
43
37
|
- - "~>"
|
|
44
38
|
- !ruby/object:Gem::Version
|
|
45
|
-
version: '0.
|
|
39
|
+
version: '0.7'
|
|
46
40
|
- !ruby/object:Gem::Dependency
|
|
47
41
|
name: midi-events
|
|
48
42
|
requirement: !ruby/object:Gem::Requirement
|
|
49
43
|
requirements:
|
|
50
44
|
- - "~>"
|
|
51
45
|
- !ruby/object:Gem::Version
|
|
52
|
-
version: '0.
|
|
46
|
+
version: '0.7'
|
|
53
47
|
type: :runtime
|
|
54
48
|
prerelease: false
|
|
55
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
56
50
|
requirements:
|
|
57
51
|
- - "~>"
|
|
58
52
|
- !ruby/object:Gem::Version
|
|
59
|
-
version: '0.
|
|
53
|
+
version: '0.7'
|
|
60
54
|
- !ruby/object:Gem::Dependency
|
|
61
55
|
name: midi-parser
|
|
62
56
|
requirement: !ruby/object:Gem::Requirement
|
|
63
57
|
requirements:
|
|
64
58
|
- - "~>"
|
|
65
59
|
- !ruby/object:Gem::Version
|
|
66
|
-
version: '0.
|
|
60
|
+
version: '0.5'
|
|
67
61
|
type: :runtime
|
|
68
62
|
prerelease: false
|
|
69
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
70
64
|
requirements:
|
|
71
65
|
- - "~>"
|
|
72
66
|
- !ruby/object:Gem::Version
|
|
73
|
-
version: '0.
|
|
67
|
+
version: '0.5'
|
|
74
68
|
- !ruby/object:Gem::Dependency
|
|
75
69
|
name: osc-ruby
|
|
76
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -91,6 +85,76 @@ dependencies:
|
|
|
91
85
|
- - ">="
|
|
92
86
|
- !ruby/object:Gem::Version
|
|
93
87
|
version: 1.1.5
|
|
88
|
+
- !ruby/object:Gem::Dependency
|
|
89
|
+
name: eventmachine
|
|
90
|
+
requirement: !ruby/object:Gem::Requirement
|
|
91
|
+
requirements:
|
|
92
|
+
- - "~>"
|
|
93
|
+
- !ruby/object:Gem::Version
|
|
94
|
+
version: '1.2'
|
|
95
|
+
type: :runtime
|
|
96
|
+
prerelease: false
|
|
97
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
98
|
+
requirements:
|
|
99
|
+
- - "~>"
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '1.2'
|
|
102
|
+
- !ruby/object:Gem::Dependency
|
|
103
|
+
name: rspec
|
|
104
|
+
requirement: !ruby/object:Gem::Requirement
|
|
105
|
+
requirements:
|
|
106
|
+
- - "~>"
|
|
107
|
+
- !ruby/object:Gem::Version
|
|
108
|
+
version: '3'
|
|
109
|
+
type: :development
|
|
110
|
+
prerelease: false
|
|
111
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
112
|
+
requirements:
|
|
113
|
+
- - "~>"
|
|
114
|
+
- !ruby/object:Gem::Version
|
|
115
|
+
version: '3'
|
|
116
|
+
- !ruby/object:Gem::Dependency
|
|
117
|
+
name: yard
|
|
118
|
+
requirement: !ruby/object:Gem::Requirement
|
|
119
|
+
requirements:
|
|
120
|
+
- - "~>"
|
|
121
|
+
- !ruby/object:Gem::Version
|
|
122
|
+
version: '0.9'
|
|
123
|
+
type: :development
|
|
124
|
+
prerelease: false
|
|
125
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
126
|
+
requirements:
|
|
127
|
+
- - "~>"
|
|
128
|
+
- !ruby/object:Gem::Version
|
|
129
|
+
version: '0.9'
|
|
130
|
+
- !ruby/object:Gem::Dependency
|
|
131
|
+
name: redcarpet
|
|
132
|
+
requirement: !ruby/object:Gem::Requirement
|
|
133
|
+
requirements:
|
|
134
|
+
- - "~>"
|
|
135
|
+
- !ruby/object:Gem::Version
|
|
136
|
+
version: '3.6'
|
|
137
|
+
type: :development
|
|
138
|
+
prerelease: false
|
|
139
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
140
|
+
requirements:
|
|
141
|
+
- - "~>"
|
|
142
|
+
- !ruby/object:Gem::Version
|
|
143
|
+
version: '3.6'
|
|
144
|
+
- !ruby/object:Gem::Dependency
|
|
145
|
+
name: webrick
|
|
146
|
+
requirement: !ruby/object:Gem::Requirement
|
|
147
|
+
requirements:
|
|
148
|
+
- - "~>"
|
|
149
|
+
- !ruby/object:Gem::Version
|
|
150
|
+
version: '1.8'
|
|
151
|
+
type: :development
|
|
152
|
+
prerelease: false
|
|
153
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
154
|
+
requirements:
|
|
155
|
+
- - "~>"
|
|
156
|
+
- !ruby/object:Gem::Version
|
|
157
|
+
version: '1.8'
|
|
94
158
|
description: This package implements the Server part of the Musa DSL Live Coding Environment
|
|
95
159
|
for Ableton Live and Bitwig Studio
|
|
96
160
|
email: javier.sy@gmail.com
|
|
@@ -99,11 +163,15 @@ executables:
|
|
|
99
163
|
extensions: []
|
|
100
164
|
extra_rdoc_files: []
|
|
101
165
|
files:
|
|
166
|
+
- ".github/workflows/notify-plugin.yml"
|
|
102
167
|
- ".gitignore"
|
|
168
|
+
- ".version"
|
|
169
|
+
- ".yardopts"
|
|
103
170
|
- Gemfile
|
|
104
171
|
- LICENSE
|
|
105
172
|
- README.md
|
|
106
173
|
- bin/musalce-server
|
|
174
|
+
- docs/architecture.md
|
|
107
175
|
- lib/bitwig/bitwig.rb
|
|
108
176
|
- lib/bitwig/controllers.rb
|
|
109
177
|
- lib/bitwig/handler.rb
|
|
@@ -114,12 +182,17 @@ files:
|
|
|
114
182
|
- lib/live/tracks.rb
|
|
115
183
|
- lib/midi-devices.rb
|
|
116
184
|
- lib/musalce-server.rb
|
|
185
|
+
- lib/surface-bridge.rb
|
|
186
|
+
- lib/surface.rb
|
|
117
187
|
- lib/version.rb
|
|
118
188
|
- musalce-server.gemspec
|
|
119
189
|
homepage: https://github.com/javier-sy/musalce-server
|
|
120
190
|
licenses:
|
|
121
191
|
- GPL-3.0-or-later
|
|
122
|
-
metadata:
|
|
192
|
+
metadata:
|
|
193
|
+
homepage_uri: https://github.com/javier-sy/musalce-server
|
|
194
|
+
source_code_uri: https://github.com/javier-sy/musalce-server
|
|
195
|
+
documentation_uri: https://www.rubydoc.info/gems/musalce-server
|
|
123
196
|
rdoc_options: []
|
|
124
197
|
require_paths:
|
|
125
198
|
- lib
|