reaper-osc
Version:
Controls Cockos Reaper using OSC
263 lines • 9.88 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReaperConfiguration = exports.Reaper = void 0;
/**
* Contains classes for controlling Reaper via OSC
* @module
*/
const Messages_1 = require("./Messages");
const Tracks_1 = require("./Tracks");
const Transport_1 = require("./Transport");
const osc = __importStar(require("osc"));
const Handlers_1 = require("./Handlers");
const Notify_1 = require("./Notify");
/**
* Allows control of an instance of Reaper via OSC.
*
* @example
* ```typescript
* // Create an instance of Reaper using default settings
* const reaper = new Reaper();
* // Start OSC
* reaper.startOsc();
* // Give the port a chance to open, then tell Reaper to start playback
* setTimeout(() => {reaper.transport.play();}, 100);
*```
*/
let Reaper = class Reaper {
constructor(config = new ReaperConfiguration()) {
this._isMetronomeEnabled = false;
this._handlers = [];
this._isReady = false;
this._tracks = [];
this._transport = new Transport_1.Transport(message => this.sendOscMessage(message));
this._osc = new osc.UDPPort({
localAddress: config.localAddress,
localPort: config.localPort,
remoteAddress: config.remoteAddress,
remotePort: config.remotePort,
broadcast: true,
metadata: true,
});
this._afterMessageReceived = config.afterMessageReceived;
this._log = config.log;
this.initOsc();
this.initHandlers();
this._masterTrack = new Tracks_1.Track(0, config.numberOfFx, message => this.sendOscMessage(message));
for (let i = 0; i < config.numberOfTracks; i++) {
this._tracks[i] = new Tracks_1.Track(i + 1, config.numberOfFx, message => this.sendOscMessage(message));
}
}
/** Indicates whether the metronome is enabled */
get isMetronomeEnabled() {
return this._isMetronomeEnabled;
}
/** Indicates whether OSC is ready to send and receive messages */
get isReady() {
return this._isReady;
}
/** The master track */
get master() {
return this._masterTrack;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
onPropertyChanged(property, callback) {
throw new Error('not implemented');
}
/**
* Triggers the action `Control surface: Refresh all surfaces` (Command ID: 41743)
*/
refreshControlSurfaces() {
this.triggerAction(41743);
}
/**
* Send a message to Reaper via OSC. Messages may not be sent while {@link Reaper.isReady} is false.
* @param message The OSC message to be sent
*/
sendOscMessage(message) {
if (!this._isReady) {
throw new Error("Can't send while OSC is not ready");
}
this._osc.send(message);
this._log('debug', 'OSC Message sent', message);
}
/** Open the OSC port and start listening for messages */
async start() {
if (this.isReady) {
return;
}
let errorCallback;
let readyCallback;
const promise = new Promise((resolve, reject) => {
errorCallback = (err) => {
this._log('debug', 'Error opening OSC connection', err);
reject(err);
};
readyCallback = () => {
this._log('debug', 'OSC listener ready');
this._isReady = true;
resolve();
};
this._osc.once('ready', readyCallback);
this._osc.once('error', errorCallback);
});
const removeListeners = () => {
this._osc.removeListener('ready', readyCallback);
this._osc.removeListener('error', errorCallback);
};
this._osc.open();
try {
await promise;
}
finally {
removeListeners();
}
}
/** Stop listening for OSC messages */
async stop() {
if (!this._isReady) {
return;
}
const promise = new Promise(resolve => {
this._osc.once('close', () => {
this._log('debug', 'OSC listener stopped');
resolve();
});
});
this._isReady = false;
this._osc.close();
return promise;
}
/** Toggle the metronome on or off */
toggleMetronome() {
this.sendOscMessage(new Messages_1.OscMessage('/click'));
}
/** The current bank of tracks */
get tracks() {
return this._tracks;
}
/** Transport controls */
get transport() {
return this._transport;
}
/**
* Trigger a Reaper action
* @param commandId The Command ID of the action to be triggered.
* @param value The value to send for the action. Note that some actions expect the CC value (0-127) while others expect a decimal value between 0 and 1.
* @example
* ```typescript
* // Trigger action 'Track: Toggle mute for master track'
* reaper.triggerAction(14);
* // Trigger SWS Extension action 'SWS/S&M: Live Config #1 - Apply config (MIDI/OSC only)' with a CC value of 3, selects config #3
* reaper.triggerAction('_S&M_LIVECFG_APPLY1', 3);
* // Trigger action 'Track: Set volume for track 01 (MIDI CC/OSC only)' with a value of 0.75, sets volume of track 1 to +0.0dB
* reaper.triggerAction(20, 0.7156)
* ```
*/
triggerAction(commandId, value = null) {
if (value !== null && (value < 0 || value > 127)) {
throw new RangeError('Values must be between 0 and 127 inclusive');
}
this.sendOscMessage(new Messages_1.ActionMessage(commandId, value));
}
initHandlers() {
this._handlers.push(new Handlers_1.TrackMessageHandler(trackNumber => {
if (trackNumber == 0) {
return this._masterTrack;
}
return this._tracks[trackNumber - 1] !== undefined ? this._tracks[trackNumber - 1] : null;
}), new Handlers_1.TransportMessageHandler(this._transport), new Handlers_1.BooleanMessageHandler('/click', value => (this._isMetronomeEnabled = value)));
}
initOsc() {
this._osc.on('error', (err) => {
this._log('error', 'OSC error received', err);
});
this._osc.on('message', (rawMessage) => {
const message = new Messages_1.OscMessage(rawMessage.address, rawMessage.args);
let handled = false;
for (const handler of this._handlers) {
if (handler.handle(message)) {
handled = true;
break;
}
}
if (this._afterMessageReceived !== null) {
this._afterMessageReceived(message, handled);
}
});
}
};
__decorate([
Notify_1.notify('isMetronomeEnabled')
], Reaper.prototype, "_isMetronomeEnabled", void 0);
Reaper = __decorate([
Notify_1.notifyOnPropertyChanged
], Reaper);
exports.Reaper = Reaper;
class ReaperConfiguration {
constructor() {
this.afterMessageReceived = null;
/** The address to listen for Reaper OSC messages on */
this.localAddress = '127.0.0.1';
/** The port to listen for Reaper OSC messages on */
this.localPort = 9000;
/** Function for logging messages. Defaults to logging to console */
this.log = ConsoleLogger;
/** Number of FX per track */
this.numberOfFx = 8;
/** Number of tracks per bank */
this.numberOfTracks = 8;
/** The address to send Reaper OSC messages to */
this.remoteAddress = '127.0.0.1';
/** The port to send Reaper OSC messages to */
this.remotePort = 8000;
}
}
exports.ReaperConfiguration = ReaperConfiguration;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function ConsoleLogger(level, message, ...optionalParams) {
switch (level) {
case 'debug': {
console.debug(message, optionalParams);
break;
}
case 'info': {
console.log(message, optionalParams);
break;
}
case 'warn': {
console.warn(message, optionalParams);
break;
}
case 'error': {
console.error(message, optionalParams);
break;
}
}
}
//# sourceMappingURL=Reaper.js.map