Extend REAPER extension to read/write automation items, wire desktop app to it over UDP
extension-reaper-macos: split main.cpp into tracking.cpp (polls selected envelope/automation items, reads/writes D_BASELINE) and socket.cpp (basic UDP listener, non-blocking, drains all pending packets per tick). Nudging now applies to every selected automation item across every track in the project, not just the first one found. app-desktop-macos: new "Extension" column (mirrors the OSC/Remote columns) to enable/disable the UDP connection and point it at a host/port, plus a log window. FocusFaderWidget gets an OSC/Ext output-mode toggle (mirrors TransportWidget's MIDI/OSC toggle) so a fader can drive the extension directly instead of/alongside OSC. Also renamed the Remote/Tablet buttons for clarity and split their status indicator into its own column. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ from zonebutton import ZoneButton
|
||||
from colorswatchpopup import ColorSwatchPopup
|
||||
from osc_sender import send_osc_message
|
||||
from midi_sender import send_cc
|
||||
from extension_sender import send_to_extension
|
||||
|
||||
|
||||
class FocusFaderWidget(QWidget):
|
||||
@@ -96,6 +97,29 @@ class FocusFaderWidget(QWidget):
|
||||
_grp2_layout.setContentsMargins(4, 4, 4, 4)
|
||||
_grp2_layout.setSpacing(3)
|
||||
|
||||
# OSC / Ext mode selector
|
||||
mode_row = QHBoxLayout()
|
||||
mode_row.setContentsMargins(0, 0, 0, 0)
|
||||
mode_row.setSpacing(2)
|
||||
self.osc_mode_btn = QPushButton("OSC")
|
||||
self.osc_mode_btn.setFixedHeight(18)
|
||||
self.osc_mode_btn.setCheckable(True)
|
||||
self.osc_mode_btn.setChecked(True)
|
||||
self.osc_mode_btn.setStyleSheet("border: none;")
|
||||
self.ext_mode_btn = QPushButton("Ext")
|
||||
self.ext_mode_btn.setFixedHeight(18)
|
||||
self.ext_mode_btn.setCheckable(True)
|
||||
self.ext_mode_btn.setChecked(False)
|
||||
self.ext_mode_btn.setStyleSheet("border: none;")
|
||||
self.osc_mode_btn.clicked.connect(lambda: self.set_output_mode("osc"))
|
||||
self.ext_mode_btn.clicked.connect(lambda: self.set_output_mode("ext"))
|
||||
mode_row.addWidget(self.osc_mode_btn)
|
||||
mode_row.addWidget(self.ext_mode_btn)
|
||||
_grp2_layout.addLayout(mode_row)
|
||||
|
||||
self.output_mode = "osc"
|
||||
self._last_ext_value = None # tracks previous fader value, for delta-on-move in Ext mode
|
||||
|
||||
# OSC address field
|
||||
self.osc_addr_out_edit = QLineEdit("/track/volume")
|
||||
self.osc_addr_out_edit.setAlignment(Qt.AlignmentFlag.AlignHCenter)
|
||||
@@ -376,6 +400,24 @@ class FocusFaderWidget(QWidget):
|
||||
self.touch_sense_mode = "jl_cooper" if index == 1 else "off"
|
||||
self.is_touching = False
|
||||
|
||||
def set_output_mode(self, mode):
|
||||
self.output_mode = mode
|
||||
if mode == "osc":
|
||||
self.osc_mode_btn.setChecked(True)
|
||||
self.ext_mode_btn.setChecked(False)
|
||||
self.osc_mode_btn.setStyleSheet("font-weight: bold; color: #00e676;")
|
||||
self.ext_mode_btn.setStyleSheet("")
|
||||
self.osc_addr_out_edit.setVisible(True)
|
||||
self.cc_output_lbl.setText(self._osc_output_label(self.fader.value()))
|
||||
else:
|
||||
self.ext_mode_btn.setChecked(True)
|
||||
self.osc_mode_btn.setChecked(False)
|
||||
self.ext_mode_btn.setStyleSheet("font-weight: bold; color: #00e676;")
|
||||
self.osc_mode_btn.setStyleSheet("")
|
||||
self.osc_addr_out_edit.setVisible(False)
|
||||
self.cc_output_lbl.setText("Ext [--]")
|
||||
self._last_ext_value = None # reset delta tracking on mode switch
|
||||
|
||||
def receive_osc_value(self, value):
|
||||
"""Update the fader to reflect feedback from the DAW, without echoing it back out."""
|
||||
if self.is_touching:
|
||||
@@ -449,8 +491,11 @@ class FocusFaderWidget(QWidget):
|
||||
self._set_readout_style("clear")
|
||||
|
||||
def on_fader_moved(self, value):
|
||||
addr = self.osc_addr_out_edit.text().strip() or "/track/volume"
|
||||
self.cc_output_lbl.setText(self._osc_output_label(value))
|
||||
if self.output_mode == "ext":
|
||||
self.cc_output_lbl.setText(f"Ext [{value}]")
|
||||
else:
|
||||
self.cc_output_lbl.setText(self._osc_output_label(value))
|
||||
|
||||
if self.pickup_mode and self.hunting:
|
||||
crossed = (
|
||||
(self.prev_position < self.last_sent <= value) or
|
||||
@@ -462,17 +507,32 @@ class FocusFaderWidget(QWidget):
|
||||
self._stop_blink()
|
||||
self._set_readout_style("orange")
|
||||
self.last_sent = value
|
||||
send_osc_message(addr, value / 127.0)
|
||||
self._send_hw_feedback(value)
|
||||
self._emit_value(value)
|
||||
else:
|
||||
self.readout.setText(f"{addr} → {value}")
|
||||
self.readout.setText(f"[{value}]")
|
||||
return
|
||||
else:
|
||||
self.last_sent = value
|
||||
self.prev_position = value
|
||||
self._emit_value(value)
|
||||
self.readout.setText(f"[{value}]")
|
||||
|
||||
def _emit_value(self, value):
|
||||
"""Sends the current fader value out via whichever protocol is
|
||||
selected. Ext mode sends a delta (change since last move), not the
|
||||
absolute position — the extension's UDP protocol only understands
|
||||
deltas added to the current baseline, not absolute targets."""
|
||||
if self.output_mode == "ext":
|
||||
if self._last_ext_value is None:
|
||||
self._last_ext_value = value
|
||||
delta = (value - self._last_ext_value) / 127.0
|
||||
self._last_ext_value = value
|
||||
if delta != 0:
|
||||
send_to_extension(delta)
|
||||
else:
|
||||
addr = self.osc_addr_out_edit.text().strip() or "/track/volume"
|
||||
send_osc_message(addr, value / 127.0)
|
||||
self._send_hw_feedback(value)
|
||||
self.readout.setText(f"{addr} → {value}")
|
||||
self._send_hw_feedback(value)
|
||||
|
||||
def _send_hw_feedback(self, value):
|
||||
"""Sends a translated MIDI CC out (e.g. so a motorized fader's position
|
||||
@@ -558,6 +618,7 @@ class FocusFaderWidget(QWidget):
|
||||
"hw_out_cc": self.hw_cc_spin.value(),
|
||||
"hw_out_ch": self.hw_ch_spin.value(),
|
||||
"touch_sense_mode": self.touch_sense_mode,
|
||||
"output_mode": self.output_mode,
|
||||
}
|
||||
|
||||
def set_state(self, state):
|
||||
@@ -599,3 +660,4 @@ class FocusFaderWidget(QWidget):
|
||||
self.touch_sense_combo.blockSignals(False)
|
||||
self.is_touching = False
|
||||
self._update_hw_visibility()
|
||||
self.set_output_mode(state.get("output_mode", "osc"))
|
||||
|
||||
Reference in New Issue
Block a user