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
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
known the editor *probably* has something but couldn't find which menu it
lives in, this is the answer: type "snap", "tempo", "export", "guide" and
the matching commands surface instantly, fuzzy matching included.
- **Two new shortcut profiles: Logical and Cableton.** If your hands already
know a DAW, the editor can meet them there. **Logical** (Logic-style) puts
the metronome on **K**, quantize on **Q**, steps the playhead by beat with
**`,` / `.`**, loops the selection with **C**, creates a section with
**Alt+'** (the marker key). **Cableton** (Ableton-style) quantizes with
**Ctrl+U**, narrows/widens the grid with **Ctrl+1 / Ctrl+2**, toggles snap
with **Ctrl+4**, clicks with **O**, follows playback with **Ctrl+Shift+F**,
and loops the selection with **Ctrl+L**. Everything a profile doesn't remap
keeps its FeedBack key, so editor-specific commands (techniques, tempo
mapping, string moves) work identically everywhere — and where a DAW key
displaces a FeedBack one, the displaced command *relocates* (Logical: pick
direction moves to **Shift+K**, guide claps to **Ctrl+Shift+C**; Cableton:
pop moves to **Ctrl+Shift+P**, select-matching to **Ctrl+Shift+L**) and the
shortcut panel shows its new key — no command ever loses its keyboard, and
no chord is ever double-bound. Logic's Repeat (**Cmd/Ctrl+R**) is
deliberately *not* bound: the desktop app's own Reload accelerator owns that
chord and would reload the editor out from under you; duplicate stays on
**Ctrl+D** in every profile. The old EOF profile is still here as **Legacy
(EOF)**. Switch in Help ▸ Shortcut profile, the Shortcuts panel, or the
toolbar select; the shortcut panel always shows the keys for whichever
profile is live.
- **Loop toggle and Song Fit are real commands now.** "Toggle loop playback
for the selected region" and "Song Fit" joined the command registry, so both
are reachable from anywhere commands are listed, Song Fit gained a menu home
(**Tempo/Grid ▸ Song Fit**), and profiles can bind keys to them (Logical's C
and Cableton's Ctrl+L drive the loop toggle).
- **Scan for tempo zones (preview).** A new **Tempo/Grid ▸ Scan for tempo zones**
action reads the recording and reports the handful of *tempo intents* it finds
— e.g. "3 tempo zones detected: 120 bpm · 140 bpm · rit 140→90". It's the first
Expand Down
10 changes: 7 additions & 3 deletions docs/USER-GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ grid up to the music, place the notes, mark the tempo and sections, and build a
`.feedpak` the rest of the app can practice against.

This guide is the same content you get in-app from **Help ▸ User Guide**. It
covers the FeedBack shortcut profile (the default); the EOF Legacy profile
remaps some keys — switch profiles in **Help ▸ Shortcut profile** or the
shortcut panel (`?`).
covers the FeedBack shortcut profile (the default). Three other profiles remap
some keys to match muscle memory you may already have: **Logical**
(Logic-style — K clicks the metronome, Q quantizes, `,`/`.` step by beat, C
loops the selection), **Cableton** (Ableton-style — Ctrl+U quantizes, Ctrl+1/2
narrow/widen the grid, Ctrl+4 toggles snap, Ctrl+L loops), and **Legacy
(EOF)**. Anything a profile doesn't remap keeps its FeedBack key. Switch
profiles in **Help ▸ Shortcut profile** or the shortcut panel (`?`).

![The Song Editor loaded with a song: the menu bar and toolbars up top, the timeline (waveform and colored note blocks per string) in the middle, the transport and inspector around it.](../assets/guide/workspace.png)

Expand Down
8 changes: 6 additions & 2 deletions screen.html
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,9 @@
<span class="text-xs text-gray-500">Keys:</span>
<select id="editor-shortcut-profile" onchange="editorSetShortcutProfile(this.value)" class="bg-dark-700 border border-gray-700 rounded px-1.5 py-0.5 text-xs text-gray-300 outline-none" title="Keyboard shortcut profile">
<option value="feedback">FeedBack</option>
<option value="eof">EOF Legacy</option>
<option value="logical">Logical (Logic-style)</option>
<option value="cableton">Cableton (Ableton-style)</option>
<option value="eof">Legacy (EOF)</option>
</select>
<button id="editor-shortcuts-btn" onclick="editorToggleShortcutPanel()" class="px-2 py-0.5 bg-dark-600 hover:bg-dark-500 rounded text-xs" title="Show shortcut and command controls">Shortcuts…</button>
</div>
Expand Down Expand Up @@ -305,7 +307,9 @@
<span class="text-gray-500">Profile</span>
<select id="editor-shortcut-panel-profile" onchange="editorSetShortcutProfile(this.value)" class="bg-dark-700 border border-gray-700 rounded px-1.5 py-0.5 text-xs text-gray-300 outline-none">
<option value="feedback">FeedBack</option>
<option value="eof">EOF Legacy</option>
<option value="logical">Logical (Logic-style)</option>
<option value="cableton">Cableton (Ableton-style)</option>
<option value="eof">Legacy (EOF)</option>
</select>
<span class="text-gray-500 ml-2">Right-click</span>
<select id="editor-right-click-behavior" onchange="editorSetRightClickBehavior(this.value)" class="bg-dark-700 border border-gray-700 rounded px-1.5 py-0.5 text-xs text-gray-300 outline-none">
Expand Down
24 changes: 20 additions & 4 deletions src/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ import { hitNote } from './hit-test.js';
import { _renderInspector, _selectedChordContext } from './inspector.js';
import { PIANO_LANE_H, _rollLockNotice, _rollReadOnly, isKeysArr, isKeysMode, midiToFret, midiToString, pianoLaneCount, yToMidi } from './keys.js';
import { lanes, _stringCountFor } from './lanes.js';
import { _editorClampScrollX, _loopNudgeEdge, snapGuidelineAfter, snapTime } from './loop.js';
import {
_editorClampScrollX, _loopNudgeEdge, editorToggleLoopRegion, snapGuidelineAfter, snapTime,
} from './loop.js';
import { _editorSongFit } from './song-fit.js';
import { _recState } from './midi-record.js';
import { getMousePos } from './mouse.js';
import { _resizeSustainsForDeltaPure, notes } from './notes.js';
import { _editorCommandById, _editorEffectiveRightClickBehaviorPure, _editorEofCommandForKeyPure, _editorFeedbackCommandForKeyPure, _editorIsTypingTarget, _editorRenderShortcutPanel, editorRightClickBehavior, editorShortcutProfile } from './shortcuts.js';
import { EDITOR_PROFILE_OVERRIDES, _editorCommandById, _editorEffectiveRightClickBehaviorPure, _editorEofCommandForKeyPure, _editorFeedbackCommandForKeyPure, _editorIsTypingTarget, _editorRenderShortcutPanel, _editorTableCommandForKeyPure, editorRightClickBehavior, editorShortcutProfile } from './shortcuts.js';
import { SNAP_VALUES, _editorEffectiveSnapValuePure, _editorSnapSubdivisionsPure } from './snap.js';
import { S } from './state.js';
import { _editorShowTabPreview, _tabPreviewKeyPolicyPure } from './tab-preview.js';
Expand Down Expand Up @@ -913,6 +916,8 @@ export function _editorRunEofCommand(cmd) {
case 'toggleMetronome': return _editorToggleMetronome();
case 'toggleMixer': return editorToggleMixerPanel();
case 'toggleLoopAB': return _editorToggleLoopAB();
case 'toggleLoopRegion': return editorToggleLoopRegion();
case 'songFit': _editorSongFit(); return true;
case 'toggleOnsetStrip': return _editorToggleOnsetStrip();
case 'togglePartsView': return host.editorTogglePartsView();
case 'toggleKeyHighlight': return host.editorToggleKeyHighlight();
Expand Down Expand Up @@ -1017,8 +1022,19 @@ export function _editorRunEofCommand(cmd) {
}

function _editorDispatchFeedbackShortcut(e) {
if (editorShortcutProfile !== 'feedback' || _editorIsTypingTarget(e)) return false;
const cmd = _editorFeedbackCommandForKeyPure(e, S.tempoMapMode ? 'tempoMap' : 'note');
if (_editorIsTypingTarget(e)) return false;
// The delta profiles (Logical / Cableton) resolve their override table
// first and fall through to the FeedBack meaning for everything else;
// plain FeedBack skips the table. EOF has its own dispatcher below.
let cmd = null;
if (editorShortcutProfile === 'feedback') {
cmd = _editorFeedbackCommandForKeyPure(e, S.tempoMapMode ? 'tempoMap' : 'note');
} else if (EDITOR_PROFILE_OVERRIDES[editorShortcutProfile]) {
cmd = _editorTableCommandForKeyPure(e, S.tempoMapMode ? 'tempoMap' : 'note',
EDITOR_PROFILE_OVERRIDES[editorShortcutProfile]);
} else {
return false;
}
if (!cmd) return false;
const def = _editorCommandById(cmd);
if (def && def.status !== 'ready') return false;
Expand Down
9 changes: 6 additions & 3 deletions src/menu-bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ export const EDITOR_MENUS = Object.freeze([
{ cmd: 'nextAnchor' },
{ sep: true },
{ hdr: 'Loop' },
{ label: 'Loop region', fn: 'editorToggleLoopRegion' },
{ cmd: 'toggleLoopRegion' },
{ cmd: 'toggleLoopAB' },
{ label: 'Loop in 3D', fn: 'editorLoopIn3D' },
{ loopClear: true, label: 'Clear loop' },
Expand Down Expand Up @@ -228,6 +228,7 @@ export const EDITOR_MENUS = Object.freeze([
{ cmd: 'tempoFullDialog' },
{ cmd: 'tempoRebuildGrid' },
{ sep: true },
{ cmd: 'songFit' },
{ label: 'Sync tempo to audio', fn: 'editorSyncTempo', audioOnly: true },
{ label: 'Scan for tempo zones…', fn: 'editorScanTempoZones', audioOnly: true },
{ label: 'Apply rough map from tempo zones', fn: 'editorApplyTempoZones', audioOnly: true },
Expand All @@ -251,7 +252,7 @@ export const EDITOR_MENUS = Object.freeze([
{ cmd: 'showShortcutHelp' },
{ cmd: 'midiTones' },
{ sep: true },
{ label: 'Shortcut profile: FeedBack ⇄ EOF', fn: '__swapProfile' },
{ label: 'Shortcut profile: next (FeedBack → Logical → Cableton → Legacy)', fn: '__swapProfile' },
] },
]);

Expand Down Expand Up @@ -415,7 +416,9 @@ function dispatch(d) {
if (d.guideVoice) { _editorSetGuideVoiceMode(d.guideVoice); return; }
if (d.gmVoice != null) { editorSetGmVoice(d.gmKind, d.gmVoice); return; }
if (d.fn === '__swapProfile') {
const next = editorShortcutProfile === 'eof' ? 'feedback' : 'eof';
// Cycle the four profiles in a fixed order; the two selects follow.
const order = ['feedback', 'logical', 'cableton', 'eof'];
const next = order[(order.indexOf(editorShortcutProfile) + 1) % order.length];
if (typeof window.editorSetShortcutProfile === 'function') window.editorSetShortcutProfile(next);
const sel = document.getElementById('editor-shortcut-profile');
if (sel) sel.value = next;
Expand Down
Loading
Loading