Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
tracks now sound out of the box, including alongside recordings and stems:
**every track plays its instrument** (band mode defaults on), and the guide
voice is always a real instrument — the old "clap"
- **Bring in a whole studio session — File › Audio tracks…** Import any
number of audio tracks (wav / ogg / opus / mp3 / flac — a real session's
multitrack, not just separated stems), then rename them, reorder them,
delete them, and **pair each with the chart track that transcribes it**.
Pairings are saved with the song and survive track reordering. A new
**Solo my source track** command isolates the current track's paired
audio while you chart against it (audible once the stem-mixer strips
land; the pairing itself works today). Imported tracks are packed into
the feedpak on Save (or Build for a fresh Guitar Pro/MIDI import) and
show up as stems everywhere stems already work.

- **Drums sound like a drum kit now.** Every drum piece in the chart plays
its real sound — kick, snares (and cross-stick), open/closed/pedal hats,
all four toms, crashes, splash, china, ride and bell, cowbell — instead of
Expand Down
417 changes: 416 additions & 1 deletion routes.py

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions screen.html
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,24 @@
<tbody id="editor-tempo-list-body"></tbody>
</table>
</div>
<!-- Multitrack stem manager (studio-session ingest): import /
rename / reorder / delete audio tracks and pair each with
the chart track transcribing it. src/stem-tracks.js owns it. -->
<div id="editor-stem-tracks-modal" class="hidden absolute inset-0 z-40 bg-black/40 backdrop-blur-[2px]">
<div class="mx-auto mt-16 w-[30rem] max-w-[calc(100%-2rem)] rounded-lg border border-gray-600 bg-dark-800/95 shadow-2xl text-sm" role="dialog" aria-label="Audio tracks">
<div class="flex items-center justify-between px-3 py-2 border-b border-gray-700">
<span class="text-teal-300 font-medium">Audio tracks</span>
<div class="flex items-center gap-2">
<label class="px-2 py-0.5 rounded bg-teal-700 text-white hover:bg-teal-600 cursor-pointer">Import…
<input id="editor-stem-tracks-file" type="file" multiple accept=".wav,.ogg,.opus,.mp3,.flac,.m4a,.aac,audio/*" class="hidden">
</label>
<button id="editor-stem-tracks-close" class="px-1.5 rounded text-gray-400 hover:text-white hover:bg-dark-600" title="Close">&#10005;</button>
</div>
</div>
<div id="editor-stem-tracks-list" class="px-3 py-1.5 max-h-80 overflow-y-auto text-xs"></div>
<p class="px-3 pb-2 text-[10px] text-gray-500">Each track can be paired with the chart track that transcribes it — pairing powers "Solo my source track" and is saved with the song.</p>
</div>
</div>
<div id="editor-command-palette" class="hidden absolute inset-0 z-40 bg-black/40 backdrop-blur-[2px]">
<div class="mx-auto mt-16 w-[36rem] max-w-[calc(100%-2rem)] rounded-lg border border-gray-600 bg-dark-800/95 shadow-2xl text-sm" role="dialog" aria-label="Command palette">
<input id="editor-palette-input" type="text" placeholder="Type a command… (Esc to close)" autocomplete="off" spellcheck="false"
Expand Down
5 changes: 5 additions & 0 deletions src/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -1584,7 +1584,7 @@
// Left in place rather than deleted, because deleting them is a separate change
// from the bug fix that made them redundant. They arrived with the same
// half-wired Create-New redesign (977ec65, #45).
function _populateCreateArrButtons() {

Check warning on line 1587 in src/create.js

View workflow job for this annotation

GitHub Actions / lint

'_populateCreateArrButtons' is defined but never used
const wrap = document.getElementById('editor-create-arr-buttons');
if (!wrap) return;
wrap.replaceChildren();
Expand Down Expand Up @@ -1757,7 +1757,7 @@
createState.lastSync = { ...createState.lastSync, ...data };
}
return data;
} catch (e) {

Check warning on line 1760 in src/create.js

View workflow job for this annotation

GitHub Actions / lint

'e' is defined but never used. Allowed unused caught errors must match /^_/u
return null;
}
}
Expand Down Expand Up @@ -2440,6 +2440,11 @@
// built pack's manifest (read back on load via data.audio_shift)
// so the alignment survives the first build, not just re-saves.
audio_shift: Number(S.audioShift) || 0,
// Chart-track <-> stem pairings — persisted as
// editor_stem_links in the built pack's manifest (the same
// wire the save body ships), so a pairing made while
// arranging survives the first Build, not just re-saves.
stem_links: S.stemLinks || {},
metadata: {
title: S.title,
artist: S.artist,
Expand Down
6 changes: 6 additions & 0 deletions src/file-ops.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ export async function loadCDLC(filename, options = {}) {
// Authored tempo/meter marks (P2-5) — sanitized at the load boundary
// (hand-edited or future-versioned packs must never crash the editor).
S.tempoMarks = _marksSanitizePure(data.tempo_marks);
// Multitrack stems + chart pairings (studio-session ingest).
S.stems = Array.isArray(data.stems) ? data.stems : [];
S.stemLinks = (data.stem_links && typeof data.stem_links === 'object') ? data.stem_links : {};
S.stemMix = {};
// Drum tab is loaded server-side when the manifest carries a
// `drum_tab:` key and the file passes schema validation. Treat
// a missing/falsey value as "no drums" so the +Drums modal can
Expand Down Expand Up @@ -546,6 +550,8 @@ function _buildSaveBody(forceFullSnapshot) {
// Authored tempo/meter marks (P2-5) ride every save the same way —
// persisted as the `editor_tempo_marks` manifest extension key.
tempo_marks: S.tempoMarks || [],
// Chart-track <-> stem pairings — persisted as editor_stem_links.
stem_links: S.stemLinks || {},
// Always ship title/artist so archive saves persist in-session
// metadata edits too. Backend merges with session metadata
// (album/year captured at load time) so all four fields
Expand Down
3 changes: 3 additions & 0 deletions src/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
} from './loop.js';
import { _editorSongFit } from './song-fit.js';
import { _recState } from './midi-record.js';
import { editorSoloMyStem, editorToggleStemTracks } from './stem-tracks.js';
import { getMousePos } from './mouse.js';
import { _resizeSustainsForDeltaPure, notes } from './notes.js';
import { EDITOR_PROFILE_OVERRIDES, _editorCommandById, _editorEffectiveRightClickBehaviorPure, _editorEofCommandForKeyPure, _editorFeedbackCommandForKeyPure, _editorIsTypingTarget, _editorRenderShortcutPanel, _editorTableCommandForKeyPure, editorRightClickBehavior, editorShortcutProfile } from './shortcuts.js';
Expand Down Expand Up @@ -1041,6 +1042,8 @@ export function _editorRunEofCommand(cmd) {
case 'toggleMetronome': return _editorToggleMetronome();
case 'toggleMixer': return editorToggleMixerPanel();
case 'togglePlayAllTracks': return editorTogglePlayAllTracks();
case 'manageStemTracks': return editorToggleStemTracks();
case 'soloMyStem': return editorSoloMyStem();
case 'toggleLoopAB': return _editorToggleLoopAB();
case 'toggleLoopRegion': return editorToggleLoopRegion();
case 'songFit': _editorSongFit(); return true;
Expand Down
4 changes: 4 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@
} from './tempo.js';
import { initTempoZones } from './tempo-zones.js';
import { _tempoListRender, editorToggleTempoList, initTempoList } from './tempo-list.js';
import { editorSoloMyStem, editorToggleStemTracks, initStemTracks } from './stem-tracks.js';
import {
drawAnchorLane,
drawHandshapeLane, drawToneLane, editorApplyTonesModal, editorHideTonesModal,
Expand Down Expand Up @@ -1745,7 +1746,7 @@
// the same save path as the Save button (in-place sloppak write, not the
// heavy create-mode build).
if (S.sessionId) {
try { await saveCDLC(); } catch (e) { /* surfaced via setStatus */ }

Check warning on line 1749 in src/main.js

View workflow job for this annotation

GitHub Actions / lint

'e' is defined but never used. Allowed unused caught errors must match /^_/u
}
// Capture where we are so the return trip lands on the same spot.
const returnCtx = {
Expand Down Expand Up @@ -2012,6 +2013,9 @@
octave: editorZonesOctaveFix, feel: editorZonesFeelFix });
initTempoList();
window.editorToggleTempoList = editorToggleTempoList;
initStemTracks();
window.editorToggleStemTracks = editorToggleStemTracks;
window.editorSoloMyStem = editorSoloMyStem;
// Registry commands run through `editorRunShortcutCommand` — the SAME
// by-id dispatcher the shortcut panel's buttons use, which is what the
// palette is (a click on a command, not a keypress). Going straight to
Expand Down
11 changes: 9 additions & 2 deletions src/menu-bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
} from './toolbars.js';
import { _clearBarSelection, editorLoopSnapMode, editorSetLoopSnapMode } from './loop.js';
import { editorSetTabViewStaff, editorTabViewStaff } from './tab-view-live.js';
import { stemMixerAvailable } from './stem-tracks.js';

/* @pure:menu-model:start */
// The nine menus (charrette §2.2). Item kinds:
Expand All @@ -53,7 +54,9 @@ import { editorSetTabViewStaff, editorTabViewStaff } from './tab-view-live.js';
// { hdr } — non-interactive section header.
// { sep: true } — divider.
// Gates: `audioOnly` hides without a recording; `needs:'tempoMap'` greys
// outside Tempo Map mode; `fn` items grey when the entry point is absent.
// outside Tempo Map mode; `needs:'stemMixer'` greys until a stem-mixer
// implementation consumes S.stemMix (host.stemMixChanged wired — see
// stemMixerAvailable); `fn` items grey when the entry point is absent.
export const EDITOR_MENUS = Object.freeze([
{ title: 'File', items: [
{ label: 'New…', fn: 'editorShowCreateModal' },
Expand All @@ -70,6 +73,7 @@ export const EDITOR_MENUS = Object.freeze([
{ cmd: 'exportGp5' },
{ sep: true },
{ label: 'Replace audio…', fn: 'editorShowReplaceAudioModal' },
{ cmd: 'manageStemTracks' },
{ label: 'Build feedpak', fn: 'editorBuild' },
] },
{ title: 'Edit', items: [
Expand Down Expand Up @@ -214,6 +218,7 @@ export const EDITOR_MENUS = Object.freeze([
{ sep: true },
{ cmd: 'toggleMetronome' },
{ cmd: 'togglePlayAllTracks' },
{ cmd: 'soloMyStem', needs: 'stemMixer' },
{ cmd: 'toggleGuideClap' },
// Guide voice (DAW 1.2/1.5): what the guide toggle SOUNDS like —
// the clap, or the charted pitches on a GM instrument. The
Expand Down Expand Up @@ -361,7 +366,8 @@ export function _menuModelPure(menus, rows, ctx) {
const row = byId.get(it.cmd);
if (!row) continue; // registry moved on — never render a dangling id
const planned = row.status === 'planned';
const gated = it.needs === 'tempoMap' && !ctx.tempoMapMode;
const gated = (it.needs === 'tempoMap' && !ctx.tempoMapMode)
|| (it.needs === 'stemMixer' && !ctx.stemMixer);
items.push({
label: row.label,
key: row.key,
Expand Down Expand Up @@ -424,6 +430,7 @@ function currentModel() {
_editorShortcutRowsPure(editorShortcutProfile),
{
tempoMapMode: !!S.tempoMapMode, hasAudio: !!S.audioBuffer, fns: windowFns(),
stemMixer: stemMixerAvailable(),
v3: !!(window.slopsmith && window.slopsmith.uiVersion === 'v3'),
toolbars: getToolbarCtx(),
loopSnapMode: editorLoopSnapMode(),
Expand Down
2 changes: 2 additions & 0 deletions src/shortcuts.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ const EDITOR_SHORTCUT_COMMANDS = Object.freeze([
{ id: 'toggleMetronome', label: 'Toggle metronome click', group: 'Preview', status: 'ready', keys: { feedback: '', logical: 'K', cableton: 'O', eof: '' } },
{ id: 'toggleMixer', label: 'Toggle Mixer panel', group: 'Preview', status: 'ready', keys: { feedback: 'Shift+C', eof: 'Shift+C' } },
{ id: 'togglePlayAllTracks', label: 'Play all tracks (band mode)', group: 'Preview', status: 'ready', keys: { feedback: '', eof: '' } },
{ id: 'manageStemTracks', label: 'Audio tracks (import / pair stems)', group: 'Preview', status: 'ready', keys: { feedback: '', eof: '' } },
{ id: 'soloMyStem', label: 'Solo my source track (paired stem)', group: 'Preview', status: 'ready', keys: { feedback: '', eof: '' } },
{ id: 'toggleLoopAB', label: 'Toggle loop A/B compare (recording ↔ guide)', group: 'Preview', status: 'ready', keys: { feedback: 'Alt+B', eof: 'Alt+B' } },
{ id: 'toggleLoopRegion', label: 'Toggle loop playback for the selected region', group: 'Preview', status: 'ready', keys: { feedback: '', logical: 'C', cableton: 'Ctrl+L', eof: '' } },
{ id: 'songFit', label: 'Song Fit — line the chart up with the recording', group: 'Tempo map', status: 'ready', keys: { feedback: '', eof: '' } },
Expand Down
7 changes: 7 additions & 0 deletions src/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,13 @@ export const S = {
// "Edit region" action. Used to make the preview button read as a
// return trip instead of a fresh action.
returnToHighway: false,
// Multitrack stems (studio-session ingest): the session's audio tracks
// [{id, url}] as /load or the import endpoints report them, the
// chart-track -> stem pairings (persisted as editor_stem_links), and
// the live mix map the stem-mixer strips/engine consume.
stems: [],
stemLinks: {},
stemMix: {},
// Drag state
drag: null, // { type, startX, startY, startTime, startString, noteIdx, origTimes, origStrings }

Expand Down
Loading
Loading