Skip to content

Commit 42b7c6b

Browse files
committed
Added buttons to start recording (but no actual recording feature yet). Also made minor changes to the wording of the preference options for clarity.
1 parent ddf597b commit 42b7c6b

File tree

5 files changed

+193
-56
lines changed

5 files changed

+193
-56
lines changed

editor/Preferences.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export class Preferences {
3737

3838
public reload(): void {
3939
this.autoPlay = window.localStorage.getItem("autoPlay") == "true";
40-
this.autoFollow = window.localStorage.getItem("autoFollow") == "true";
40+
this.autoFollow = window.localStorage.getItem("autoFollow") != "false";
4141
this.enableNotePreview = window.localStorage.getItem("enableNotePreview") != "false";
4242
this.showFifth = window.localStorage.getItem("showFifth") == "true";
4343
this.notesOutsideScale = window.localStorage.getItem("notesOutsideScale") == "true";

editor/RecordingSetupPrompt.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export class RecordingSetupPrompt implements Prompt {
3333
h2("Note Recording Setup"),
3434
p("BeepBox can record notes that you play on a keyboard. You can start recording by pressing Ctrl+Space."),
3535
label({style: "display: flex; flex-direction: row; align-items: center; height: 2em; justify-content: flex-end;"},
36-
"Also show record button:",
36+
"Also show record button:",
3737
this._showRecordButton,
3838
),
3939
label({style: "display: flex; flex-direction: row; align-items: center; height: 2em; justify-content: flex-end;"},

editor/SongEditor.ts

Lines changed: 95 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,12 @@ export class SongEditor {
132132
private readonly _loopEditor: LoopEditor = new LoopEditor(this._doc);
133133
private readonly _octaveScrollBar: OctaveScrollBar = new OctaveScrollBar(this._doc);
134134
private readonly _piano: Piano = new Piano(this._doc);
135-
private readonly _playButton: HTMLButtonElement = button({style: "width: 80px;", type: "button"});
136-
private readonly _prevBarButton: HTMLButtonElement = button({class: "prevBarButton", style: "width: 40px;", type: "button", title: "Previous Bar (left bracket)"});
137-
private readonly _nextBarButton: HTMLButtonElement = button({class: "nextBarButton", style: "width: 40px;", type: "button", title: "Next Bar (right bracket)"});
135+
private readonly _playButton: HTMLButtonElement = button({class: "playButton", type: "button", title: "Play (Space)"}, span("Play"));
136+
private readonly _pauseButton: HTMLButtonElement = button({class: "pauseButton", style: "display: none;", type: "button", title: "Pause (Space)"}, "Pause");
137+
private readonly _recordButton: HTMLButtonElement = button({class: "recordButton", style: "display: none;", type: "button", title: "Record (Ctrl+Space)"}, span("Record"));
138+
private readonly _stopButton: HTMLButtonElement = button({class: "stopButton", style: "display: none;", type: "button", title: "Stop Recording (Space)"}, "Stop");
139+
private readonly _prevBarButton: HTMLButtonElement = button({class: "prevBarButton", type: "button", title: "Previous Bar (left bracket)"});
140+
private readonly _nextBarButton: HTMLButtonElement = button({class: "nextBarButton", type: "button", title: "Next Bar (right bracket)"});
138141
private readonly _volumeSlider: HTMLInputElement = input({title: "main volume", style: "width: 5em; flex-grow: 1; margin: 0;", type: "range", min: "0", max: "75", value: "50", step: "1"});
139142
private readonly _fileMenu: HTMLSelectElement = select({style: "width: 100%;"},
140143
option({selected: true, disabled: true, hidden: false}, "File"), // todo: "hidden" should be true but looks wrong on mac chrome, adds checkmark next to first visible option even though it's not selected. :(
@@ -171,15 +174,15 @@ export class SongEditor {
171174
);
172175
private readonly _optionsMenu: HTMLSelectElement = select({style: "width: 100%;"},
173176
option({selected: true, disabled: true, hidden: false}, "Preferences"), // todo: "hidden" should be true but looks wrong on mac chrome, adds checkmark next to first visible option even though it's not selected. :(
174-
option({value: "autoPlay"}, "Auto Play On Load"),
175-
option({value: "autoFollow"}, "Auto Follow Track"),
176-
option({value: "enableNotePreview"}, "Preview Added Notes"),
177+
option({value: "autoPlay"}, "Auto Play on Load"),
178+
option({value: "autoFollow"}, "Keep Current Pattern Selected"),
179+
option({value: "enableNotePreview"}, "Hear Preview of Added Notes"),
177180
option({value: "showLetters"}, "Show Piano Keys"),
178-
option({value: "showFifth"}, 'Highlight "Fifth" Notes'),
179-
option({value: "notesOutsideScale"}, "Allow Notes Outside Scale"),
181+
option({value: "showFifth"}, 'Highlight "Fifth" of Song Key'),
182+
option({value: "notesOutsideScale"}, "Allow Adding Notes Not in Scale"),
180183
option({value: "setDefaultScale"}, "Use Current Scale as Default"),
181-
option({value: "showChannels"}, "Show All Channels"),
182-
option({value: "showScrollBar"}, "Octave Scroll Bar"),
184+
option({value: "showChannels"}, "Show Notes From All Channels"),
185+
option({value: "showScrollBar"}, "Show Octave Scroll Bar"),
183186
option({value: "alwaysShowSettings"}, "Customize All Instruments"),
184187
option({value: "instrumentCopyPaste"}, "Instrument Copy/Paste Buttons"),
185188
option({value: "enableChannelMuting"}, "Enable Channel Muting"),
@@ -367,6 +370,9 @@ export class SongEditor {
367370
div({class: "play-pause-area"},
368371
div({class: "playback-bar-controls"},
369372
this._playButton,
373+
this._pauseButton,
374+
this._recordButton,
375+
this._stopButton,
370376
this._prevBarButton,
371377
this._nextBarButton,
372378
),
@@ -426,6 +432,11 @@ export class SongEditor {
426432
private _currentPromptName: string | null = null;
427433
private _highlightedInstrumentIndex: number = -1;
428434
private _renderedInstrumentCount: number = 0;
435+
private _renderedIsPlaying: boolean = false;
436+
private _renderedIsRecording: boolean = false;
437+
private _renderedShowRecordButton: boolean = false;
438+
private _renderedCtrlHeld: boolean = false;
439+
private _ctrlHeld: boolean = false;
429440
private _deactivatedInstruments: boolean = false;
430441
private readonly _operatorRows: HTMLDivElement[] = []
431442
private readonly _operatorAmplitudeSliders: Slider[] = []
@@ -437,6 +448,7 @@ export class SongEditor {
437448
this._doc.notifier.watch(this.whenUpdated);
438449
new MidiInputHandler(this._doc);
439450
window.addEventListener("resize", this.whenUpdated);
451+
window.requestAnimationFrame(this.updatePlayButton);
440452

441453
if (!("share" in navigator)) {
442454
this._fileMenu.removeChild(this._fileMenu.querySelector("[value='shareUrl']")!);
@@ -525,6 +537,22 @@ export class SongEditor {
525537
this._chordSelect.addEventListener("change", this._whenSetChord);
526538
this._vibratoSelect.addEventListener("change", this._whenSetVibrato);
527539
this._playButton.addEventListener("click", this._togglePlay);
540+
this._pauseButton.addEventListener("click", this._togglePlay);
541+
this._recordButton.addEventListener("click", this._toggleRecord);
542+
this._stopButton.addEventListener("click", this._toggleRecord);
543+
// Start recording instead of opening context menu when control-clicking the record button on a Mac.
544+
this._recordButton.addEventListener("contextmenu", (event: MouseEvent) => {
545+
if (event.ctrlKey) {
546+
event.preventDefault();
547+
this._toggleRecord();
548+
}
549+
});
550+
this._stopButton.addEventListener("contextmenu", (event: MouseEvent) => {
551+
if (event.ctrlKey) {
552+
event.preventDefault();
553+
this._toggleRecord();
554+
}
555+
});
528556
this._prevBarButton.addEventListener("click", this._whenPrevBarPressed);
529557
this._nextBarButton.addEventListener("click", this._whenNextBarPressed);
530558
this._volumeSlider.addEventListener("input", this._setVolumeSlider);
@@ -681,15 +709,15 @@ export class SongEditor {
681709
this._patternEditor.render();
682710

683711
const optionCommands: ReadonlyArray<string> = [
684-
(prefs.autoPlay ? "✓ " : " ") + "Auto Play On Load",
685-
(prefs.autoFollow ? "✓ " : " ") + "Auto Follow Track",
686-
(prefs.enableNotePreview ? "✓ " : " ") + "Preview Added Notes",
712+
(prefs.autoPlay ? "✓ " : " ") + "Auto Play on Load",
713+
(prefs.autoFollow ? "✓ " : " ") + "Keep Current Pattern Selected",
714+
(prefs.enableNotePreview ? "✓ " : " ") + "Hear Preview of Added Notes",
687715
(prefs.showLetters ? "✓ " : " ") + "Show Piano Keys",
688-
(prefs.showFifth ? "✓ " : " ") + 'Highlight "Fifth" Notes',
689-
(prefs.notesOutsideScale ? "✓ " : " ") + "Allow Notes Outside Scale",
716+
(prefs.showFifth ? "✓ " : " ") + 'Highlight "Fifth" of Song Key',
717+
(prefs.notesOutsideScale ? "✓ " : " ") + "Allow Adding Notes Not in Scale",
690718
(prefs.defaultScale == this._doc.song.scale ? "✓ " : " ") + "Use Current Scale as Default",
691-
(prefs.showChannels ? "✓ " : " ") + "Show All Channels",
692-
(prefs.showScrollBar ? "✓ " : " ") + "Octave Scroll Bar",
719+
(prefs.showChannels ? "✓ " : " ") + "Show Notes From All Channels",
720+
(prefs.showScrollBar ? "✓ " : " ") + "Show Octave Scroll Bar",
693721
(prefs.alwaysShowSettings ? "✓ " : " ") + "Customize All Instruments",
694722
(prefs.instrumentCopyPaste ? "✓ " : " ") + "Instrument Copy/Paste Buttons",
695723
(prefs.enableChannelMuting ? "✓ " : " ") + "Enable Channel Muting",
@@ -1047,18 +1075,40 @@ export class SongEditor {
10471075
}
10481076
}
10491077

1050-
public updatePlayButton(): void {
1051-
if (this._doc.synth.playing) {
1052-
this._playButton.classList.remove("playButton");
1053-
this._playButton.classList.add("pauseButton");
1054-
this._playButton.title = "Pause (Space)";
1055-
this._playButton.textContent = "Pause";
1056-
} else {
1057-
this._playButton.classList.remove("pauseButton");
1058-
this._playButton.classList.add("playButton");
1059-
this._playButton.title = "Play (Space)";
1060-
this._playButton.textContent = "Play";
1078+
public updatePlayButton = (): void => {
1079+
if (this._renderedIsPlaying != this._doc.synth.playing || this._renderedIsRecording != this._doc.synth.recording || this._renderedShowRecordButton != this._doc.prefs.showRecordButton || this._renderedCtrlHeld != this._ctrlHeld) {
1080+
this._renderedIsPlaying = this._doc.synth.playing;
1081+
this._renderedIsRecording = this._doc.synth.recording;
1082+
this._renderedShowRecordButton = this._doc.prefs.showRecordButton;
1083+
this._renderedCtrlHeld = this._ctrlHeld;
1084+
1085+
if (document.activeElement == this._playButton || document.activeElement == this._pauseButton || document.activeElement == this._recordButton || document.activeElement == this._stopButton) {
1086+
// When a focused element is hidden, focus is transferred to the document, so let's refocus the editor instead to make sure we can still capture keyboard input.
1087+
this._refocusStage();
1088+
}
1089+
1090+
this._playButton.style.display = "none";
1091+
this._pauseButton.style.display = "none";
1092+
this._recordButton.style.display = "none";
1093+
this._stopButton.style.display = "none";
1094+
this._playButton.classList.remove("shrunk");
1095+
this._recordButton.classList.remove("shrunk");
1096+
if (this._doc.synth.recording) {
1097+
this._stopButton.style.display = "";
1098+
} else if (this._doc.synth.playing) {
1099+
this._pauseButton.style.display = "";
1100+
} else if (this._doc.prefs.showRecordButton) {
1101+
this._playButton.style.display = "";
1102+
this._recordButton.style.display = "";
1103+
this._playButton.classList.add("shrunk");
1104+
this._recordButton.classList.add("shrunk");
1105+
} else if (this._ctrlHeld) {
1106+
this._recordButton.style.display = "";
1107+
} else {
1108+
this._playButton.style.display = "";
1109+
}
10611110
}
1111+
window.requestAnimationFrame(this.updatePlayButton);
10621112
}
10631113

10641114
private _disableCtrlContextMenu = (event: MouseEvent): boolean => {
@@ -1098,6 +1148,8 @@ export class SongEditor {
10981148
}
10991149

11001150
private _whenKeyPressed = (event: KeyboardEvent): void => {
1151+
this._ctrlHeld = event.ctrlKey;
1152+
11011153
if (this.prompt) {
11021154
if (event.keyCode == 27) { // ESC key
11031155
// close prompt.
@@ -1117,7 +1169,9 @@ export class SongEditor {
11171169
}
11181170
break;
11191171
case 32: // space
1120-
if (event.shiftKey) {
1172+
if (event.ctrlKey) {
1173+
this._toggleRecord();
1174+
} else if (event.shiftKey) {
11211175
// Jump to mouse
11221176
if (this._trackEditor.movePlayheadToMouse() || this._patternEditor.movePlayheadToMouse()) {
11231177
if (!this._doc.synth.playing) this._play();
@@ -1453,6 +1507,8 @@ export class SongEditor {
14531507
}
14541508

14551509
private _whenKeyReleased = (event: KeyboardEvent): void => {
1510+
this._ctrlHeld = event.ctrlKey;
1511+
14561512
const canPlayNotes: boolean = (this._doc.prefs.pressControlForShortcuts ? !event.ctrlKey && !event.metaKey : event.getModifierState("CapsLock"));
14571513
if (canPlayNotes) this._keyboardLayout.handleKeyEvent(event, false);
14581514
}
@@ -1491,10 +1547,19 @@ export class SongEditor {
14911547
}
14921548
}
14931549

1550+
private _toggleRecord = (): void => {
1551+
if (this._doc.synth.playing) {
1552+
this._pause();
1553+
} else {
1554+
this._doc.synth.snapToBar();
1555+
this._doc.synth.startRecording();
1556+
this._doc.synth.maintainLiveInput();
1557+
}
1558+
}
1559+
14941560
private _play(): void {
14951561
this._doc.synth.play();
14961562
this._doc.synth.maintainLiveInput();
1497-
this.updatePlayButton();
14981563
}
14991564

15001565
private _pause(): void {
@@ -1505,7 +1570,6 @@ export class SongEditor {
15051570
this._doc.synth.goToBar(this._doc.bar);
15061571
}
15071572
this._doc.synth.snapToBar();
1508-
this.updatePlayButton();
15091573
}
15101574

15111575
private _setVolumeSlider = (): void => {

0 commit comments

Comments
 (0)