Refine piano recording controls and stop behavior

This commit is contained in:
Jage9
2026-02-23 02:00:01 -05:00
parent 92aabd54ef
commit ccbe41e618
11 changed files with 106 additions and 23 deletions

View File

@@ -87,7 +87,7 @@
},
{
"keys": "Piano mode",
"description": "When using a piano: press question mark for piano help. 1-9 (and 0 for the 10th slot) changes instrument, -/= changes octave, ASDFGHJKL;' plays C major notes, WETYUOP] plays sharps, Z starts/stops recording, X plays recording, Enter plays demo, C stops demo/playback, Escape exits"
"description": "When using a piano: press question mark for piano help. 1-9 (and 0 for the 10th slot) changes instrument, -/= changes octave, ASDFGHJKL;' plays C major notes, WETYUOP] plays sharps, Z starts/pauses/resumes recording, X plays recording, Enter plays demo, C stops demo/playback/recording, Escape exits"
}
]
},

View File

@@ -21,19 +21,19 @@
},
{
"keys": "Z",
"description": "Start or stop recording (up to 30 seconds)."
"description": "Start, pause, or resume recording (up to 30 seconds of recorded time)."
},
{
"keys": "X",
"description": "Play the saved recording."
"description": "Play the saved recording. Stops demo if demo is active."
},
{
"keys": "Enter",
"description": "Play demo melody. Press Enter again to restart it."
"description": "Play demo melody. Press Enter again to restart it. Stops recording playback first."
},
{
"keys": "C",
"description": "Stop demo and recording playback."
"description": "Stop demo, recording playback, and active recording."
},
{
"keys": "Escape",

View File

@@ -1,5 +1,5 @@
// Maintainer-controlled web client version.
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2)
window.CHGRID_WEB_VERSION = "2026.02.23 R219";
window.CHGRID_WEB_VERSION = "2026.02.23 R220";
// Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
window.CHGRID_TIME_ZONE = "America/Detroit";

View File

@@ -297,6 +297,7 @@ const activePianoDemoTimeoutIds: number[] = [];
const activePianoDemoNotes = new Map<string, { runtimeKey: string; midi: number }>();
const pianoDemoSongs = new Map<string, PianoDemoSong>();
let pianoDemoDefaultSongId = '';
let activePianoRecordingState: 'idle' | 'recording' | 'paused' = 'idle';
const activeRemotePianoKeys = new Set<string>();
let pianoPreviewTimeoutId: number | null = null;
let activeTeleport:
@@ -1034,6 +1035,7 @@ async function startPianoUseMode(itemId: string): Promise<void> {
activePianoKeyMidi.clear();
activePianoHeldOrder.length = 0;
activePianoMonophonicKey = null;
activePianoRecordingState = 'idle';
state.mode = 'pianoUse';
await audio.ensureContext();
updateStatus(`using ${item.title}, press question mark for help.`);
@@ -1056,6 +1058,7 @@ function stopPianoUseMode(announce = true): void {
activePianoKeyMidi.clear();
activePianoHeldOrder.length = 0;
activePianoMonophonicKey = null;
activePianoRecordingState = 'idle';
state.mode = 'normal';
if (announce) {
updateStatus('Stopped piano.');
@@ -2136,6 +2139,22 @@ async function onSignalingMessage(message: IncomingMessage): Promise<void> {
startHeartbeat();
}
await onAppMessage(message);
if (
message.type === 'item_action_result' &&
message.ok &&
message.action === 'use' &&
typeof message.itemId === 'string' &&
activePianoItemId &&
message.itemId === activePianoItemId
) {
if (message.message === 'record' || message.message === 'resume') {
activePianoRecordingState = 'recording';
} else if (message.message === 'pause') {
activePianoRecordingState = 'paused';
} else if (message.message === 'stop') {
activePianoRecordingState = 'idle';
}
}
if (
message.type === 'item_action_result' &&
message.ok &&
@@ -2645,6 +2664,12 @@ function handlePianoUseModeInput(code: string): void {
return;
}
if (code === 'Enter') {
if (activePianoRecordingState !== 'idle') {
updateStatus('Stop or pause recording first.');
audio.sfxUiCancel();
return;
}
signaling.send({ type: 'item_piano_recording', itemId, action: 'stop_playback' });
startPianoDemo(item, itemId);
updateStatus('demo play');
audio.sfxUiBlip();
@@ -2655,12 +2680,20 @@ function handlePianoUseModeInput(code: string): void {
return;
}
if (code === 'KeyX') {
if (activePianoRecordingState !== 'idle') {
updateStatus('Stop or pause recording first.');
audio.sfxUiCancel();
return;
}
stopPianoDemo(true);
signaling.send({ type: 'item_piano_recording', itemId, action: 'playback' });
return;
}
if (code === 'KeyC') {
stopPianoDemo(true);
signaling.send({ type: 'item_piano_recording', itemId, action: 'stop_playback' });
signaling.send({ type: 'item_piano_recording', itemId, action: 'stop_record' });
activePianoRecordingState = 'idle';
return;
}
if (code === 'Equal' || code === 'Minus') {

View File

@@ -243,6 +243,8 @@ export function createOnMessageHandler(deps: MessageHandlerDeps): (message: Inco
case 'item_action_result': {
const pianoStatusMessages = new Set([
'record',
'pause',
'resume',
'play',
'stop',
'No recording saved on this piano.',

View File

@@ -199,7 +199,7 @@ export type OutgoingMessage =
| { type: 'item_delete'; itemId: string }
| { type: 'item_use'; itemId: string }
| { type: 'item_piano_note'; itemId: string; keyId: string; midi: number; on: boolean }
| { type: 'item_piano_recording'; itemId: string; action: 'toggle_record' | 'playback' | 'stop_playback' }
| { type: 'item_piano_recording'; itemId: string; action: 'toggle_record' | 'playback' | 'stop_playback' | 'stop_record' }
| {
type: 'item_update';
itemId: string;