diff --git a/README.md b/README.md index 150849b..11854fe 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,9 @@ Check the project blog here: https://ko-fi.com/phones24 - MIDI (MIDI file + archived samples) - Ableton Live: - Native Live 10 and Live 11+ project schemas - - Supports all sampler features: envelope, trimming points, stretching, playback modes + - Supports sampler trimming points, stretching and playback modes - Export as arrangements or session clips - - Static fader parameters and recorded fader automation, except TIM + - Static fader parameters and recorded fader automation, except TIM, ATK and REL - FX send/return - Track grouping: 4 groups, same as on the device - Automatically builds one Drum Rack per group for single-pitch pads while preserving chromatic and expressive pads as individual MIDI tracks diff --git a/src/routes/faq/Faq.tsx b/src/routes/faq/Faq.tsx index b056e5c..a141fbf 100644 --- a/src/routes/faq/Faq.tsx +++ b/src/routes/faq/Faq.tsx @@ -21,10 +21,11 @@ function Faq() { Several reasons. The main one is that the EP devices uses a custom FX processor that is hard to recreate in a DAW one to one. Recorded fader automation is exported to Ableton where Live has a matching parameter. TIM automation is omitted because Live has no direct - equivalent for the EP's real-time stretch control. Sample stretching algorithms are - different as well. Ableton exports automatically place suitable pads in Drum Racks so - their choke groups work, but choking does not cross from a rack to a separate melodic - track. + equivalent for the EP's real-time stretch control. ATK and REL are omitted because the + EP's envelope curves and mode-specific behavior do not map linearly to Live's envelopes. + Sample stretching algorithms are different as well. Ableton exports automatically place + suitable pads in Drum Racks so their choke groups work, but choking does not cross from a + rack to a separate melodic track.

Initially this project was intended to export notes and samples so you could continue diff --git a/src/routes/home/Export/ExportProjectDialog.tsx b/src/routes/home/Export/ExportProjectDialog.tsx index 0a87485..a6898d4 100644 --- a/src/routes/home/Export/ExportProjectDialog.tsx +++ b/src/routes/home/Export/ExportProjectDialog.tsx @@ -25,7 +25,7 @@ function sanitizeFileName(value: string): string { const NOTES: Record = { ableton: `Every selected EP scene is exported to Session View. When the project has an authored EP Song, the complete saved Song is additionally laid out in Arrangement View even if Session scene selection is limited. A one-position Song containing only Scene 1 is indistinguishable from the device default and is therefore treated as no authored Song, leaving Arrangement View empty. -When samples are included, suitable single-pitch pads are automatically organized into Drum Racks, while chromatic or expressive pads remain on separate MIDI tracks. Recorded fader automation is exported for LVL, PTC, LPF, HPF, FX, ATK, REL, PAN, TUNE and MOD; VEL is baked into note velocities. Choke groups work between pads in the same Drum Rack. TIM automation is not exported, while the EP effects and stretching algorithms remain approximations.`, +When samples are included, suitable single-pitch pads are automatically organized into Drum Racks, while chromatic or expressive pads remain on separate MIDI tracks. Recorded fader automation is exported for LVL, PTC, LPF, HPF, FX, PAN, TUNE and MOD; VEL is baked into note velocities. Choke groups work between pads in the same Drum Rack. TIM, ATK and REL automation are not exported, while the EP effects and stretching algorithms remain approximations.`, dawproject: `Unfortunately, the DAWproject format does not currently support the "Sampler" instrument, so you will need to manually assign the samples in your DAW.`, midi: `The simplest format supported by any DAW. But you have to assign the samples manually.`, reaper: `Only basic sampler features are supported`, diff --git a/tests/ableton-hybrid.test.ts b/tests/ableton-hybrid.test.ts index 0cafaa1..a63b0dd 100644 --- a/tests/ableton-hybrid.test.ts +++ b/tests/ableton-hybrid.test.ts @@ -207,6 +207,12 @@ test('serializes nested Live 10 Simplers without artificial empty Session clips' try { const { buildProject } = await server.ssrLoadModule('/src/lib/exporters/ableton/builders.ts'); const data = project(); + data.pads.a.forEach((item) => { + item.attack = 200; + item.release = 20; + }); + data.settings.groupFaderParams.a[FaderParam.ATK] = 0.66; + data.settings.groupFaderParams.a[FaderParam.REL] = 0.87; delete data.groupPatterns[0].notes[1]; delete data.groupPatterns[1].notes[0]; data.groupPatterns[1].notes[1] = [note(67, 48), note(68, 96)]; @@ -229,6 +235,29 @@ test('serializes nested Live 10 Simplers without artificial empty Session clips' assert.ok(simplerTags.length > 0); assert.ok(simplerTags.every((tag) => /\sId="\d+"/.test(tag))); + const simplerBlocks = [...xml.matchAll(//g)].map( + (match) => match[0], + ); + assert.ok( + simplerBlocks.every((simpler) => + /[\s\S]*?/.test(simpler), + ), + ); + assert.ok( + simplerBlocks.every((simpler) => + /[\s\S]*?/.test(simpler), + ), + ); + assert.ok( + simplerBlocks.every((simpler) => + /[\s\S]*?[\s\S]*?/.test(simpler), + ), + ); + assert.ok( + simplerBlocks.every((simpler) => + /[\s\S]*?[\s\S]*?/.test(simpler), + ), + ); assert.ok(rackTags.length > 0); assert.ok(rackTags.every((rack) => //.test(rack))); assert.ok(rackTags.every((rack) => //.test(rack)));