Add support for MIDI controller targets and clip envelopes in Ableton export
This commit is contained in:
83
src/lib/exporters/ableton/midi.ts
Normal file
83
src/lib/exporters/ableton/midi.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import type { AblMidiEvent } from '../../transformers/ableton';
|
||||
|
||||
export type AbletonMidiControllerTargets = Record<string, { '@Id': number }>;
|
||||
|
||||
type AutomationPoint = {
|
||||
time: number;
|
||||
value: number;
|
||||
};
|
||||
|
||||
const CHANNEL_PRESSURE_TARGET = 129;
|
||||
const SENTINEL_TIME = -63072000;
|
||||
|
||||
export function decodeChannelPressure(event: AblMidiEvent) {
|
||||
if (
|
||||
event.data.length !== 4 ||
|
||||
event.data[0] !== 0xd0 ||
|
||||
event.data[1] > 127 ||
|
||||
event.data[2] !== 0xff ||
|
||||
event.data[3] !== 0
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return event.data[1];
|
||||
}
|
||||
|
||||
function floatEvents(points: AutomationPoint[]) {
|
||||
return points.map((point, index) => ({
|
||||
'@Id': index,
|
||||
'@Time': point.time,
|
||||
'@Value': point.value,
|
||||
}));
|
||||
}
|
||||
|
||||
export function buildMidiClipEnvelopes(
|
||||
events: AblMidiEvent[],
|
||||
controllerTargets: AbletonMidiControllerTargets,
|
||||
) {
|
||||
const target = controllerTargets[`ControllerTargets.${CHANNEL_PRESSURE_TARGET}`]?.['@Id'];
|
||||
if (target === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const points: AutomationPoint[] = [{ time: SENTINEL_TIME, value: 0 }];
|
||||
let previousValue = 0;
|
||||
let eventCount = 0;
|
||||
|
||||
events.forEach((event) => {
|
||||
const value = decodeChannelPressure(event);
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const time = event.position / 96;
|
||||
points.push({ time, value: previousValue });
|
||||
points.push({ time, value });
|
||||
previousValue = value;
|
||||
eventCount++;
|
||||
});
|
||||
|
||||
if (eventCount === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
'@Id': 0,
|
||||
EnvelopeTarget: { PointeeId: { '@Value': target } },
|
||||
Automation: {
|
||||
Events: { FloatEvent: floatEvents(points) },
|
||||
AutomationTransformViewState: {
|
||||
IsTransformPending: { '@Value': 'false' },
|
||||
TimeAndValueTransforms: {},
|
||||
},
|
||||
},
|
||||
LoopSlot: { Value: {} },
|
||||
ScrollerTimePreserver: {
|
||||
LeftTime: { '@Value': 0 },
|
||||
RightTime: { '@Value': 0 },
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user