Fix Activity 3 bassline import, media proxying, and multitrack editor interactions#156
Merged
hcientist merged 4 commits intoLab-Lab-Lab:mainfrom Apr 2, 2026
Merged
Conversation
Resize clamping: - resizeL: can't drag past offset=0 (buffer start) - resizeR: can't extend past sourceDuration-offset (buffer end) - If sourceDuration is unknown, defaults to offset+duration (no extending) Waveform rendering: - Peaks drawn at 1 peak = 1 pixel instead of stretching across clip width - Audio portion renders accurately; excess clip area beyond audio is empty - Center line still spans full clip width for visual bounds
Replaces the broken getPeaksForClip approach (which returned peaks at a cached resolution that didn't match pixel width, causing stretch/drop artifacts) with direct per-pixel computation from the decoded AudioBuffer. Follows the approach used by Audacity, wavesurfer.js, and waveform-playlist: - Cache decoded AudioBuffer per source URL (not pre-computed peaks) - At draw time, compute samplesPerPixel = durationSamples / pixelWidth - For each pixel column, find min/max from the corresponding sample range - Use Audacity's rounding pattern (round(col*spp) to round((col+1)*spp)) to avoid cumulative drift across columns - Apply Audacity's gap-filling between adjacent columns for visual continuity
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Activity 3's bassline import was broken in prod — students could not load the bassline into the multitrack editor. The root cause was a chain of issues: wrong data source for the audio URL, CORS blocking
fetch()/decodeAudioData()on cross-origin media files, and several multitrack editor interaction bugs.High-risk changes
/media/proxy via Next.js rewrite (next.config.js)The backend's
sample_audioFileField serializes as an absolute URL pointing to the API host (e.g.https://dev-api.musiccpr.org/media/sample_audio/...). The browser's<audio>element loads these cross-origin fine, butfetch()anddecodeAudioData()— used by the import pipeline to decode audio into a buffer for waveform rendering — require CORS headers that the backend doesn't serve for/media/paths.Rather than adding CORS configuration to the backend (environment-specific, fragile), media requests are now proxied through Next.js using the same rewrite pattern already established for
/backend/*:The bassline's absolute URL is stripped to a relative
/media/...path so it routes through the proxy. This works in all environments without backend changes.assertResponsenow includes response body (api.js)Error messages from the API layer now include the backend's response body (e.g.
400: Bad Request — {"error":"database is locked"}) instead of just the HTTP status text. This is a change to the sharedmakeRequestpath —assertResponseis now async.Bassline import flow
activities[piece]?.find(a => a.part_type === 'Bassline')?.part?.sample_audio, not from the Melody assignment'spreferredSampleinitialTracks(which haduseStatetiming issues)Audio.loadedmetadatafor takes that report 0:00DAWInitializercomponent setsshowDAW=trueanddawMode='multi'for Activities 3-4 (previously both defaulted to false/single)Activity operation logging
logOperationcalls inCustomTimeline.jsxare nowawaited sequentially to prevent concurrent backend requests (causeddatabase is lockedon SQLite, stalestep_completionsoverwrites on PostgreSQL)Multitrack editor fixes
AudioContextwas suspended; addedaudioContext.resume()on playonPointerDownselected the clip but returned before initializing drag state; now initializes drag in the same handlerresizeLclamps at offset=0,resizeRclamps atsourceDuration - offset. Clips storesourceDurationfor bounds checking, with fallback to the decoded AudioBuffer's duration from the waveform cache