From 9d9190413d4d047b189c7e8be8955c1b464c3300 Mon Sep 17 00:00:00 2001 From: Xiaohan-Tian <157918347+Xiaohan-Tian@users.noreply.github.com> Date: Thu, 16 Apr 2026 21:05:05 -0700 Subject: [PATCH] fix: allow user to create audio region by triggering audio upload pop-up --- src/components/track/TrackGridPanel.tsx | 97 ++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 3 deletions(-) diff --git a/src/components/track/TrackGridPanel.tsx b/src/components/track/TrackGridPanel.tsx index bb8511f..1feeb16 100644 --- a/src/components/track/TrackGridPanel.tsx +++ b/src/components/track/TrackGridPanel.tsx @@ -1,8 +1,8 @@ -import React, { useRef } from 'react'; +import React, { useRef, useState } from 'react'; import { KGTrack, TrackType } from '../../core/track/KGTrack'; import { KGMidiRegion } from '../../core/region/KGMidiRegion'; import TrackGridItem from './TrackGridItem'; -import { Playhead } from '../common'; +import { Playhead, FileImportModal } from '../common'; import type { RegionUI } from '../interfaces'; import { DEBUG_MODE, REGION_CONSTANTS } from '../../constants'; import { KGMainContentState } from '../../core/state/KGMainContentState'; @@ -48,6 +48,8 @@ const TrackGridPanel: React.FC = ({ onExternalDropComplete, }) => { const gridContainerRef = useRef(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 const createRegionAtPosition = (e: React.MouseEvent, trackIndex: number) => { @@ -74,8 +76,13 @@ const TrackGridPanel: React.FC = ({ const track = tracks[trackIndex]; 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) { + 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; } @@ -135,6 +142,81 @@ const TrackGridPanel: React.FC = ({ } }; + // 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((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 const handleTrackGridDoubleClick = (e: React.MouseEvent, trackIndex: number) => { // Only allow double-click creation in pointer mode @@ -572,6 +654,15 @@ const TrackGridPanel: React.FC = ({ onKGOneClipDrop={handleExternalDrop} /> ))} + + setShowAudioImportModal(false)} + onFileImport={handleAudioFileImport} + acceptedTypes={['.wav', '.mp3', '.ogg', '.flac', '.aac']} + title="Import Audio" + description="Drag and drop your audio file here" + /> ); };