Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ableton-link): add ableton link support to orca #247

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ All commands have a shorthand equivalent to their first two characters, for exam
- `midi:1;2` Set Midi output device to `#1`, and input device to `#2`.
- `udp:1234;5678` Set UDP output port to `1234`, and input port to `5678`.
- `osc:1234` Set OSC output port to `1234`.
- `link` Enables/Disables Ableton Link

## Base36 Table

Expand Down
27 changes: 27 additions & 0 deletions desktop/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"electron-packager": "^14.2.1"
},
"dependencies": {
"abletonlink-addon": "^0.2.9",
"node-osc": "^4.1.8"
},
"standard": {
Expand Down
41 changes: 41 additions & 0 deletions desktop/sources/scripts/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
/* global Clock */
/* global Theme */

const AbletonLink = require("abletonlink-addon")

function Client () {
this.version = 176
this.library = library
Expand All @@ -26,6 +28,29 @@ function Client () {
this.commander = new Commander(this)
this.clock = new Clock(this)

// Ableton Link
this.link = new AbletonLink();

this.link.setTempoCallback((newTempo) => {
newTempo = this.link.getTempo(true)
if (this.clock.isLinkEnabled && this.clock.speed.value != newTempo) {
this.clock.setSpeed(newTempo, newTempo, true)
this.clock.setFrame(0)
mcartagenah marked this conversation as resolved.
Show resolved Hide resolved
this.update()
};
});

this.link.setStartStopCallback((startStopState) => {
console.log("startstop: " + startStopState);
if (startStopState && this.clock.isPaused) {
this.clock.play(false, true, true)
mcartagenah marked this conversation as resolved.
Show resolved Hide resolved
} else if (!startStopState && !this.clock.isPaused) {
this.clock.stop(false, true, false)
this.clock.setFrame(0)
this.update()
}
});

// Settings
this.scale = window.devicePixelRatio
this.grid = { w: 8, h: 8 }
Expand Down Expand Up @@ -117,6 +142,7 @@ function Client () {
this.acels.set('Midi', 'Next Input Device', 'CmdOrCtrl+,', () => { this.clock.setFrame(0); this.io.midi.selectNextInput() })
this.acels.set('Midi', 'Next Output Device', 'CmdOrCtrl+.', () => { this.clock.setFrame(0); this.io.midi.selectNextOutput() })
this.acels.set('Midi', 'Refresh Devices', 'CmdOrCtrl+Shift+M', () => { this.io.midi.refresh() })
this.acels.set('Midi', 'Toggle Ableton Link', 'CmdOrCtrl+Shift+L', () => { this.toggleLink() })

this.acels.set('Communication', 'Choose OSC Port', 'alt+O', () => { this.commander.start('osc:') })
this.acels.set('Communication', 'Choose UDP Port', 'alt+U', () => { this.commander.start('udp:') })
Expand Down Expand Up @@ -160,6 +186,21 @@ function Client () {
this.update()
}

this.toggleLink = () => {
if (this.clock.isLinkEnabled) {
this.link.disable()
this.link.disableStartStopSync()
} else {
this.link.enable()
this.link.enableStartStopSync()
this.clock.setSpeed(this.link.getTempo(true), this.link.getTempo(true), true)
if (!this.link.isPlaying()) {
this.clock.stop(false, true)
}
}
this.clock.isLinkEnabled = !this.clock.isLinkEnabled
mcartagenah marked this conversation as resolved.
Show resolved Hide resolved
}

this.update = () => {
if (document.hidden === true) { return }
this.clear()
Expand Down
41 changes: 37 additions & 4 deletions desktop/sources/scripts/clock.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ function Clock (client) {
this.isPaused = true
this.timer = null
this.isPuppet = false
this.isLinkEnabled = false

this.speed = { value: 120, target: 120 }

Expand All @@ -34,6 +35,15 @@ function Clock (client) {
if (value) { this.speed.value = clamp(value, 60, 300) }
if (target) { this.speed.target = clamp(target, 60, 300) }
if (setTimer === true) { this.setTimer(this.speed.value) }
if (this.isLinkEnabled) { this.setFrame(0) }
}

this.setSpeedLink = (value) => {
client.link.setTempo(value)
if (!client.link.isPlaying()) {
this.setFrame(0)
client.update()
}
}

this.modSpeed = function (mod = 0, animate = false) {
Expand All @@ -56,8 +66,14 @@ function Clock (client) {
client.update()
}

this.play = function (msg = false, midiStart = false) {
console.log('Clock', 'Play', msg, midiStart)
this.play = function (msg = false, midiStart = false, linkStart = false) {
console.log('Clock', 'Play', msg, midiStart, linkStart)
if (this.isLinkEnabled && this.isPaused && !linkStart) {
this.isPaused = false
this.setSpeed(this.speed.target, this.speed.target, true)
client.link.play()
return
}
if (this.isPaused === false && !midiStart) { return }
this.isPaused = false
if (this.isPuppet === true) {
Expand All @@ -73,8 +89,17 @@ function Clock (client) {
}
}

this.stop = function (msg = false) {
this.stop = function (msg = false, linkStop = false) {
console.log('Clock', 'Stop')
console.log(this.isLinkEnabled, this.isPaused, linkStop)
if (this.isLinkEnabled && !this.isPaused && !linkStop) {
this.isPaused = true
this.clearTimer()
client.link.stop()
client.io.midi.allNotesOff()
client.io.midi.silence()
return
}
mcartagenah marked this conversation as resolved.
Show resolved Hide resolved
if (this.isPaused === true) { return }
this.isPaused = true
if (this.isPuppet === true) {
Expand Down Expand Up @@ -159,10 +184,18 @@ function Clock (client) {

// UI

this.getUIMessage = function (offset) {
if (this.isLinkEnabled) {
return `link${this.speed.value}${offset}`
} else {
return this.isPuppet === true ? 'midi' : `${this.speed.value}${offset}`
}
}

this.toString = function () {
const diff = this.speed.target - this.speed.value
const _offset = Math.abs(diff) > 5 ? (diff > 0 ? `+${diff}` : diff) : ''
const _message = this.isPuppet === true ? 'midi' : `${this.speed.value}${_offset}`
const _message = this.getUIMessage(_offset)
const _beat = diff === 0 && client.orca.f % 4 === 0 ? '*' : ''
return `${_message}${_beat}`
}
Expand Down
19 changes: 17 additions & 2 deletions desktop/sources/scripts/commander.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,24 @@ function Commander (client) {
play: (p) => { client.clock.play() },
stop: (p) => { client.clock.stop() },
run: (p) => { client.run() },
link: (p) => { client.toggleLink() },
// Time
apm: (p) => { client.clock.setSpeed(null, p.int) },
bpm: (p) => { client.clock.setSpeed(p.int, p.int, true) },
apm: (p) => {
if (client.clock.isLinkEnabled) {
client.clock.setSpeed(null, p.int)
client.clock.setSpeedLink(p.int)
} else {
client.clock.setSpeed(null, p.int)
}
},
bpm: (p) => {
if (client.clock.isLinkEnabled) {
client.clock.setSpeed(p.int, p.int, true)
client.clock.setSpeedLink(p.int)
} else {
client.clock.setSpeed(p.int, p.int, true)
}
},
frame: (p) => { client.clock.setFrame(p.int) },
rewind: (p) => { client.clock.setFrame(client.orca.f - p.int) },
skip: (p) => { client.clock.setFrame(client.orca.f + p.int) },
Expand Down
36 changes: 19 additions & 17 deletions desktop/sources/scripts/core/io/midi.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,23 +122,25 @@ function Midi (client) {
}

this.receive = function (msg) {
switch (msg.data[0]) {
// Clock
case 0xF8:
client.clock.tap()
break
case 0xFA:
console.log('MIDI', 'Start Received')
client.clock.play(false, true)
break
case 0xFB:
console.log('MIDI', 'Continue Received')
client.clock.play()
break
case 0xFC:
console.log('MIDI', 'Stop Received')
client.clock.stop()
break
if (!client.clock.isLinkEnabled) {
switch (msg.data[0]) {
// Clock
case 0xF8:
client.clock.tap()
break
case 0xFA:
console.log('MIDI', 'Start Received')
client.clock.play(false, true)
break
case 0xFB:
console.log('MIDI', 'Continue Received')
client.clock.play()
break
case 0xFC:
console.log('MIDI', 'Stop Received')
client.clock.stop()
break
}
}
}

Expand Down