Implement fader policies and pad sound state extraction for Ableton export

This commit is contained in:
2026-07-14 11:24:34 +02:00
parent f75bffa879
commit 0d7c574677
2 changed files with 188 additions and 0 deletions

View File

@@ -0,0 +1,126 @@
import { FaderParam } from '../../../types/types';
import type { AblTrack } from '../../transformers/ableton';
export type FaderRenderStrategy = 'target' | 'note' | 'unsupported';
export type FaderTargetScope = 'track' | 'pad';
export type FaderSupportStatus = 'mapped' | 'research' | 'unsupported';
export type FaderConversionContext = {
track: AblTrack;
groupMixer?: boolean;
valueOffset?: number;
};
export type FaderPolicy = {
defaultValue: number;
renderStrategy: FaderRenderStrategy;
targetScope: FaderTargetScope;
status: FaderSupportStatus;
toLive: (value: number, context: FaderConversionContext) => number;
};
const identity = (value: number) => value;
export const FADER_POLICIES = {
[FaderParam.LVL]: {
defaultValue: 1,
renderStrategy: 'target',
targetScope: 'track',
status: 'mapped',
toLive: (value, context) =>
context.groupMixer ? value : context.track.volume * value,
},
[FaderParam.PTC]: {
defaultValue: 0.5,
renderStrategy: 'target',
targetScope: 'pad',
status: 'research',
toLive: (value, context) => (context.valueOffset ?? 0) + (value - 0.5) * 10,
},
[FaderParam.TIM]: {
defaultValue: 0.5,
renderStrategy: 'unsupported',
targetScope: 'pad',
status: 'unsupported',
toLive: identity,
},
[FaderParam.LPF]: {
defaultValue: 1,
renderStrategy: 'target',
targetScope: 'track',
status: 'research',
toLive: (value) => 20 + value * 0.9 * 115,
},
[FaderParam.HPF]: {
defaultValue: 0,
renderStrategy: 'target',
targetScope: 'track',
status: 'research',
toLive: (value) => 20 + value * 0.7 * 115,
},
[FaderParam.FX]: {
defaultValue: 0,
renderStrategy: 'target',
targetScope: 'track',
status: 'mapped',
toLive: identity,
},
[FaderParam.ATK]: {
defaultValue: 0,
renderStrategy: 'unsupported',
targetScope: 'pad',
status: 'research',
toLive: identity,
},
[FaderParam.REL]: {
defaultValue: 0,
renderStrategy: 'unsupported',
targetScope: 'pad',
status: 'research',
toLive: identity,
},
[FaderParam.PAN]: {
defaultValue: 0.5,
renderStrategy: 'target',
targetScope: 'track',
status: 'mapped',
toLive: (value) => (value - 0.5) * 2,
},
[FaderParam.TUNE]: {
defaultValue: 0.5,
renderStrategy: 'target',
targetScope: 'pad',
status: 'research',
toLive: (value) => Math.max(-0.5, Math.min(0.5, value - 0.5)) * 100,
},
[FaderParam.VEL]: {
defaultValue: 0,
renderStrategy: 'note',
targetScope: 'track',
status: 'research',
toLive: identity,
},
[FaderParam.MOD]: {
defaultValue: 0,
renderStrategy: 'target',
targetScope: 'pad',
status: 'research',
toLive: (value) => value / 20,
},
} satisfies Record<FaderParam, FaderPolicy>;
export function getFaderPolicy(parameter: FaderParam) {
return FADER_POLICIES[parameter];
}
export function getFaderDefaultValue(parameter: FaderParam) {
return getFaderPolicy(parameter).defaultValue;
}
export function convertGroupFaderValue(
parameter: FaderParam,
value: number,
context: FaderConversionContext,
) {
return getFaderPolicy(parameter).toLive(value, context);
}

View File

@@ -0,0 +1,62 @@
import type { AblTrack } from '../../transformers/ableton';
import { koEnvRangeToSeconds } from './utils';
export type PadSoundState = {
amp: number;
pitch: number;
playMode: AblTrack['playMode'];
pan: number;
trimStart: number;
trimEnd: number;
envelope: {
attack: number;
release: number;
};
time: {
mode: AblTrack['timeStretch'];
bpm: number;
bars: number;
};
rootNote: number;
soundLength: number;
};
export type PadEnvelopeState = {
classicAttackMs: number;
classicReleaseMs: number;
oneShotFadeInMs: number;
oneShotFadeOutMs: number;
};
export function getPadSoundState(track: AblTrack): PadSoundState {
return {
amp: track.volume,
pitch: track.pitch,
playMode: track.playMode,
pan: track.pan,
trimStart: track.trimLeft,
trimEnd: track.trimRight,
envelope: {
attack: track.attack,
release: track.release,
},
time: {
mode: track.timeStretch,
bpm: track.timeStretchBpm,
bars: track.timeStretchBars,
},
rootNote: track.rootNote,
soundLength: track.soundLength,
};
}
export function getPadEnvelopeState(sound: PadSoundState): PadEnvelopeState {
return {
classicAttackMs:
koEnvRangeToSeconds(sound.envelope.attack, sound.soundLength) * 1000,
classicReleaseMs:
koEnvRangeToSeconds(sound.envelope.release, sound.soundLength) * 1000,
oneShotFadeInMs: 0,
oneShotFadeOutMs: 5,
};
}