is it possible to record and playback fader and knob movements at all? i guess if this functionality is not native, maybe it’s possible to code it as a plugin?
i want to be able to record movements, and play them back. so i guess a kind of automation.
“press record, move a fader, let go, and it will play the movement back” - that sort of thing
AFAIK this is possible with lemur using a “roboknob” plugin…?
That’s very unlikely to be implemented (the app’s main focus is direct user interaction), but I guess it could be possible to code something like this with a custom module.
It should be doable, i think of a javascript object, where every time a fader is touched, it is stored in a object. On a practical view there are several things to consider - and it has to be generic so other peeps could use it.
When it ends up in a node module it could be used in other project. In my big project(keko kemper control) i have implemented a dynamic object, that is written to hdd and loaded on startup... -> I am a beginner, so there might be better solutions.
I fiddled a little with the idea, here is a basic implementation example (as a custom module). It's far from complete and is not going to be implemented as a core feature, but it could help you nonetheless:
class Automation {
constructor() {
// osc message store
this.events = []
// record state
this.recordStartTime = 0
this.recording = false
// play state
this.playStartTime = 0
this.playing = false
this.playbackCursor = 0
// looping state
this.looping = false
// playback process
this.interval = 10
this.intervalCallback = null
}
startRecording() {
// clear osc message store
this.events = []
// enable recording
this.recording = true
// save timestamp
this.recordStartTime = Date.now()
}
stopRecording() {
// stop recording
this.recording = false
}
startPlaying() {
// start or restart playback
if (this.playing) this.stopPlaying()
this.playing = true
// save timestamp
this.playStartTime = Date.now()
// start playback process
this.intervalCallback = setInterval(this.processEvents.bind(this), this.interval)
}
stopPlaying() {
// stop playback
this.playing = false
// reset playback cursor
this.playbackCursor = 0
// stop playback process
clearInterval(this.intervalCallback)
}
setLooping(v) {
// set looping state
this.looping = !!v
}
addEvent(data) {
if (!this.recording) return
// store osc message with relative timestamp
this.events.push({
timestamp: Date.now() - this.recordStartTime,
data: data
})
}
processEvents() {
// playback process
// compute elapsed time
var playtime = Date.now() - this.playStartTime
while (this.playing) {
// retrieve event at playback cursor
var event = this.events[this.playbackCursor]
if (!event) {
this.stopPlaying()
return
}
// retrieve osc message data and timestamp
var {data, timestamp} = event
if (playtime >= timestamp) {
// if elapsed time is greater than relative timestamp
// send osc message at playback cursor
send(data.host, data.port, data.address, ...data.args)
// and send it back to the clients to update the gui
receive(data.host, data.port, data.address, ...data.args)
// increment playback cursor
this.playbackCursor += 1
if (this.playbackCursor == this.events.length) {
// if last event was sent, stop playback or loop
if (this.looping) {
this.startPlaying()
break
} else {
this.stopPlaying()
break
}
}
} else {
// else wait for next process call (in this.interval milliseconds)
break
}
}
}
}
var automation = new Automation()
module.exports = {
oscOutFilter: (data)=>{
var {host, port, address, args} = data
if (address === '/record') {
if (args[0].value) automation.startRecording()
else automation.stopRecording()
return
} else if (address === '/play') {
if (args[0].value) automation.startPlaying()
else automation.stopPlaying()
return
} else if (address === '/looping') {
automation.setLooping(args[0].value)
return
} else {
automation.addEvent(data)
}
return data
}
}