module.exports = function (context) { var activatedAt = Date.now() var unloadState = { lastSuppressId: null, lastSuppressAt: 0 } var midiPort = MidiPort(context, function (port, lastPort) { // turn off on switch lastPort && lastPort.write(turnOffAll) if (port) { activatedAt = Date.now() port.write(turnOffAll) var values = [] for (var i = 0; i < 8; i++) { if (i >= 2) { values.push(i, light(1, 1)) } else { values.push(i, light(2, 2)) } values.push(i + 8, light(1, 0)) values.push(i + 16, light(0, 1)) } port.write(setLed.concat(values, 247)) } }) var obs = ObservStruct({ port: midiPort }) // grab the midi for the current port obs.grabInput = function () { midiPort.grab() } obs.context = context var project = context.project var onTrigger = AnyTrigger(project.items) var knobs = { 3: Observ(0), 4: Observ(0), 5: Observ(0), 6: Observ(0), 7: Observ(0), 8: Observ(0) } var params = [] Object.keys(knobs).forEach(function (key) { var id = 'Launch Control XL > ' + key params.push(id) context.paramLookup.put(id, MidiParam(context, id, knobs[key])) }) var paramState = [] watchKnobs(midiPort.stream, mappings.row1.concat(mappings.row2, mappings.row3), function (id, data) { var state = paramState[id] = paramState[id] || {} var index = Math.floor(id / 8) if (index === 0) { if (id === 0) { project.tempo.set(scaleInterpolate(project.tempo() - 60, data, state) + 60) } else if (id === 1) { project.swing.set(scaleInterpolate(project.swing() * 128, data, state) / 128) } else { knobs[id + 1].set(scaleInterpolate(knobs[id + 1](), data, state)) } } else { var item = project.items.get(id % 8) if (isSetup(item)) { var setup = item.node var param = index === 1 ? setup.overrideLowPass : setup.overrideHighPass var currentValue = getValue(param()) param.set(scaleInterpolate(currentValue * 128, data, state) / 128) } } }) var sliderState = [] watchKnobs(midiPort.stream, mappings.sliders, function (id, data) { var state = sliderState[id] = sliderState[id] || {} var item = project.items.get(id) if (isSetup(item)) { var setup = item.node var volume = setup.overrideVolume var currentPosition = Math.pow(volume(), 1 / Math.E) * 108 var newPosition = scaleInterpolate(currentPosition, data, state) volume.set(Math.pow(newPosition / 108, Math.E)) } }, 108) var selectedId = 0 var buttonBase = computed([project.selected, project.items], function (selected, items) { var result = [] for (var i = 0; i < 8; i++) { var item = project.items.get(i) if (item) { if (item.path === selected) { selectedId = i result.push(light(2, 3)) } else { result.push(light(0, 1)) } } else { result.push(0) } } return result }) var stopButtonBase = Observ([light(1,0),light(1,0),light(1,0),light(1,0),light(1,0),light(1,0),light(1,0),light(1,0)]) var buttonFlash = FlashArray() onTrigger(function (index) { if (index === selectedId) { buttonFlash.flash(index, light(3, 3), 40) } else { buttonFlash.flash(index, light(0, 3), 40) } }) var buttons = ObservMidi(midiPort.stream, mappings.trackFocus, ArrayStack([ buttonBase, buttonFlash ])) buttons(function (values) { var result = null values.forEach(function (val, i) { if (val) { result = i } }) if (result != null) { var item = project.items.get(result) if (item) { project.selected.set(item.path) } } }) var stopAllButtons = ObservMidi(midiPort.stream, mappings.trackControl, ArrayStack([ stopButtonBase, buttonFlash ])) stopAllButtons(function (values) { var result = null values.forEach(function (val, i) { if (val) { result = i } }) if (result != null) { var item = project.items.get(result) if (item && item.node) { if (unloadState.lastSuppressId === result && unloadState.lastSuppressAt > Date.now() - 500) { unloadState.lastSuppressAt = 0 item.close() } else { unloadState.lastSuppressId = result unloadState.lastSuppressAt = Date.now() suppressNode(item.node, true) } } } }) // CONTROL BUTTONS: var controlButtons = LightStack(midiPort.stream, { mode: mappings.device }) controlButtons.mode(function (value) { if (value || Date.now() - activatedAt > 300) { midiPort.override.set(false) var activeItem = findItemByPath(project.items, project.selected()) if (isSetup(activeItem)) { activeItem.node.grabInput() } else { midiPort.previous() } } }) controlButtons.mode.light(light(2, 2)) obs.grabInput = function () { midiPort.grab() } obs.destroy = function () { midiPort.destroy() params.forEach(function (id) { context.paramLookup.delete(id) }) } return obs // scoped function suppressNode (node, flatten) { if (node && node.controllers) { node.controllers.forEach(function (controller) { if (controller.looper) { var release = controller.looper.transform(function (grid) { grid.data = [] return grid }) if (flatten) { controller.looper.flatten() } else { return release } } }) } } }
module.exports = function (context) { var midiPort = MidiPort(context, function (port, lastPort) { // turn off on switch lastPort && lastPort.write(turnOffAll) port && port.write(turnOffAll) }) var obs = ObservStruct({ port: midiPort, chunkIds: Property([]) }) var params = [] for (var i = 0; i < 8; i++) { params[i] = [ Param(context, 0), Param(context, 0), Param(context, 0) ] } var paramReleases = [] obs.chunkIds(refreshParamLinks) context.chunkLookup(refreshParamLinks) function refreshParamLinks () { while (paramReleases.length) { paramReleases.pop()() } obs.chunkIds().forEach(function (id, i) { var chunk = context.chunkLookup.get(id) if (chunk && chunk.overrideParams) { paramReleases.push(chunk.overrideParams(params[i])) } }) } // grab the midi for the current port obs.grabInput = function () { midiPort.grab() } obs.context = context var setup = context.setup var paramState = [] watchKnobs(midiPort.stream, mappings.row1.concat(mappings.row2, mappings.row3), function (id, data) { var param = params[id % 8][Math.floor(id / 8)] var chunk = setup.context.chunkLookup.get(obs.chunkIds()[id % 8]) if (chunk && chunk.overrideParams && chunk.params) { param.set(scaleInterpolate(param() * 128, data, paramState[id] = paramState[id] || {}) / 128) } }) var sliderState = [] watchKnobs(midiPort.stream, mappings.sliders, function (id, data) { var chunk = setup.chunks.lookup.get(obs.chunkIds()[id]) if (chunk) { var volume = chunk.overrideVolume || chunk.node && chunk.node.overrideVolume if (volume) { var currentPosition = Math.pow(volume(), 1 / Math.E) * 108 var newPosition = scaleInterpolate(currentPosition, data, sliderState[id] = sliderState[id] || {}) volume.set(Math.pow(newPosition / 108, Math.E)) } } }, 108) var pressed = PressedChunks(setup.controllers) var knobLights = computed([obs.chunkIds, setup.context.chunkLookup, pressed, setup.selectedChunkId], function (chunkIds, lookup, pressed, selected) { var result = [] for (var i = 0; i < 8; i++) { var chunk = setup.context.chunkLookup.get(chunkIds[i]) if (chunk && chunk.params) { var onValue = pressed[chunkIds[i]] ? light(0, 2) : selected === chunkIds[i] ? light(1, 1) : light(2, 0) result[0 + i] = chunk.params()[0] ? onValue : 0 result[8 + i] = chunk.params()[1] ? onValue : 0 result[16 + i] = chunk.params()[2] ? onValue : 0 } else { result[0 + i] = result[8 + i] = result[16 + i] = 0 } } return result }) setLights(knobLights, midiPort.stream) var buttonBase = computed([setup.selectedChunkId, obs.chunkIds, pressed], function (selected, chunkIds, pressed) { var result = [] for (var i = 0; i < 8; i++) { var chunkId = chunkIds[i] if (chunkId) { if (chunkId === selected) { result.push(light(2, 3)) } else if (pressed[chunkId]) { result.push(light(0, 1)) } else { result.push(light(1, 0)) } } else { result.push(0) } } return result }) var buttonFlash = FlashArray() setup.onTrigger(function (event) { if (event.id) { var chunkId = event.id.split('/')[0] var index = obs.chunkIds().indexOf(chunkId) if (event.event === 'start') { if (chunkId === setup.selectedChunkId()) { buttonFlash.flash(index, light(3, 3), 40) } else { buttonFlash.flash(index, light(3, 0), 40) } } } }) var buttons = ObservMidi(midiPort.stream, mappings.trackFocus, ArrayStack([ buttonBase, buttonFlash ])) buttons(function (values) { var result = null values.forEach(function (val, i) { if (val) { result = i } }) if (result != null) { var id = obs.chunkIds()[result] if (id) { setup.selectedChunkId.set(id) setup.context.actions.scrollToSelectedChunk() } } }) // CONTROL BUTTONS: var controlButtons = LightStack(midiPort.stream, { mode: mappings.device }) controlButtons.mode(function (value) { if (value) { context.project.globalControllers.forEach(function (controller) { if (controller && controller.port && controller.port() === obs.port() && controller.grabInput) { controller.grabInput() controller.port.override.set(true) } }) } }) obs.destroy = function () { midiPort.destroy() } return obs }
module.exports = function (context) { var midiPort = MidiPort(context, function (port, lastPort) { // turn off on switch lastPort && lastPort.write(turnOffAll) port && port.write(turnOffAll) }) var obs = ObservStruct({ port: midiPort, chunkIds: Property([]) }) var releases = [] var params = [] var paramLoopers = [] var recordingIndexes = Dict() var playingIndexes = Dict() var recordStarts = {} for (var i = 0; i < 8; i++) { params[i] = [ Value(0), Value(0), Value(0) ] paramLoopers[i] = [ ParamLooper(context, params[i][0]), ParamLooper(context, params[i][1]), ParamLooper(context, params[i][2]) ] recordingIndexes.put(i, computed(paramLoopers[i].map(x => x.recording), (...args) => args.some(Boolean))) playingIndexes.put(i, computed(paramLoopers[i].map(x => x.playing), (...args) => args.some(Boolean))) } var bindingReleases = new Map() var bindings = MutantMap(obs.chunkIds, (id, invalidateOn) => { var item = context.chunkLookup.get(id) var index = obs.chunkIds().indexOf(id) invalidateOn(computed([context.chunkLookup, obs.chunkIds], (_, chunkIds) => { // rebind when chunk is changed return item !== context.chunkLookup.get(id) || chunkIds.indexOf(id) !== index })) if (item) { bindingReleases.set(item, item.overrideParams(paramLoopers[index])) } return item }, { onRemove: function (item) { if (bindingReleases.has(item)) { bindingReleases.get(item)() bindingReleases.delete(item) } } }) releases.push(watch(bindings)) // grab the midi for the current port obs.grabInput = function () { midiPort.grab() } obs.context = context var setup = context.setup watchKnobs(midiPort.stream, mappings.row1.concat(mappings.row2, mappings.row3), function (id, data) { var param = params[id % 8][Math.floor(id / 8)] var chunk = setup.context.chunkLookup.get(obs.chunkIds()[id % 8]) if (chunk && chunk.overrideParams && chunk.params) { param.set(data / 128) } }) var sliderState = [] watchKnobs(midiPort.stream, mappings.sliders, function (id, data) { var chunk = setup.context.chunkLookup.get(obs.chunkIds()[id]) if (chunk && chunk.overrideVolume) { var currentPosition = Math.pow(chunk.overrideVolume(), 1 / Math.E) * 108 var newPosition = scaleInterpolate(currentPosition, data, sliderState[id] = sliderState[id] || {}) chunk.overrideVolume.set(Math.pow(newPosition / 108, Math.E)) } }, 108) var pressed = computed(MutantMap(setup.controllers, function (controller) { return controller && controller.currentlyPressed }), function (items) { return items.reduce(function (result, pressed) { if (pressed) { pressed.map(x => x && x.split('/')[0]).reduce(addIfUnique, result) } return result }, []) }) var knobLights = computed([obs.chunkIds, setup.context.chunkLookup, pressed, setup.selectedChunkId], function (chunkIds, lookup, pressed, selected) { var result = [] if (setup.context) { for (var i = 0; i < 8; i++) { var chunk = setup.context.chunkLookup.get(chunkIds[i]) if (chunk && chunk.params) { var onValue = pressed.includes(chunkIds[i]) ? light(0, 2) : selected === chunkIds[i] ? light(1, 1) : light(2, 0) result[0 + i] = chunk.params()[0] ? onValue : 0 result[8 + i] = chunk.params()[1] ? onValue : 0 result[16 + i] = chunk.params()[2] ? onValue : 0 } else { result[0 + i] = result[8 + i] = result[16 + i] = 0 } } } return result }) setLights(knobLights, midiPort.stream) var buttonBase = computed([setup.selectedChunkId, obs.chunkIds, pressed], function (selected, chunkIds, pressed) { var result = [] for (var i = 0; i < 8; i++) { var chunkId = chunkIds[i] if (chunkId) { if (chunkId === selected) { result.push(light(2, 3)) } else if (pressed.includes(chunkId)) { result.push(light(0, 1)) } else { result.push(light(1, 0)) } } else { result.push(0) } } return result }) var buttonFlash = FlashArray() setup.onTrigger(function (event) { if (event.id) { var chunkId = event.id.split('/')[0] var index = obs.chunkIds().indexOf(chunkId) if (event.event === 'start') { if (chunkId === setup.selectedChunkId()) { buttonFlash.flash(index, light(3, 3), 40) } else { buttonFlash.flash(index, light(3, 0), 40) } } } }) var buttons = ObservMidi(midiPort.stream, mappings.trackFocus, ArrayStack([ buttonBase, buttonFlash ])) buttons(function (values) { var result = null values.forEach(function (val, i) { if (val) { result = i } }) if (result != null) { var id = obs.chunkIds()[result] if (id) { setup.selectedChunkId.set(id) setup.context.actions.scrollToSelectedChunk() } } }) var recordButtonBase = computed([recordingIndexes, playingIndexes], function (recordingIndexes, playingIndexes) { var result = [] for (var i = 0; i < 8; i++) { if (recordingIndexes[i]) { result[i] = light(3, 0) } else if (playingIndexes[i]) { result[i] = light(0, 3) } else { result[i] = 0 } } return result }) var recordButtons = ObservMidi(midiPort.stream, mappings.trackControl, recordButtonBase) recordButtons(function (values) { values.forEach(function (val, i) { paramLoopers[i].forEach(looper => looper.recording.set(!!val)) if (val) { recordStarts[i] = Date.now() } else if (Date.now() - recordStarts[i] < 400) { paramLoopers[i].forEach(looper => looper.set(0)) } }) }) // CONTROL BUTTONS: var controlButtons = LightStack(midiPort.stream, { mode: mappings.device }) controlButtons.mode(function (value) { if (value) { context.project.globalControllers.forEach(function (controller) { if (controller && controller.port && controller.port() === obs.port() && controller.grabInput) { controller.grabInput() controller.port.override.set(true) } }) } }) obs.destroy = function () { while (releases.length) { releases.pop()() } for (var fn of bindingReleases.values()) { fn() } bindingReleases.clear() midiPort.destroy() paramLoopers.forEach(items => items.forEach(param => param.destroy())) } return obs }