Refactor Simpler device building to use playback state object

This commit is contained in:
2026-07-13 10:20:23 +02:00
parent c2b6a3e487
commit 1501513473
3 changed files with 55 additions and 25 deletions

View File

@@ -29,6 +29,7 @@ import {
filterEffectValues, filterEffectValues,
reverbEffectValues, reverbEffectValues,
} from './effects'; } from './effects';
import { getSimplerPlaybackState } from './playback';
import { ALSDrumBranch } from './types/drumBranch'; import { ALSDrumBranch } from './types/drumBranch';
import { ALSDrumRack } from './types/drumRack'; import { ALSDrumRack } from './types/drumRack';
import { ALSChorus } from './types/effectChorus'; import { ALSChorus } from './types/effectChorus';
@@ -388,7 +389,7 @@ async function buildMidiClip(
async function buildSimplerDevice(koTrack: AblTrack, abletonVersion: AbletonVersion) { async function buildSimplerDevice(koTrack: AblTrack, abletonVersion: AbletonVersion) {
const simplerTemplate = await loadTemplate<ALSSimpler>('simpler', abletonVersion); const simplerTemplate = await loadTemplate<ALSSimpler>('simpler', abletonVersion);
const device = structuredClone(simplerTemplate.OriginalSimpler); const device = structuredClone(simplerTemplate.OriginalSimpler);
const projectOneShot = koTrack.playMode === 'oneshot'; const playbackState = getSimplerPlaybackState(koTrack.playMode, koTrack.oneShotOverride);
const samplePart = device.Player.MultiSampleMap.SampleParts.MultiSamplePart; const samplePart = device.Player.MultiSampleMap.SampleParts.MultiSamplePart;
const loopModulators = device.Player.LoopModulators; const loopModulators = device.Player.LoopModulators;
const simplerFilter = device.Filter.Slot.Value.SimplerFilter; const simplerFilter = device.Filter.Slot.Value.SimplerFilter;
@@ -413,21 +414,10 @@ async function buildSimplerDevice(koTrack: AblTrack, abletonVersion: AbletonVers
device.VolumeAndPan.OneShotEnvelope.FadeInTime.Manual['@Value'] = 0; device.VolumeAndPan.OneShotEnvelope.FadeInTime.Manual['@Value'] = 0;
device.VolumeAndPan.OneShotEnvelope.FadeOutTime.Manual['@Value'] = 0; device.VolumeAndPan.OneShotEnvelope.FadeOutTime.Manual['@Value'] = 0;
device.VolumeAndPan.OneShotEnvelope.SustainMode.Manual['@Value'] = 0; device.VolumeAndPan.OneShotEnvelope.SustainMode.Manual['@Value'] = 0;
switch (koTrack.playMode) { device.Globals.PlaybackMode['@Value'] = playbackState.playbackMode;
case 'legato': if (playbackState.monophonic) {
device.Globals.PlaybackMode['@Value'] = 0;
device.Globals.NumVoices['@Value'] = 1; device.Globals.NumVoices['@Value'] = 1;
device.Globals.RetriggerMode['@Value'] = 'false'; device.Globals.RetriggerMode['@Value'] = 'false';
break;
case 'oneshot':
device.Globals.PlaybackMode['@Value'] = 1;
break;
case 'key':
device.Globals.PlaybackMode['@Value'] = 0;
break;
}
if (koTrack.oneShotOverride) {
device.Globals.PlaybackMode['@Value'] = 1;
} }
const sampleRef = device.Player.MultiSampleMap.SampleParts.MultiSamplePart.SampleRef; const sampleRef = device.Player.MultiSampleMap.SampleParts.MultiSamplePart.SampleRef;
if (abletonVersion === '10') { if (abletonVersion === '10') {
@@ -466,22 +456,22 @@ async function buildSimplerDevice(koTrack: AblTrack, abletonVersion: AbletonVers
// setting velocity scaling to 20% to match KO behavior // setting velocity scaling to 20% to match KO behavior
device.VolumeAndPan.VolumeVelScale.Manual['@Value'] = 0.2; device.VolumeAndPan.VolumeVelScale.Manual['@Value'] = 0.2;
const attackTarget = projectOneShot const attackTarget = playbackState.useOneShotEnvelope
? device.VolumeAndPan.OneShotEnvelope.FadeInTime ? device.VolumeAndPan.OneShotEnvelope.FadeInTime
: device.VolumeAndPan.Envelope.AttackTime; : device.VolumeAndPan.Envelope.AttackTime;
const releaseTarget = projectOneShot const releaseTarget = playbackState.useOneShotEnvelope
? device.VolumeAndPan.OneShotEnvelope.FadeOutTime ? device.VolumeAndPan.OneShotEnvelope.FadeOutTime
: device.VolumeAndPan.Envelope.ReleaseTime; : device.VolumeAndPan.Envelope.ReleaseTime;
attackTarget.Manual['@Value'] = simplerEnvelopeMilliseconds( attackTarget.Manual['@Value'] = simplerEnvelopeMilliseconds(
koTrack.attack, koTrack.attack,
playbackDuration, playbackDuration,
projectOneShot, playbackState.useOneShotEnvelope,
); );
releaseTarget.Manual['@Value'] = simplerEnvelopeMilliseconds( releaseTarget.Manual['@Value'] = simplerEnvelopeMilliseconds(
koTrack.release, koTrack.release,
playbackDuration, playbackDuration,
projectOneShot, playbackState.useOneShotEnvelope,
projectOneShot, playbackState.useOneShotEnvelope,
); );
device.VolumeAndPan.Panorama.Manual['@Value'] = koTrack.pan; device.VolumeAndPan.Panorama.Manual['@Value'] = koTrack.pan;
@@ -490,7 +480,7 @@ async function buildSimplerDevice(koTrack: AblTrack, abletonVersion: AbletonVers
attackTarget.Manual['@Value'] = simplerEnvelopeMilliseconds( attackTarget.Manual['@Value'] = simplerEnvelopeMilliseconds(
koTrack.faderParams[FaderParam.ATK] * 255, koTrack.faderParams[FaderParam.ATK] * 255,
playbackDuration, playbackDuration,
projectOneShot, playbackState.useOneShotEnvelope,
); );
} }
@@ -499,8 +489,8 @@ async function buildSimplerDevice(koTrack: AblTrack, abletonVersion: AbletonVers
releaseTarget.Manual['@Value'] = simplerEnvelopeMilliseconds( releaseTarget.Manual['@Value'] = simplerEnvelopeMilliseconds(
koTrack.faderParams[FaderParam.REL] * 255, koTrack.faderParams[FaderParam.REL] * 255,
playbackDuration, playbackDuration,
projectOneShot, playbackState.useOneShotEnvelope,
projectOneShot, playbackState.useOneShotEnvelope,
); );
} }

View File

@@ -5,3 +5,14 @@ export type AbletonPlaybackMode = 'oneshot' | 'key' | 'legato';
export function hasOneShotOverride(padCode: PadCode, oneShotPads: PadCode[] = []) { export function hasOneShotOverride(padCode: PadCode, oneShotPads: PadCode[] = []) {
return oneShotPads.includes(padCode); return oneShotPads.includes(padCode);
} }
export function getSimplerPlaybackState(
projectMode: AbletonPlaybackMode,
oneShotOverride: boolean,
) {
return {
playbackMode: projectMode === 'oneshot' || oneShotOverride ? 1 : 0,
monophonic: projectMode === 'legato',
useOneShotEnvelope: projectMode === 'oneshot',
};
}

View File

@@ -1,6 +1,9 @@
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import test from 'node:test'; import test from 'node:test';
import { hasOneShotOverride } from '../src/lib/exporters/ableton/playback'; import {
getSimplerPlaybackState,
hasOneShotOverride,
} from '../src/lib/exporters/ableton/playback';
import { PadCode } from '../src/types/types'; import { PadCode } from '../src/types/types';
const project7Overrides = ['b0', 'c1', 'c8', 'c9', 'd3'] as PadCode[]; const project7Overrides = ['b0', 'c1', 'c8', 'c9', 'd3'] as PadCode[];
@@ -17,3 +20,29 @@ test('applies the selective Project 7 One-Shot overrides', () => {
test('preserves project playback modes when no override is selected', () => { test('preserves project playback modes when no override is selected', () => {
assert.equal(hasOneShotOverride('b0'), false); assert.equal(hasOneShotOverride('b0'), false);
}); });
test('changes only the active playback mode for forced KEY and LEGATO pads', () => {
assert.deepEqual(getSimplerPlaybackState('key', true), {
playbackMode: 1,
monophonic: false,
useOneShotEnvelope: false,
});
assert.deepEqual(getSimplerPlaybackState('legato', true), {
playbackMode: 1,
monophonic: true,
useOneShotEnvelope: false,
});
});
test('uses One-Shot envelopes only for native EP ONE pads', () => {
assert.deepEqual(getSimplerPlaybackState('oneshot', false), {
playbackMode: 1,
monophonic: false,
useOneShotEnvelope: true,
});
assert.deepEqual(getSimplerPlaybackState('legato', false), {
playbackMode: 0,
monophonic: true,
useOneShotEnvelope: false,
});
});