diff --git a/src/lib/exporters/ableton/builders.ts b/src/lib/exporters/ableton/builders.ts index b84a1b3..752b001 100644 --- a/src/lib/exporters/ableton/builders.ts +++ b/src/lib/exporters/ableton/builders.ts @@ -343,10 +343,16 @@ async function buildMidiClip( return midiClip; } -async function buildSimplerDevice(koTrack: AblTrack, abletonVersion: AbletonVersion) { +async function buildSimplerDevice( + koTrack: AblTrack, + abletonVersion: AbletonVersion, + automatedParameters: ReadonlySet = new Set(), +) { const simplerTemplate = await loadTemplate('simpler', abletonVersion); const device = structuredClone(simplerTemplate.OriginalSimpler); - const playbackState = getSimplerPlaybackState(koTrack.playMode); + const padSound = getPadSoundState(koTrack); + const padEnvelope = getPadEnvelopeState(padSound); + const playbackState = getSimplerPlaybackState(padSound.playMode); const samplePart = device.Player.MultiSampleMap.SampleParts.MultiSamplePart; const loopModulators = device.Player.LoopModulators; const simplerFilter = device.Filter.Slot.Value.SimplerFilter; @@ -367,11 +373,13 @@ async function buildSimplerDevice(koTrack: AblTrack, abletonVersion: AbletonVers simplerFilter.ModByLfo.Manual['@Value'] = 0; device.Lfo.IsOn.Manual['@Value'] = 'false'; device.Pitch.PitchLfoAmount.Manual['@Value'] = 0; - device.VolumeAndPan.OneShotEnvelope.FadeInTime.Manual['@Value'] = SIMPLER_FADE_IN_MS; - device.VolumeAndPan.OneShotEnvelope.FadeOutTime.Manual['@Value'] = SIMPLER_FADE_OUT_MS; + device.VolumeAndPan.OneShotEnvelope.FadeInTime.Manual['@Value'] = + padEnvelope.oneShotFadeInMs; + device.VolumeAndPan.OneShotEnvelope.FadeOutTime.Manual['@Value'] = + padEnvelope.oneShotFadeOutMs; device.VolumeAndPan.OneShotEnvelope.SustainMode.Manual['@Value'] = 0; - device.VolumeAndPan.Envelope.AttackTime.Manual['@Value'] = SIMPLER_ATTACK_MS; - device.VolumeAndPan.Envelope.ReleaseTime.Manual['@Value'] = SIMPLER_RELEASE_MS; + device.VolumeAndPan.Envelope.AttackTime.Manual['@Value'] = padEnvelope.classicAttackMs; + device.VolumeAndPan.Envelope.ReleaseTime.Manual['@Value'] = padEnvelope.classicReleaseMs; device.Globals.PlaybackMode['@Value'] = playbackState.playbackMode; device.Globals.NumVoices['@Value'] = playbackState.voices; device.Globals.RetriggerMode['@Value'] = playbackState.retrigger; @@ -389,17 +397,17 @@ async function buildSimplerDevice(koTrack: AblTrack, abletonVersion: AbletonVers sampleRef.LastModDate['@Value'] = 0; } if (sampleRef.DefaultDuration) { - sampleRef.DefaultDuration['@Value'] = Math.round(koTrack.soundLength * koTrack.sampleRate); + sampleRef.DefaultDuration['@Value'] = Math.round(padSound.soundLength * koTrack.sampleRate); } if (sampleRef.DefaultSampleRate) { sampleRef.DefaultSampleRate['@Value'] = koTrack.sampleRate; } samplePart.Name['@Value'] = koTrack.name; - samplePart.RootKey['@Value'] = koTrack.rootNote; - samplePart.SampleStart['@Value'] = koTrack.trimLeft; - samplePart.SampleEnd['@Value'] = koTrack.trimRight; - samplePart.SustainLoop.Start['@Value'] = koTrack.trimLeft; - samplePart.SustainLoop.End['@Value'] = koTrack.trimRight; + samplePart.RootKey['@Value'] = padSound.rootNote; + samplePart.SampleStart['@Value'] = padSound.trimStart; + samplePart.SampleEnd['@Value'] = padSound.trimEnd; + samplePart.SustainLoop.Start['@Value'] = padSound.trimStart; + samplePart.SustainLoop.End['@Value'] = padSound.trimEnd; samplePart.SustainLoop.Mode['@Value'] = 0; samplePart.SustainLoop.Crossfade['@Value'] = 0; samplePart.ReleaseLoop.Start['@Value'] = 0; @@ -414,41 +422,54 @@ async function buildSimplerDevice(koTrack: AblTrack, abletonVersion: AbletonVers // setting velocity scaling to 20% to match KO behavior device.VolumeAndPan.VolumeVelScale.Manual['@Value'] = 0.2; - device.VolumeAndPan.Panorama.Manual['@Value'] = koTrack.pan; + device.VolumeAndPan.Panorama.Manual['@Value'] = padSound.pan; - let pitch = koTrack.pitch || 0; + let pitch = padSound.pitch || 0; if (koTrack.samplePitch) { pitch += koTrack.samplePitch; } // adding PITCH fader param if defined if (koTrack.faderParams[FaderParam.PTC] !== -1) { - const normalized = (koTrack.faderParams[FaderParam.PTC] - 0.5) * 2; // from 0-1 to -1 to +1 - pitch += Math.round(normalized * 5); + pitch += Math.round( + convertGroupFaderValue(FaderParam.PTC, koTrack.faderParams[FaderParam.PTC], { + track: koTrack, + }), + ); } if (koTrack.faderParams[FaderParam.TUNE] !== -1) { - device.Pitch.TransposeFine.Manual['@Value'] = - Math.max(-0.5, Math.min(0.5, koTrack.faderParams[FaderParam.TUNE] - 0.5)) * 100; + device.Pitch.TransposeFine.Manual['@Value'] = convertGroupFaderValue( + FaderParam.TUNE, + koTrack.faderParams[FaderParam.TUNE], + { track: koTrack }, + ); } device.Pitch.TransposeKey.Manual['@Value'] = pitch; // adding vibrato from MOD fader param if defined - if (koTrack.faderParams[FaderParam.MOD] !== -1) { + if ( + koTrack.faderParams[FaderParam.MOD] !== -1 || + automatedParameters.has(FaderParam.MOD) + ) { device.Lfo.IsOn.Manual['@Value'] = 'true'; if (!device.Lfo.Slot.Value.SimplerLfo && abletonVersion === '10') { const simplerLfoTemplate = await loadTemplate('simplerLfo', abletonVersion); device.Lfo.Slot.Value.SimplerLfo = structuredClone(simplerLfoTemplate.SimplerLfo); } // KO's MOD wheel on 100% is roughly equal to 5% LFO pitch modulation - device.Pitch.PitchLfoAmount.Manual['@Value'] = koTrack.faderParams[FaderParam.MOD] / 20; + device.Pitch.PitchLfoAmount.Manual['@Value'] = convertGroupFaderValue( + FaderParam.MOD, + Math.max(0, koTrack.faderParams[FaderParam.MOD]), + { track: koTrack }, + ); device.Lfo.Slot.Value.SimplerLfo.RateType.Manual['@Value'] = 1; // tempo synced 1/16 } // TODO: add bandpass filter - if (koTrack.timeStretch === 'bars') { + if (padSound.time.mode === 'bars') { device.Player.MultiSampleMap.SampleParts.MultiSamplePart.SampleWarpProperties.IsWarped[ '@Value' ] = 'true'; @@ -464,18 +485,18 @@ async function buildSimplerDevice(koTrack: AblTrack, abletonVersion: AbletonVers }, { '@Id': 1, - '@SecTime': koTrack.soundLength, + '@SecTime': padSound.soundLength, '@BeatTime': - koTrack.timeStretchBars * + padSound.time.bars * koTrack.timeSignature.numerator * (4 / koTrack.timeSignature.denominator), // convert bars to beats }, ]; } - if (koTrack.timeStretch === 'bpm') { + if (padSound.time.mode === 'bpm') { // calculating beat length using koTrack.timeStretchBpm - const beatTime = koTrack.soundLength / (60 / koTrack.timeStretchBpm); + const beatTime = padSound.soundLength / (60 / padSound.time.bpm); device.Player.MultiSampleMap.SampleParts.MultiSamplePart.SampleWarpProperties.IsWarped[ '@Value' @@ -492,13 +513,13 @@ async function buildSimplerDevice(koTrack: AblTrack, abletonVersion: AbletonVers }, { '@Id': 1, - '@SecTime': koTrack.soundLength, + '@SecTime': padSound.soundLength, '@BeatTime': beatTime, }, ]; } - if (koTrack.timeStretch === 'rev') { + if (padSound.time.mode === 'rev') { device.Player.Reverse.Manual['@Value'] = 'true'; } @@ -510,6 +531,7 @@ async function buildSimplerDevice(koTrack: AblTrack, abletonVersion: AbletonVers async function buildFaderFilterDevice( parameter: FaderParam.LPF | FaderParam.HPF, value: number, + track: AblTrack, abletonVersion: AbletonVersion, ) { const filterTemplate = await loadTemplate('effectFilter', abletonVersion); @@ -519,7 +541,7 @@ async function buildFaderFilterDevice( filter.FilterType.Manual['@Value'] = highPass ? 1 : 0; filter.LegacyFilterType.Manual['@Value'] = highPass ? 1 : 0; filter.Slope.Manual['@Value'] = highPass ? 'false' : 'true'; - filter.Cutoff.Manual['@Value'] = highPass ? 20 + value * 0.7 * 115 : 20 + value * 0.9 * 115; + filter.Cutoff.Manual['@Value'] = convertGroupFaderValue(parameter, value, { track }); return { AutoFilter: filter }; }