fix: allow user to create audio region by triggering audio upload pop-up
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
import React, { useRef } from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
import { KGTrack, TrackType } from '../../core/track/KGTrack';
|
import { KGTrack, TrackType } from '../../core/track/KGTrack';
|
||||||
import { KGMidiRegion } from '../../core/region/KGMidiRegion';
|
import { KGMidiRegion } from '../../core/region/KGMidiRegion';
|
||||||
import TrackGridItem from './TrackGridItem';
|
import TrackGridItem from './TrackGridItem';
|
||||||
import { Playhead } from '../common';
|
import { Playhead, FileImportModal } from '../common';
|
||||||
import type { RegionUI } from '../interfaces';
|
import type { RegionUI } from '../interfaces';
|
||||||
import { DEBUG_MODE, REGION_CONSTANTS } from '../../constants';
|
import { DEBUG_MODE, REGION_CONSTANTS } from '../../constants';
|
||||||
import { KGMainContentState } from '../../core/state/KGMainContentState';
|
import { KGMainContentState } from '../../core/state/KGMainContentState';
|
||||||
@@ -48,6 +48,8 @@ const TrackGridPanel: React.FC<TrackGridPanelProps> = ({
|
|||||||
onExternalDropComplete,
|
onExternalDropComplete,
|
||||||
}) => {
|
}) => {
|
||||||
const gridContainerRef = useRef<HTMLDivElement>(null);
|
const gridContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [showAudioImportModal, setShowAudioImportModal] = useState(false);
|
||||||
|
const pendingAudioImportRef = useRef<{ barNumber: number; trackIndex: number } | null>(null);
|
||||||
|
|
||||||
// Utility function to create a region at a specific position
|
// Utility function to create a region at a specific position
|
||||||
const createRegionAtPosition = (e: React.MouseEvent<HTMLDivElement>, trackIndex: number) => {
|
const createRegionAtPosition = (e: React.MouseEvent<HTMLDivElement>, trackIndex: number) => {
|
||||||
@@ -74,8 +76,13 @@ const TrackGridPanel: React.FC<TrackGridPanelProps> = ({
|
|||||||
const track = tracks[trackIndex];
|
const track = tracks[trackIndex];
|
||||||
const trackId = track.getId().toString();
|
const trackId = track.getId().toString();
|
||||||
|
|
||||||
// Don't allow manual region creation on audio tracks
|
// For audio tracks, show the file import modal instead of creating a blank region
|
||||||
if (track.getType() === TrackType.Wave) {
|
if (track.getType() === TrackType.Wave) {
|
||||||
|
const snap = KGMainContentState.instance().isSnappingEnabled();
|
||||||
|
const rawBar = relativeX / barWidth + 1;
|
||||||
|
const snappedBarNumber = Math.max(1, snap ? Math.round(rawBar) : rawBar);
|
||||||
|
pendingAudioImportRef.current = { barNumber: snappedBarNumber, trackIndex };
|
||||||
|
setShowAudioImportModal(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,6 +142,81 @@ const TrackGridPanel: React.FC<TrackGridPanelProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle audio file import after the user picks a file from the modal
|
||||||
|
const handleAudioFileImport = async (file: File) => {
|
||||||
|
setShowAudioImportModal(false);
|
||||||
|
const pending = pendingAudioImportRef.current;
|
||||||
|
if (!pending) return;
|
||||||
|
pendingAudioImportRef.current = null;
|
||||||
|
|
||||||
|
const { barNumber, trackIndex } = pending;
|
||||||
|
const track = tracks[trackIndex];
|
||||||
|
if (!track) return;
|
||||||
|
|
||||||
|
const beatsPerBar = timeSignature.numerator;
|
||||||
|
const fileId = KGAudioFileStorage.generateAudioFileId(file.name);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const arrayBuffer = await file.arrayBuffer();
|
||||||
|
const toneBuffer = new Tone.ToneAudioBuffer();
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
const audioContext = Tone.getContext().rawContext as AudioContext;
|
||||||
|
audioContext.decodeAudioData(
|
||||||
|
arrayBuffer.slice(0),
|
||||||
|
(decoded) => { toneBuffer.set(decoded); resolve(); },
|
||||||
|
(err) => reject(err)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const audioDurationSeconds = toneBuffer.duration;
|
||||||
|
await KGAudioFileStorage.storeAudioFile(projectName, fileId, file);
|
||||||
|
KGAudioInterface.instance().loadAudioBufferForTrack(
|
||||||
|
track.getId().toString(),
|
||||||
|
fileId,
|
||||||
|
toneBuffer
|
||||||
|
);
|
||||||
|
|
||||||
|
const bpm = KGCore.instance().getCurrentProject().getBpm();
|
||||||
|
const durationInBeats = audioDurationSeconds * (bpm / 60);
|
||||||
|
const insertBeat = (barNumber - 1) * beatsPerBar;
|
||||||
|
const lengthInBars = Math.max(1, Math.ceil(durationInBeats / beatsPerBar));
|
||||||
|
const prevMaxBars = maxBars;
|
||||||
|
const newMaxBars = Math.max(maxBars, barNumber + lengthInBars - 1);
|
||||||
|
|
||||||
|
const cmd = new ImportAudioCommand(
|
||||||
|
track.getId() as unknown as number,
|
||||||
|
trackIndex,
|
||||||
|
fileId,
|
||||||
|
file.name,
|
||||||
|
audioDurationSeconds,
|
||||||
|
insertBeat,
|
||||||
|
durationInBeats,
|
||||||
|
prevMaxBars,
|
||||||
|
newMaxBars
|
||||||
|
);
|
||||||
|
KGCore.instance().executeCommand(cmd);
|
||||||
|
|
||||||
|
const created = cmd.getCreatedRegion();
|
||||||
|
if (created && onExternalDropComplete) {
|
||||||
|
const regionUI: RegionUI = {
|
||||||
|
id: created.getId(),
|
||||||
|
trackId: track.getId().toString(),
|
||||||
|
trackIndex,
|
||||||
|
barNumber,
|
||||||
|
length: lengthInBars,
|
||||||
|
name: created.getName(),
|
||||||
|
};
|
||||||
|
onExternalDropComplete(trackIndex, regionUI);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG_MODE.TRACK_GRID_PANEL) {
|
||||||
|
console.log(`[TrackGrid] Imported audio "${file.name}" to track ${trackIndex}, bar ${barNumber}, ${audioDurationSeconds.toFixed(2)}s`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[TrackGrid] Audio import from click failed:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Handle double click on track grid to create region
|
// Handle double click on track grid to create region
|
||||||
const handleTrackGridDoubleClick = (e: React.MouseEvent<HTMLDivElement>, trackIndex: number) => {
|
const handleTrackGridDoubleClick = (e: React.MouseEvent<HTMLDivElement>, trackIndex: number) => {
|
||||||
// Only allow double-click creation in pointer mode
|
// Only allow double-click creation in pointer mode
|
||||||
@@ -572,6 +654,15 @@ const TrackGridPanel: React.FC<TrackGridPanelProps> = ({
|
|||||||
onKGOneClipDrop={handleExternalDrop}
|
onKGOneClipDrop={handleExternalDrop}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
<FileImportModal
|
||||||
|
isVisible={showAudioImportModal}
|
||||||
|
onClose={() => setShowAudioImportModal(false)}
|
||||||
|
onFileImport={handleAudioFileImport}
|
||||||
|
acceptedTypes={['.wav', '.mp3', '.ogg', '.flac', '.aac']}
|
||||||
|
title="Import Audio"
|
||||||
|
description="Drag and drop your audio file here"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user