Class: MusaLCEServer::Surface

Inherits:
Object
  • Object
show all
Defined in:
lib/surface.rb

Overview

Authoritative model of the physical control surface (Stream Deck buttons, encoders, …) as seen from the server.

The surface is the abstraction shared with hardware but surface-agnostic: it knows only about named controls with a type and dynamic state. Each control is keyed by its event name — a Symbol (e.g. +:launch_chorus+) that doubles as the identifier used with the sequencer's +on+/+launch+ mechanism when the control fires. In MusaLCE the unit of interaction is always the event; the surface is the physical face of one or more events.

Ownership of the two data axes:

  • Inventory (which controls exist and their type) flows inbound from Pulso Bridge through the DAW extension; the server trusts what it receives (Pulso validates type consistency across physical instances).
  • State (message, enabled, value, …) is owned by the server: the score writes to +surface[:event]+ and changes propagate outbound on +/musalce/surface/state/+.

Inventory may arrive as a full dump (between +inventory/begin+ and +inventory/end+, in which case events absent from the dump are purged at end) or as runtime deltas (+inventory/add+ / +inventory/remove+). Re-adding an event with the same type preserves its state; a type change replaces the control and resets state.

All mutating methods are expected to run on the sequencer tick thread (inbound OSC messages are routed there by SurfaceBridge). Score code writing +surface[:event].xxx =+ ... also runs on that thread (inside +at+/+every+/+on+ blocks), which keeps access serial without explicit locking.

Instance Method Summary collapse

Constructor Details

#initialize(bridge:, logger:) ⇒ Surface

Returns a new instance of Surface.

Parameters:

  • bridge (SurfaceBridge)

    the bridge used to emit state outbound

  • logger (Logger)

    the logger



40
41
42
43
44
45
# File 'lib/surface.rb', line 40

def initialize(bridge:, logger:)
  @bridge = bridge
  @logger = logger
  @controls = {}
  @pending_events = nil
end

Instance Method Details

#[](event) ⇒ Control?

Returns the control for the given event, or +nil+ if unknown.

A control becomes known once its inventory entry has been received from the surface. Score code that runs before that should use safe navigation (+surface[:foo]&.enabled = true+) or guard with #known?.

Parameters:

  • event (Symbol, String)

Returns:



56
57
58
# File 'lib/surface.rb', line 56

def [](event)
  @controls[event.to_sym]
end

#add_control(event, type) ⇒ Control

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Registers (or refreshes) a control in the inventory.

If a control with the same event and type already exists, its state is preserved. If the type differs, the existing control is replaced with a fresh instance (state reset).

Parameters:

  • event (Symbol, String)
  • type (Symbol, String)

    one of +:toggle+, +:trigger+, +:encoder+

Returns:

  • (Control)

    the (possibly new) control



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/surface.rb', line 89

def add_control(event, type)
  event = event.to_sym
  type = type.to_sym
  existing = @controls[event]

  if existing && existing.class.type_name == type
    ctrl = existing
  else
    ctrl = Control.create(type, event: event, surface: self)
    @controls[event] = ctrl
    @logger.info "Surface: added control #{event} (#{type})"
  end

  @pending_events << event if @pending_events
  ctrl
end

#begin_inventoryvoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Begins a full inventory dump. Events that are not re-added before #end_inventory are purged.



75
76
77
# File 'lib/surface.rb', line 75

def begin_inventory
  @pending_events = Set.new
end

#emit_full_statevoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Re-emits state for every known control. Used after an inventory dump or in response to a +state_request+ from the surface side.



140
141
142
# File 'lib/surface.rb', line 140

def emit_full_state
  @controls.each_value(&:emit_all_state)
end

#emit_state(event, prop, *value) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Called by a Control when one of its properties changes; relays to the bridge.

Parameters:

  • event (Symbol)
  • prop (Symbol)
  • value (Array<Object>)

    OSC-serializable values



151
152
153
# File 'lib/surface.rb', line 151

def emit_state(event, prop, *value)
  @bridge.send_state(event: event, prop: prop, value: value)
end

#end_inventoryvoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Ends a full inventory dump. Any event present before the dump but not re-added between #begin_inventory and this call is purged. Re-emits all state so the surface re-syncs after the round-trip.



123
124
125
126
127
128
129
130
131
132
133
# File 'lib/surface.rb', line 123

def end_inventory
  if @pending_events
    stale = @controls.keys - @pending_events.to_a
    stale.each do |event|
      @controls.delete(event)
      @logger.info "Surface: purged stale control #{event}"
    end
    @pending_events = nil
  end
  emit_full_state
end

#eventsArray<Symbol>

Returns all known control events.

Returns:

  • (Array<Symbol>)

    all known control events



61
62
63
# File 'lib/surface.rb', line 61

def events
  @controls.keys
end

#known?(event) ⇒ Boolean

Returns whether a control with this event is in the inventory.

Parameters:

  • event (Symbol, String)

Returns:

  • (Boolean)

    whether a control with this event is in the inventory



67
68
69
# File 'lib/surface.rb', line 67

def known?(event)
  @controls.key?(event.to_sym)
end

#remove_control(event) ⇒ Control?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Removes a control from the inventory and drops its state.

Parameters:

  • event (Symbol, String)

Returns:

  • (Control, nil)

    the removed control, or nil if unknown



110
111
112
113
114
115
# File 'lib/surface.rb', line 110

def remove_control(event)
  event = event.to_sym
  removed = @controls.delete(event)
  @logger.info "Surface: removed control #{event}" if removed
  removed
end