diff --git a/src/routes/home/Export/ExportOptions.tsx b/src/routes/home/Export/ExportOptions.tsx index 7272974..4f7030c 100644 --- a/src/routes/home/Export/ExportOptions.tsx +++ b/src/routes/home/Export/ExportOptions.tsx @@ -5,13 +5,17 @@ import { projectIdAtom } from '~/atoms/project'; import CheckboxField from '~/components/form/CheckboxField'; import SelectField from '~/components/form/SelectField'; import useProject from '~/hooks/useProject'; -import { hasMultipleNoteVariations } from '~/lib/utils'; +import { getPadDisplayName, hasMultipleNoteVariations } from '~/lib/utils'; +import { PadCode } from '~/types/types'; +import CheckBox from '~/components/ui/CheckBox'; import { ExportFormValues } from './exportFormSchema'; function ExportOptions({ disabled = false }: { disabled?: boolean }) { - const { control } = useFormContext(); + const { control, setValue } = useFormContext(); const format = useWatch({ control, name: 'format' }); const includeArchivedSamples = useWatch({ control, name: 'includeArchivedSamples' }); + const exportAllPadsWithSamples = useWatch({ control, name: 'exportAllPadsWithSamples' }); + const abletonOneShotPads = useWatch({ control, name: 'abletonOneShotPads' }); const drumRackGroupA = useWatch({ control, name: 'drumRackGroupA' }); const drumRackGroupB = useWatch({ control, name: 'drumRackGroupB' }); const drumRackGroupC = useWatch({ control, name: 'drumRackGroupC' }); @@ -20,6 +24,48 @@ function ExportOptions({ disabled = false }: { disabled?: boolean }) { const projectId = useAtomValue(projectIdAtom); const { data: projectData } = useProject(projectId); + const playbackOverrides = useMemo(() => { + if (!projectData) { + return []; + } + + const usedPads = new Set( + projectData.scenes.flatMap((scene) => scene.patterns.map((pattern) => pattern.pad)), + ); + const result: Array<{ padCode: PadCode; title: string }> = []; + + Object.entries(projectData.pads).forEach(([group, pads]) => { + pads.forEach((pad, index) => { + const padCode = `${group}${index}` as PadCode; + if ( + pad.soundId <= 0 || + pad.playMode === 'oneshot' || + (!exportAllPadsWithSamples && !usedPads.has(padCode)) + ) { + return; + } + + const soundName = projectData.sounds.find((sound) => sound.id === pad.soundId)?.meta.name; + const mode = pad.playMode === 'key' ? 'KEY' : 'LEGATO'; + result.push({ + padCode, + title: `${getPadDisplayName(group, index)} ยท ${soundName || pad.soundId} (EP: ${mode})`, + }); + }); + }); + + return result; + }, [projectData, exportAllPadsWithSamples]); + + const setOneShotOverride = (padCode: PadCode, checked: boolean) => { + const current = abletonOneShotPads || []; + setValue( + 'abletonOneShotPads', + checked ? [...new Set([...current, padCode])] : current.filter((code) => code !== padCode), + { shouldDirty: true }, + ); + }; + const notesVariationWarnings = useMemo(() => { if (format === 'reaper') { return []; @@ -137,6 +183,25 @@ function ExportOptions({ disabled = false }: { disabled?: boolean }) { disabled={disabled || !includeArchivedSamples} helperText="Export all samples and create tracks for all pads with assigned samples, even if they are not used in patterns" /> + {includeArchivedSamples && playbackOverrides.length > 0 && ( +
+ Force One-Shot + + Override selected EP KEY or LEGATO pads when Live Classic cuts them too early. + +
+ {playbackOverrides.map((item) => ( + setOneShotOverride(item.padCode, checked)} + title={item.title} + disabled={disabled} + /> + ))} +
+
+ )} ( + (value) => typeof value === 'string' && /^[abcd]\d+$/.test(value), + ), + ), projectName: z.string(), includeArchivedSamples: z.boolean(), exportAllPadsWithSamples: z.boolean(),