UNPKG

@mirage-xr/node-abletonlink

Version:

ableton link for node.js with Link 3.1.3 features

1,634 lines (1,277 loc) 48.2 kB
# @mirage-xr/node-abletonlink > A modernized Node.js/TypeScript interface to [Ableton Link](https://github.com/ableton/link), refactored from the original [2bbb/node-abletonlink](https://github.com/2bbb/node-abletonlink). > This fork updates the API surface, adds Link 3.1.3 features, and includes expanded docs and TypeScript definitions—while keeping attribution, license, and spirit of the original work. **Package**: `@mirage-xr/node-abletonlink` **Version**: 3.1.3 **Link Library**: 3.1.3 **Supported OS**: macOS, Linux, Windows **Node.js**: 18.13.0+ --- ## Table of Contents - [Overview](#overview) - [What’s New in This Fork](#whats-new-in-this-fork) - [Installation](#installation) - [Requirements & Build Tools](#requirements--build-tools) - [Quick Start](#quick-start) - [API Documentation](#api-documentation) - [Compatibility](#compatibility) - [Troubleshooting](#troubleshooting) - [Credits & Attribution](#credits--attribution) - [License](#license) - [Donations](#donations) --- ## Overview The Node.js Ableton Link module provides a complete JavaScript/TypeScript interface to the Ableton Link library, enabling real-time musical synchronization across multiple applications. This module supports both the original Link functionality and the enhanced features from Link 3.1.3. --- ## What’s New in This Fork - Updated to **Link 3.1.3** and surfaced advanced timeline/session methods - **TypeScript definitions** (`index.d.ts`) for full IntelliSense - **Expanded README/API docs** with practical examples - **Session/network/platform helpers** for monitoring and diagnostics - **Safer defaults & clearer errors** - **Non-breaking spirit**: original semantics retained wherever feasible > This is not an official continuation of the original—just a community fork with modernization and extra docs. Full credit to the original authors (see [Credits & Attribution](#credits--attribution)). --- ## Installation ```bash npm install @mirage-xr/node-abletonlink ``` --- ## Requirements & Build Tools This module uses a native addon (node-addon-api). You’ll need platform build tools: **Common** - Python (v2.7 or Python 3 as supported by node-gyp) - `node-gyp` prerequisites (see node-gyp docs) **macOS** - Xcode (with Command Line Tools) **Linux / UNIX** - `make` and a C++ compiler toolchain **Windows** - Microsoft windows-build-tools ```bash npm install --global --production windows-build-tools ``` **Tested historically (original project):** - macOS 10.14.6 + Xcode with Node.js 10.16.0 - Windows 10 + windows-build-tools with Node.js 10.16.0 **This fork targets Node.js 18.13.0+** (older versions may work with additional config but are not supported). --- ## Quick Start ```javascript const abletonlink = require('@mirage-xr/node-abletonlink'); // bpm=120, quantum=4 (e.g., 4/4), enabled=true const link = new abletonlink(120, 4, true); // Receive periodic updates (ms interval) link.startUpdate(60, (beat, phase, bpm, playState) => { console.log(`Beat: ${beat}, Phase: ${phase}, BPM: ${bpm}, Playing: ${playState}`); }); // Later // link.stopUpdate(); ``` --- ## API Documentation # Node.js Ableton Link API Documentation ## Overview The Node.js Ableton Link module provides a complete JavaScript/TypeScript interface to the Ableton Link library, enabling real-time musical synchronization across multiple applications. This module supports both the original Link functionality and the enhanced features from Link 3.1.3. **Version**: 3.1.3 **Link Library**: 3.1.3 **Platform Support**: macOS, Linux, Windows **Node.js**: 18.13.0+ --- ## Installation ```bash npm install @mirage-xr/node-abletonlink ``` **Requirements**: - Node.js 18.13.0 or higher - C++ compiler (for native module compilation) - Platform-specific build tools --- ## Basic Usage ```javascript const abletonlink = require('@mirage-xr/node-abletonlink'); // Create a new Link instance const link = new abletonlink(120, 4, true); // Start receiving updates link.startUpdate(60, (beat, phase, bpm, playState) => { console.log(`Beat: ${beat}, Phase: ${phase}, BPM: ${bpm}`); }); ``` --- ## Constructor ### `new abletonlink(bpm, quantum, enable)` Creates a new Ableton Link instance. **Parameters**: - `bpm` (number): Initial tempo in beats per minute (default: 120.0) - `quantum` (number): Musical quantum for phase calculation (default: 4.0) - `enable` (boolean): Whether to enable Link synchronization (default: true) **Returns**: `AbletonLink` instance **Example**: ```javascript // Basic initialization const link = new abletonlink(120, 4, true); // With custom tempo and quantum const link = new abletonlink(140, 8, true); // Disabled initially const link = new abletonlink(120, 4, false); ``` --- ## Properties ### Core Properties #### `bpm` (number) **Get/Set**: Readable and writable **Description**: Current tempo in beats per minute **Range**: Positive numbers (typically 20-999 BPM) **Implementation Examples**: ```javascript // Get current tempo console.log(`Current tempo: ${link.bpm}`); // Set to specific music genres link.bpm = 128; // House music link.bpm = 140; // Drum & Bass link.bpm = 90; // Hip-hop link.bpm = 120; // Pop/Rock // Dynamic tempo changes setInterval(() => { const currentBpm = link.bpm; if (currentBpm < 140) { link.bpm = currentBpm + 1; // Gradual tempo increase } }, 1000); // Tempo validation function setTempo(newTempo) { if (newTempo >= 20 && newTempo <= 999) { link.bpm = newTempo; console.log(`Tempo set to ${newTempo} BPM`); } else { console.error('Invalid tempo range (20-999 BPM)'); } } ``` #### `quantum` (number) **Get/Set**: Readable and writable **Description**: Musical quantum for phase calculation **Range**: Positive numbers (typically 1-32) **Implementation Examples**: ```javascript // Get current quantum console.log(`Current quantum: ${link.quantum}`); // Set to common time signatures link.quantum = 4; // 4/4 time (common time) link.quantum = 3; // 3/4 time (waltz) link.quantum = 6; // 6/8 time (compound duple) link.quantum = 8; // 8/8 time (complex meter) // Dynamic quantum changes based on music structure function setTimeSignature(numerator, denominator) { link.quantum = numerator; console.log(`Time signature set to ${numerator}/${denominator}`); } // Quantum validation function setQuantum(newQuantum) { if (newQuantum >= 1 && newQuantum <= 32) { link.quantum = newQuantum; console.log(`Quantum set to ${newQuantum}`); } else { console.error('Invalid quantum range (1-32)'); } } // Phase calculation with quantum function getPhaseInBeats() { return link.phase / link.quantum; } ``` #### `enabled` (boolean) **Get/Set**: Readable and writable **Description**: Whether Link synchronization is enabled **Implementation Examples**: ```javascript // Get current enabled state console.log(`Link enabled: ${link.enabled}`); // Basic enable/disable link.enabled = false; // Disable synchronization link.enabled = true; // Enable synchronization // Conditional enabling function enableIfNetworkAvailable() { const networkStats = link.getNetworkStats(); if (networkStats.connectionQuality !== 'poor') { link.enabled = true; console.log('Link enabled - network quality good'); } else { link.enabled = false; console.log('Link disabled - poor network quality'); } } // Toggle functionality function toggleLink() { link.enabled = !link.enabled; console.log(`Link ${link.enabled ? 'enabled' : 'disabled'}`); } // Auto-disable on errors function handleLinkError() { link.enabled = false; console.log('Link disabled due to error'); // Retry after delay setTimeout(() => { link.enabled = true; console.log('Link re-enabled'); }, 5000); } ``` #### `beat` (number) **Get**: Read-only **Description**: Current beat position in the musical timeline **Range**: 0.0 and above **Implementation Examples**: ```javascript // Get current beat console.log(`Current beat: ${link.beat}`); // Beat-based animations function updateBeatVisualization() { const currentBeat = link.beat; const beatFraction = currentBeat % 1; if (beatFraction < 0.1) { triggerBeatAnimation(); // On beat } else if (beatFraction < 0.5) { updateBeatProgress(beatFraction); // Beat progress } } // Beat counting function countBeats() { const currentBeat = link.beat; const wholeBeats = Math.floor(currentBeat); const beatFraction = currentBeat - wholeBeats; console.log(`Beat ${wholeBeats}, ${(beatFraction * 100).toFixed(0)}% complete`); } // Beat-based timing function scheduleOnBeat(targetBeat) { const currentBeat = link.beat; const beatsUntilTarget = targetBeat - currentBeat; if (beatsUntilTarget > 0) { const msUntilTarget = (beatsUntilTarget * 60 / link.bpm) * 1000; setTimeout(() => { console.log(`Target beat ${targetBeat} reached!`); }, msUntilTarget); } } // Beat validation function isValidBeat(beat) { return beat >= 0 && Number.isFinite(beat); } ``` #### `phase` (number) **Get**: Read-only **Description**: Current phase within the current quantum **Range**: 0.0 to quantum value **Implementation Examples**: ```javascript // Get current phase console.log(`Current phase: ${link.phase}`); // Phase-based visualizations function updatePhaseMeter() { const currentPhase = link.phase; const quantum = link.quantum; const phasePercentage = (currentPhase / quantum) * 100; updateProgressBar(phasePercentage); if (phasePercentage < 10) { highlightBeatMarker(); // Start of measure } } // Phase synchronization function syncToPhase(targetPhase) { const currentPhase = link.phase; const phaseDiff = targetPhase - currentPhase; if (Math.abs(phaseDiff) > 0.1) { console.log(`Phase offset: ${phaseDiff.toFixed(3)} beats`); adjustTiming(phaseDiff); } } // Phase-based effects function applyPhaseEffects() { const currentPhase = link.phase; const quantum = link.quantum; if (currentPhase < quantum * 0.25) { applyIntroEffect(); // First quarter } else if (currentPhase < quantum * 0.5) { applyBuildEffect(); // Second quarter } else if (currentPhase < quantum * 0.75) { applyDropEffect(); // Third quarter } else { applyOutroEffect(); // Last quarter } } // Phase calculation utilities function getPhaseInMeasures() { return link.phase / link.quantum; } function isPhaseInRange(minPhase, maxPhase) { const currentPhase = link.phase; return currentPhase >= minPhase && currentPhase <= maxPhase; } ``` #### `isPlaying` (boolean) **Get**: Read-only **Description**: Whether the transport is currently playing **Implementation Examples**: ```javascript // Get current play state console.log(`Transport playing: ${link.isPlaying}`); // Play state monitoring function monitorPlayState() { const isCurrentlyPlaying = link.isPlaying; if (isCurrentlyPlaying) { startAudioEngine(); startVisualization(); console.log('Transport started - audio and visuals active'); } else { stopAudioEngine(); pauseVisualization(); console.log('Transport stopped - audio and visuals paused'); } } // Auto-play functionality function autoPlayOnBeat(beatNumber) { const currentBeat = link.beat; if (Math.floor(currentBeat) === beatNumber && !link.isPlaying) { console.log(`Auto-play triggered at beat ${beatNumber}`); // Trigger play state change } } // Play state validation function validatePlayState() { const isPlaying = link.isPlaying; const currentBeat = link.beat; if (isPlaying && currentBeat < 0) { console.warn('Playing with negative beat - may indicate timing issue'); } return isPlaying; } // Conditional actions based on play state function handleTransportChange() { if (link.isPlaying) { enableRealTimeUpdates(); startBeatTracking(); } else { disableRealTimeUpdates(); stopBeatTracking(); } } ``` #### `isStartStopSyncEnabled` (boolean) **Get/Set**: Readable and writable **Description**: Whether start/stop synchronization is enabled **Implementation Examples**: ```javascript // Get current sync state console.log(`Start/Stop sync: ${link.isStartStopSyncEnabled}`); // Basic enable/disable link.isStartStopSyncEnabled = true; // Enable sync link.isStartStopSyncEnabled = false; // Disable sync // Conditional sync enabling function enableSyncIfMultiplePeers() { const sessionInfo = link.getSessionInfo(); if (sessionInfo.numPeers > 1) { link.isStartStopSyncEnabled = true; console.log('Start/Stop sync enabled - multiple peers detected'); } else { link.isStartStopSyncEnabled = false; console.log('Start/Stop sync disabled - single peer only'); } } // Sync state management function manageSyncState() { const shouldSync = link.isStartStopSyncEnabled; const numPeers = link.getSessionInfo().numPeers; if (shouldSync && numPeers === 0) { console.log('Sync enabled but no peers - waiting for connections'); } else if (shouldSync && numPeers > 0) { console.log(`Sync active with ${numPeers} peers`); } else { console.log('Sync disabled - independent transport control'); } } // Toggle sync functionality function toggleStartStopSync() { const currentState = link.isStartStopSyncEnabled; link.isStartStopSyncEnabled = !currentState; console.log(`Start/Stop sync ${link.isStartStopSyncEnabled ? 'enabled' : 'disabled'}`); return link.isStartStopSyncEnabled; } // Sync validation function validateSyncSettings() { const syncEnabled = link.isStartStopSyncEnabled; const linkEnabled = link.enabled; if (syncEnabled && !linkEnabled) { console.warn('Sync enabled but Link disabled - sync will not work'); return false; } return true; } ``` --- ## Methods ### Core Methods #### `startUpdate(interval, callback?)` Starts the update loop for receiving Link synchronization data. **Parameters**: - `interval` (number): Update interval in milliseconds - `callback` (function, optional): Callback function for updates **Callback Parameters**: - `beat` (number): Current beat position - `phase` (number): Current phase - `bpm` (number): Current tempo - `playState` (boolean): Current play state **Implementation Examples**: ```javascript // Basic update with callback link.startUpdate(60, (beat, phase, bpm, playState) => { console.log(`Beat: ${beat}, Phase: ${phase}, BPM: ${bpm}`); }); // High-frequency updates for real-time applications link.startUpdate(16, (beat, phase, bpm, playState) => { updateVisualization(beat, phase); updateTempoDisplay(bpm); updateTransportState(playState); }); // Low-frequency updates for monitoring link.startUpdate(1000, (beat, phase, bpm, playState) => { logSessionState(beat, phase, bpm, playState); updateStatusDisplay(); }); // Update without callback for manual polling link.startUpdate(60); // Dynamic update frequency based on application state function setUpdateFrequency(isActive) { if (isActive) { link.startUpdate(16); // High frequency when active } else { link.startUpdate(500); // Low frequency when idle } } // Update with error handling function startUpdateWithErrorHandling(interval, callback) { try { link.startUpdate(interval, (beat, phase, bpm, playState) => { try { callback(beat, phase, bpm, playState); } catch (error) { console.error('Update callback error:', error); } }); } catch (error) { console.error('Failed to start updates:', error); } } // Conditional updates based on play state function startConditionalUpdates() { link.startUpdate(60, (beat, phase, bpm, playState) => { if (playState) { // High-frequency updates when playing updateRealTimeElements(beat, phase, bpm); } else { // Low-frequency updates when stopped updateIdleElements(beat, phase, bpm); } }); } ``` #### `stopUpdate()` Stops the update loop. **Implementation Examples**: ```javascript // Basic stop link.stopUpdate(); // Stop with confirmation function stopUpdatesSafely() { try { link.stopUpdate(); console.log('Updates stopped successfully'); return true; } catch (error) { console.error('Failed to stop updates:', error); return false; } } // Conditional stop function stopUpdatesIfIdle() { const sessionInfo = link.getSessionInfo(); if (sessionInfo.numPeers === 0 && !sessionInfo.isPlaying) { link.stopUpdate(); console.log('Updates stopped - no active session'); } } // Stop with cleanup function stopUpdatesWithCleanup() { link.stopUpdate(); // Clean up resources clearInterval(updateTimer); resetVisualization(); console.log('Updates stopped and resources cleaned up'); } // Stop and restart with new frequency function restartUpdates(newInterval) { link.stopUpdate(); setTimeout(() => { link.startUpdate(newInterval, updateCallback); console.log(`Updates restarted with ${newInterval}ms interval`); }, 100); } // Stop updates on application shutdown function shutdownGracefully() { link.stopUpdate(); link.enabled = false; console.log('Link shutdown complete'); } ``` --- ### Advanced Timeline Methods (Link 3.1.3) #### `getTimeAtBeat(beat, quantum)` Calculates the precise time at which a specific beat occurs. **Parameters**: - `beat` (number): Target beat position - `quantum` (number): Musical quantum for calculation **Returns**: `number` - Time in milliseconds **Implementation Examples**: ```javascript // Basic beat timing const timeAtBeat = link.getTimeAtBeat(4.0, 4.0); console.log(`Beat 4 occurs at ${timeAtBeat}ms`); // Calculate multiple beat timings function calculateBeatTimings() { const beat1 = link.getTimeAtBeat(1, 4); const beat2 = link.getTimeAtBeat(2, 4); const beat4 = link.getTimeAtBeat(4, 4); console.log(`Beat intervals: ${beat2 - beat1}ms, ${beat4 - beat2}ms`); return { beat1, beat2, beat4 }; } // Schedule events at specific beats function scheduleEventAtBeat(beat, eventFunction) { const eventTime = link.getTimeAtBeat(beat, link.quantum); const currentTime = Date.now(); const delay = eventTime - currentTime; if (delay > 0) { setTimeout(eventFunction, delay); console.log(`Event scheduled for beat ${beat} in ${delay}ms`); } else { console.log(`Beat ${beat} already passed`); } } // Beat-to-time conversion for different time signatures function getTimeInTimeSignature(beat, timeSignature) { const [numerator, denominator] = timeSignature.split('/'); const quantum = parseInt(numerator); return link.getTimeAtBeat(beat, quantum); } // Validate beat timing calculations function validateBeatTiming(beat, quantum) { try { const time = link.getTimeAtBeat(beat, quantum); if (time > 0 && Number.isFinite(time)) { return { valid: true, time }; } else { return { valid: false, error: 'Invalid time result' }; } } catch (error) { return { valid: false, error: error.message }; } } // Calculate tempo from beat intervals function calculateTempoFromBeats(beat1, beat2) { const time1 = link.getTimeAtBeat(beat1, link.quantum); const time2 = link.getTimeAtBeat(beat2, link.quantum); const timeDiff = time2 - time1; const beatDiff = beat2 - beat1; if (timeDiff > 0 && beatDiff > 0) { const msPerBeat = timeDiff / beatDiff; const bpm = (60 * 1000) / msPerBeat; return bpm; } return null; } ``` #### `requestBeatAtStartPlayingTime(beat, quantum)` Requests that a specific beat be mapped to the transport start time. **Parameters**: - `beat` (number): Target beat position - `quantum` (number): Musical quantum for mapping **Implementation Examples**: ```javascript // Basic beat mapping link.requestBeatAtStartPlayingTime(0, 4); // Map beat 0 to transport start // Map different beats for different sections function setupSongStructure() { link.requestBeatAtStartPlayingTime(0, 4); // Intro starts at beat 0 link.requestBeatAtStartPlayingTime(16, 4); // Verse starts at beat 16 link.requestBeatAtStartPlayingTime(32, 4); // Chorus starts at beat 32 link.requestBeatAtStartPlayingTime(48, 4); // Bridge starts at beat 48 } // Dynamic beat mapping based on user input function mapBeatToUserPreference(userBeat) { const currentQuantum = link.quantum; link.requestBeatAtStartPlayingTime(userBeat, currentQuantum); console.log(`Beat ${userBeat} mapped to transport start`); } // Beat mapping with validation function requestBeatMapping(beat, quantum) { if (beat >= 0 && quantum > 0) { link.requestBeatAtStartPlayingTime(beat, quantum); console.log(`Beat ${beat} mapped with quantum ${quantum}`); return true; } else { console.error('Invalid beat or quantum values'); return false; } } // Conditional beat mapping function mapBeatIfPlaying(beat, quantum) { if (link.isPlaying) { link.requestBeatAtStartPlayingTime(beat, quantum); console.log(`Beat ${beat} mapped while playing`); } else { console.log('Transport not playing - beat mapping deferred'); } } // Beat mapping for different time signatures function mapBeatInTimeSignature(beat, timeSignature) { const [numerator] = timeSignature.split('/'); const quantum = parseInt(numerator); link.requestBeatAtStartPlayingTime(beat, quantum); console.log(`Beat ${beat} mapped in ${timeSignature} time`); } ``` #### `setIsPlayingAndRequestBeatAtTime(isPlaying, timeMs, beat, quantum)` Combines setting the play state and requesting a beat mapping at a specific time. **Parameters**: - `isPlaying` (boolean): Whether transport should be playing - `timeMs` (number): Target time in milliseconds - `beat` (number): Target beat position - `quantum` (number): Musical quantum for mapping **Implementation Examples**: ```javascript // Basic combined play state and beat mapping const futureTime = Date.now() + 2000; link.setIsPlayingAndRequestBeatAtTime(true, futureTime, 8, 4); // Scheduled transport start with beat mapping function scheduleTransportStart(delayMs, startBeat) { const startTime = Date.now() + delayMs; link.setIsPlayingAndRequestBeatAtTime(true, startTime, startBeat, link.quantum); console.log(`Transport scheduled to start at beat ${startBeat} in ${delayMs}ms`); } // Transport stop with beat mapping function scheduleTransportStop(delayMs, stopBeat) { const stopTime = Date.now() + delayMs; link.setIsPlayingAndRequestBeatAtTime(false, stopTime, stopBeat, link.quantum); console.log(`Transport scheduled to stop at beat ${stopBeat} in ${delayMs}ms`); } // Conditional transport control function controlTransportConditionally(shouldPlay, targetBeat) { const currentTime = Date.now(); const quantum = link.quantum; if (shouldPlay && !link.isPlaying) { link.setIsPlayingAndRequestBeatAtTime(true, currentTime, targetBeat, quantum); console.log(`Transport started at beat ${targetBeat}`); } else if (!shouldPlay && link.isPlaying) { link.setIsPlayingAndRequestBeatAtTime(false, currentTime, targetBeat, quantum); console.log(`Transport stopped at beat ${targetBeat}`); } } // Beat-synchronized transport control function syncTransportToBeat(beat, shouldPlay) { const targetTime = link.getTimeAtBeat(beat, link.quantum); link.setIsPlayingAndRequestBeatAtTime(shouldPlay, targetTime, beat, link.quantum); const action = shouldPlay ? 'start' : 'stop'; console.log(`Transport will ${action} at beat ${beat}`); } // Transport control with validation function setTransportStateSafely(isPlaying, timeMs, beat, quantum) { if (timeMs > Date.now() && beat >= 0 && quantum > 0) { link.setIsPlayingAndRequestBeatAtTime(isPlaying, timeMs, beat, quantum); console.log(`Transport state set: playing=${isPlaying}, beat=${beat}`); return true; } else { console.error('Invalid parameters for transport control'); return false; } } ``` #### `getTimeForIsPlaying()` Gets the current time information for transport start/stop operations. **Returns**: `number` - Time in milliseconds **Implementation Examples**: ```javascript // Basic transport timing const transportTime = link.getTimeForIsPlaying(); console.log(`Transport timing: ${transportTime}ms`); // Monitor transport timing changes function monitorTransportTiming() { const currentTime = link.getTimeForIsPlaying(); const timeDiff = currentTime - Date.now(); if (timeDiff > 0) { console.log(`Transport will change in ${timeDiff}ms`); } else { console.log('Transport timing is current'); } } // Validate transport timing function validateTransportTiming() { const transportTime = link.getTimeForIsPlaying(); const currentTime = Date.now(); if (transportTime >= currentTime) { console.log('Transport timing is valid'); return true; } else { console.warn('Transport timing appears to be in the past'); return false; } } // Calculate time until transport change function getTimeUntilTransportChange() { const transportTime = link.getTimeForIsPlaying(); const currentTime = Date.now(); const timeUntilChange = transportTime - currentTime; return Math.max(0, timeUntilChange); } // Transport timing synchronization function syncToTransportTiming() { const transportTime = link.getTimeForIsPlaying(); const currentTime = Date.now(); const syncDelay = transportTime - currentTime; if (syncDelay > 0) { setTimeout(() => { console.log('Synchronized with transport timing'); }, syncDelay); } } // Transport timing for scheduling function scheduleWithTransportTiming(callback) { const transportTime = link.getTimeForIsPlaying(); const currentTime = Date.now(); const delay = transportTime - currentTime; if (delay > 0) { setTimeout(callback, delay); console.log(`Callback scheduled for transport timing in ${delay}ms`); } else { callback(); // Execute immediately if timing has passed } } ``` --- ### Session Information Methods (Link 3.1.3) #### `getSessionId()` Gets a unique identifier for the current Link session. **Returns**: `string` - Session identifier **Implementation Examples**: ```javascript // Basic session ID retrieval const sessionId = link.getSessionId(); console.log(`Session ID: ${sessionId}`); // Session ID validation function validateSessionId() { const sessionId = link.getSessionId(); if (sessionId && sessionId.length > 0) { console.log('Session ID is valid:', sessionId); return true; } else { console.error('Invalid session ID'); return false; } } // Session ID monitoring function monitorSessionChanges() { let lastSessionId = link.getSessionId(); setInterval(() => { const currentSessionId = link.getSessionId(); if (currentSessionId !== lastSessionId) { console.log('Session changed:', { from: lastSessionId, to: currentSessionId }); lastSessionId = currentSessionId; } }, 1000); } // Session ID for logging function logSessionActivity(activity) { const sessionId = link.getSessionId(); const timestamp = new Date().toISOString(); console.log(`[${timestamp}] Session ${sessionId}: ${activity}`); } // Session ID comparison function isSameSession(sessionId1, sessionId2) { return sessionId1 === sessionId2; } // Session ID storage function storeSessionInfo() { const sessionId = link.getSessionId(); const sessionData = { id: sessionId, timestamp: Date.now(), userAgent: navigator.userAgent }; localStorage.setItem('linkSession', JSON.stringify(sessionData)); console.log('Session info stored:', sessionData); } ``` #### `getSessionInfo()` Gets comprehensive information about the current Link session. **Returns**: `object` with the following properties: - `numPeers` (number): Number of connected peers - `isEnabled` (boolean): Whether Link is enabled - `isStartStopSyncEnabled` (boolean): Whether start/stop sync is enabled - `currentTempo` (number): Current session tempo - `currentBeat` (number): Current beat position - `currentPhase` (number): Current phase - `quantum` (number): Current quantum - `isPlaying` (boolean): Current play state **Implementation Examples**: ```javascript // Basic session info retrieval const sessionInfo = link.getSessionInfo(); console.log(`Active session with ${sessionInfo.numPeers} peers`); console.log(`Tempo: ${sessionInfo.currentTempo} BPM`); // Comprehensive session monitoring function monitorSessionState() { const sessionInfo = link.getSessionInfo(); console.log('=== Session Status ==='); console.log(`Peers: ${sessionInfo.numPeers}`); console.log(`Enabled: ${sessionInfo.isEnabled}`); console.log(`Sync: ${sessionInfo.isStartStopSyncEnabled}`); console.log(`Tempo: ${sessionInfo.currentTempo} BPM`); console.log(`Beat: ${sessionInfo.currentBeat.toFixed(2)}`); console.log(`Phase: ${sessionInfo.currentPhase.toFixed(2)}`); console.log(`Quantum: ${sessionInfo.quantum}`); console.log(`Playing: ${sessionInfo.isPlaying}`); } // Session health check function checkSessionHealth() { const sessionInfo = link.getSessionInfo(); const health = { hasPeers: sessionInfo.numPeers > 0, isEnabled: sessionInfo.isEnabled, isSynced: sessionInfo.isStartStopSyncEnabled, hasValidTempo: sessionInfo.currentTempo > 0, isActive: sessionInfo.isPlaying }; const healthScore = Object.values(health).filter(Boolean).length; console.log(`Session health: ${healthScore}/5`); return health; } // Peer connection monitoring function monitorPeerConnections() { let lastPeerCount = 0; setInterval(() => { const sessionInfo = link.getSessionInfo(); const currentPeerCount = sessionInfo.numPeers; if (currentPeerCount > lastPeerCount) { console.log(`New peer connected! Total: ${currentPeerCount}`); } else if (currentPeerCount < lastPeerCount) { console.log(`Peer disconnected. Total: ${currentPeerCount}`); } lastPeerCount = currentPeerCount; }, 1000); } // Session state validation function validateSessionState() { const sessionInfo = link.getSessionInfo(); const errors = []; if (!sessionInfo.isEnabled) errors.push('Link is disabled'); if (sessionInfo.currentTempo <= 0) errors.push('Invalid tempo'); if (sessionInfo.quantum <= 0) errors.push('Invalid quantum'); if (errors.length > 0) { console.error('Session validation errors:', errors); return false; } return true; } // Session data export function exportSessionData() { const sessionInfo = link.getSessionInfo(); const exportData = { timestamp: Date.now(), sessionId: link.getSessionId(), ...sessionInfo }; const dataStr = JSON.stringify(exportData, null, 2); const dataBlob = new Blob([dataStr], { type: 'application/json' }); const url = URL.createObjectURL(dataBlob); const link = document.createElement('a'); link.href = url; link.download = `link-session-${Date.now()}.json`; link.click(); console.log('Session data exported'); } ``` --- ### Platform and System Methods (Link 3.1.3) #### `getPlatformInfo()` Gets information about the current platform and Link capabilities. **Returns**: `object` with the following properties: - `platform` (string): Platform identifier ('macos', 'linux', 'windows') - `linkVersion` (string): Link library version - `hasCustomClock` (boolean): Whether custom clock is available - `supportsAdvancedTimeline` (boolean): Whether advanced timeline features are supported - `supportsSessionManagement` (boolean): Whether session management features are supported **Implementation Examples**: ```javascript // Basic platform info retrieval const platformInfo = link.getPlatformInfo(); console.log(`Running on ${platformInfo.platform} with Link ${platformInfo.linkVersion}`); // Platform-specific optimizations function applyPlatformOptimizations() { const platformInfo = link.getPlatformInfo(); switch (platformInfo.platform) { case 'macos': console.log('Applying macOS-specific optimizations'); enableMetalAcceleration(); break; case 'linux': console.log('Applying Linux-specific optimizations'); enableALSAOptimizations(); break; case 'windows': console.log('Applying Windows-specific optimizations'); enableWASAPIOptimizations(); break; default: console.log('Unknown platform, using generic optimizations'); } } // Feature capability checking function checkFeatureSupport() { const platformInfo = link.getPlatformInfo(); const capabilities = { advancedTimeline: platformInfo.supportsAdvancedTimeline, sessionManagement: platformInfo.supportsSessionManagement, customClock: platformInfo.hasCustomClock }; console.log('Feature capabilities:', capabilities); if (capabilities.advancedTimeline) { console.log('Advanced timeline features available'); } return capabilities; } // Version compatibility check function checkVersionCompatibility() { const platformInfo = link.getPlatformInfo(); const linkVersion = platformInfo.linkVersion; if (linkVersion.startsWith('3.1.')) { console.log('Link 3.1.x features fully supported'); return 'full'; } else if (linkVersion.startsWith('3.0.')) { console.log('Link 3.0.x features supported'); return 'partial'; } else { console.log('Legacy Link version - limited features'); return 'limited'; } } // Platform detection for UI function updatePlatformUI() { const platformInfo = link.getPlatformInfo(); // Update UI elements based on platform document.body.className = `platform-${platformInfo.platform}`; // Show/hide platform-specific features if (platformInfo.supportsAdvancedTimeline) { showAdvancedTimelineControls(); } if (platformInfo.supportsSessionManagement) { showSessionManagementPanel(); } } // Platform info logging function logPlatformInfo() { const platformInfo = link.getPlatformInfo(); console.log('=== Platform Information ==='); console.log(`Platform: ${platformInfo.platform}`); console.log(`Link Version: ${platformInfo.linkVersion}`); console.log(`Custom Clock: ${platformInfo.hasCustomClock}`); console.log(`Advanced Timeline: ${platformInfo.supportsAdvancedTimeline}`); console.log(`Session Management: ${platformInfo.supportsSessionManagement}`); } ``` #### `getNetworkStats()` Gets network performance and connection statistics. **Returns**: `object` with the following properties: - `numPeers` (number): Number of connected peers - `isEnabled` (boolean): Whether Link is enabled - `sessionActive` (boolean): Whether session is active - `networkLatency` (number): Network latency in milliseconds - `connectionQuality` (string): Connection quality rating ('excellent', 'good', 'fair', 'poor', 'unknown') **Implementation Examples**: ```javascript // Basic network stats retrieval const networkStats = link.getNetworkStats(); if (networkStats.sessionActive) { console.log(`Connected with ${networkStats.numPeers} peers`); console.log(`Connection quality: ${networkStats.connectionQuality}`); } // Network health monitoring function monitorNetworkHealth() { const networkStats = link.getNetworkStats(); const health = { hasPeers: networkStats.numPeers > 0, isActive: networkStats.sessionActive, lowLatency: networkStats.networkLatency < 50, goodQuality: ['excellent', 'good'].includes(networkStats.connectionQuality) }; const healthScore = Object.values(health).filter(Boolean).length; console.log(`Network health: ${healthScore}/4`); return health; } // Connection quality monitoring function monitorConnectionQuality() { let lastQuality = ''; setInterval(() => { const networkStats = link.getNetworkStats(); const currentQuality = networkStats.connectionQuality; if (currentQuality !== lastQuality) { console.log(`Connection quality changed: ${lastQuality} ${currentQuality}`); if (currentQuality === 'poor') { console.warn('Poor connection quality detected'); suggestNetworkOptimization(); } lastQuality = currentQuality; } }, 5000); } // Latency monitoring function monitorNetworkLatency() { const networkStats = link.getNetworkStats(); const latency = networkStats.networkLatency; if (latency > 100) { console.warn(`High network latency: ${latency}ms`); return 'high'; } else if (latency > 50) { console.log(`Moderate network latency: ${latency}ms`); return 'moderate'; } else { console.log(`Low network latency: ${latency}ms`); return 'low'; } } // Network optimization suggestions function suggestNetworkOptimization() { const networkStats = link.getNetworkStats(); if (networkStats.connectionQuality === 'poor') { console.log('Network optimization suggestions:'); console.log('- Check firewall settings'); console.log('- Verify multicast is enabled'); console.log('- Reduce network congestion'); console.log('- Check for interference'); } } // Network performance logging function logNetworkPerformance() { const networkStats = link.getNetworkStats(); console.log('=== Network Performance ==='); console.log(`Peers: ${networkStats.numPeers}`); console.log(`Enabled: ${networkStats.isEnabled}`); console.log(`Session Active: ${networkStats.sessionActive}`); console.log(`Latency: ${networkStats.networkLatency}ms`); console.log(`Quality: ${networkStats.connectionQuality}`); } // Auto-adjust based on network conditions function autoAdjustForNetwork() { const networkStats = link.getNetworkStats(); if (networkStats.connectionQuality === 'poor') { // Reduce update frequency for poor connections link.startUpdate(1000); // 1 second updates console.log('Reduced update frequency due to poor network'); } else if (networkStats.connectionQuality === 'excellent') { // Increase update frequency for excellent connections link.startUpdate(16); // 60fps updates console.log('Increased update frequency due to excellent network'); } } ``` --- ## Events The module supports event-based updates through the `startUpdate` callback mechanism. ### Update Event **Event**: Update callback **Frequency**: Based on `startUpdate` interval **Data**: Beat, phase, BPM, and play state **Example**: ```javascript link.startUpdate(60, (beat, phase, bpm, playState) => { // Handle real-time updates updateVisualization(beat, phase); updateTempoDisplay(bpm); updateTransportState(playState); }); ``` --- ## Advanced Features ### Timeline Management The advanced timeline features provide precise control over musical timing and synchronization: - **Beat-to-Time Mapping**: Convert musical beats to precise timestamps - **Quantized Beat Requests**: Map specific beats to transport events - **Transport Synchronization**: Coordinate play state with beat positioning - **Tempo-Aware Calculations**: All timing calculations respect current tempo ### Session Management Comprehensive session monitoring and control: - **Peer Discovery**: Monitor connected applications - **Session State**: Track tempo, beat, and phase across the network - **Transport Control**: Synchronize start/stop across applications - **Session Identification**: Unique session tracking ### Platform Optimization Automatic platform detection and optimization: - **Native Performance**: Platform-specific optimizations - **Feature Detection**: Automatic capability discovery - **Version Compatibility**: Link 3.1.3 feature support - **Cross-Platform**: Consistent API across operating systems --- ## Error Handling The module includes robust error handling for various scenarios: ### Constructor Errors ```javascript try { const link = new abletonlink(120, 4, true); } catch (error) { console.error('Failed to create Link instance:', error.message); } ``` ### Method Errors ```javascript try { const timeAtBeat = link.getTimeAtBeat(4, 4); } catch (error) { console.error('Timeline calculation failed:', error.message); } ``` ### Edge Cases The module gracefully handles edge cases: - **Negative values**: Handled with appropriate defaults - **Zero quantum**: Gracefully processed - **Extreme values**: Supported within reasonable limits - **Invalid parameters**: Clear error messages --- ## Performance Considerations ### Update Frequency - **High frequency** (16-60ms): Real-time applications, audio processing - **Medium frequency** (100-500ms): UI updates, visualization - **Low frequency** (1000ms+): Background monitoring, logging ### Memory Management - **Native module**: Efficient C++ implementation - **Minimal overhead**: Lightweight JavaScript wrapper - **Garbage collection**: Automatic cleanup of temporary objects ### Network Performance - **Peer discovery**: Efficient multicast-based discovery - **Data synchronization**: Optimized for real-time updates - **Connection quality**: Automatic quality monitoring --- ## Examples ### Basic Synchronization ```javascript const abletonlink = require('@mirage-xr/node-abletonlink'); const link = new abletonlink(120, 4, true); link.startUpdate(60, (beat, phase, bpm) => { console.log(`Beat: ${beat.toFixed(2)}, Phase: ${phase.toFixed(2)}, BPM: ${bpm.toFixed(1)}`); }); ``` ### Advanced Timeline Control ```javascript const link = new abletonlink(128, 4, true); // Calculate precise timing for musical events const beat1 = link.getTimeAtBeat(1, 4); const beat2 = link.getTimeAtBeat(2, 4); const beat4 = link.getTimeAtBeat(4, 4); console.log(`Beat intervals: ${beat2 - beat1}ms, ${beat4 - beat2}ms`); // Request specific beat mapping link.requestBeatAtStartPlayingTime(0, 4); ``` ### Session Monitoring ```javascript const link = new abletonlink(120, 4, true); // Monitor session state setInterval(() => { const sessionInfo = link.getSessionInfo(); const networkStats = link.getNetworkStats(); console.log(`Peers: ${sessionInfo.numPeers}, Tempo: ${sessionInfo.currentTempo}`); console.log(`Connection: ${networkStats.connectionQuality}`); }, 1000); ``` ### Real-time Visualization ```javascript const link = new abletonlink(120, 4, true); link.startUpdate(16, (beat, phase, bpm) => { // Update visual elements updateBeatIndicator(beat); updatePhaseMeter(phase); updateTempoDisplay(bpm); // Trigger visual effects if (Math.floor(beat) !== Math.floor(link.beat)) { triggerBeatAnimation(); } }); ``` --- ## TypeScript Support The module includes complete TypeScript definitions in `index.d.ts`: ```typescript import abletonlink from '@mirage-xr/node-abletonlink'; const link: abletonlink = new abletonlink(120, 4, true); // Full type safety and IntelliSense support const sessionInfo = link.getSessionInfo(); console.log(sessionInfo.numPeers); // TypeScript knows this is a number ``` ### Type Definitions All methods and properties include proper TypeScript types: - **Constructor**: `new abletonlink(bpm: number, quantum: number, enable: boolean)` - **Properties**: Properly typed getters and setters - **Methods**: Full parameter and return type definitions - **Objects**: Structured types for complex return values --- ## Compatibility **Node.js**: 18.13.0+ (recommended) **Platforms**: macOS 10.14+, Linux (glibc distros), Windows 10+ --- ## Troubleshooting - **Build fails** verify `node-gyp` prerequisites and compiler toolchain - **No peers found** check firewall/multicast; Ableton Link uses mDNS/Bonjour - **High latency** inspect `getNetworkStats()`; reduce network congestion - **Stale peers** ensure your app prunes peers that have not been seen within a timeout --- ## Credits & Attribution This project stands on the shoulders of the original work: - **Original Project**: [2bbb/node-abletonlink](https://github.com/2bbb/node-abletonlink) - **Author**: ISHII 2bit (bufferRenaiss co., ltd.) ishii[at]buffer-renaiss.com **Special Thanks (from the original project):** - [Hlöðver Sigurðsson (hlolli)](https://github.com/hlolli) #3 - [Yuichi Yogo (yuichkun)](https://github.com/yuichkun) #10 - [Jakob Miland](https://github.com/saebekassebil) #11 - [Alessandro Oniarti](https://github.com/Onni97) #11 - [Théis Bazin](https://github.com/tbazin) #12, #15 - [Jeffrey Kog](https://github.com/jeffreykog) #16 **Dependencies (original lineage):** - [ableton/link](https://github.com/ableton/link) - [chriskohlhoff/asio](https://github.com/chriskohlhoff/asio) - [philsquared/Catch](https://github.com/philsquared/Catch) **This fork**: Modernization, TypeScript, docs, and Link 3.1.3 features by **Veacks**. Please report issues and PRs here: https://github.com/veacks/node-abletonlink. > Naming note: To avoid confusion with the original `abletonlink` package, this fork is published as **`node-abletonlink`**. --- ## License MIT License. Per MIT requirements, this fork **retains the original copyright and license notice** and adds its own. ``` Copyright (c) 2016–2019 ISHII 2bit [bufferRenaiss co., ltd.] Copyright (c) 2025 Veacks Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` --- ## Donations If this software helps you and you’re feeling generous, consider supporting the **original author**: - **Bitcoin**: `17AbtW73aydfYH3epP8T3UDmmDCcXSGcaf` (You can also support this fork by opening issues, pull requests, or starring the repo thank you!)