Files
SonicForgeStudio/md/31_MIDI_FIX.md
T

174 lines
6.5 KiB
Markdown

# DIAGNOSTIC REPORT: WHY MIDI SIGNAL IS RECEIVED BUT NOT RECORDED / RENDERED
---
## 1. Executive Summary & Root Cause Analysis
Based on the DAW UI screenshot provided, the system is successfully receiving MIDI hardware signals (as indicated by the active VU meter on Track 01 set to `MIDIIN2 (SE49)`), but no MIDI data is being written or displayed on the timeline.
This issue occurs due to four architectural and state-management gaps in the current implementation.
---
## 2. Detailed Root Causes
### Root Cause 1: Global Transport Record vs. Track Arm Disconnect
* **Observed State:** Track 01 has its individual Arm `[R]` button active (red indicator ON). However, the Global Transport Record button (red circle on the top toolbar) is inactive/stopped at time position `0:01.951`.
* **Technical Issue:** Arming a track only enables Live Monitoring (routing MIDI input to the virtual synth engine for real-time audio playback). Recording MIDI into timeline buffers requires both **Track Arm = `true**` AND **Transport Engine State = `RECORDING**`.
```text
[ Track Armed ] + [ Transport STOPPED ] --> Live Monitoring ONLY (VU meter lights up, no recording)
[ Track Armed ] + [ Transport RECORDING ] --> Live Monitoring + Event Buffer Write + Canvas Redraw
```
### Root Cause 2: Gate Condition in `handleMIDIMessage`
In the client recording engine (`ClientMIDIRecorder`), incoming MIDI events trigger live synth audio, but note recording is gated behind a transport flag:
```javascript
handleMIDIMessage(event) {
// BUG: If global transport is not in RECORD mode, execution stops here.
// Synth gets triggered elsewhere, but recordedNotes array remains empty.
if (!this.isRecording) return;
const [status, pitch, velocity] = event.data;
// ... logic to write to activeNotes and recordedNotes
}
```
### Root Cause 3: Absence of Real-Time Canvas Redraw Loop
For notes to render dynamically inside the MIDI Item clip as keys are pressed:
* The UI Canvas must run a `requestAnimationFrame` render loop while `isRecording === true`.
* The renderer must query the `activeNotes` Map (currently held keys) in addition to finalized `recordedNotes`.
* If the UI only renders on static session updates (e.g., when clicking or stopping transport), live notes will not appear on screen during playback.
### Root Cause 4: Track Target ID Unbound to Input Stream
If multiple tracks exist, `ClientMIDIRecorder` must know which `track_id` is currently armed and matched to device `MIDIIN2 (SE49)`. If events arrive without a target track context, they cannot be routed into the target `MIDIItem.source_data.notes` array.
---
## 3. Technical Solutions & Code Adjustments
### Step 1: Ensure Dual-Stage Recording State Verification
Update the transport control logic so pressing **Record + Play** on the top toolbar initializes active record buffers on all armed tracks:
```javascript
// Transport Controller
function startTransportRecording() {
const armedTracks = session.tracks.filter(t => t.is_armed);
if (armedTracks.length === 0) {
console.warn("No tracks armed for recording.");
startPlaybackOnly();
return;
}
// Activate global transport record state
transport.isRecording = true;
transport.isPlaying = true;
// Initialize temporary recording items on each armed track
armedTracks.forEach(track => {
const newRecordingItem = {
id: `rec_item_${Date.now()}`,
type: "MIDI_ITEM",
start_bar: transport.currentBar,
duration_bars: 0.1, // Expands dynamically during recording
clip_start_offset_bars: 0.0,
source_data: { total_buffer_bars: 8.0, notes: [] }
};
track.activeRecordingItem = newRecordingItem;
midiRecorder.start(track.id, transport.currentBar);
});
// Start UI animation loop for live waveform/note preview
requestAnimationFrame(renderLiveRecordingUI);
}
```
### Step 2: Live MIDI Note Binding & Duration Expansion
Update `ClientMIDIRecorder` to feed both the active buffer and the active recording clip:
```javascript
handleMIDIMessage(event) {
const [status, pitch, velocity] = event.data;
const command = status >> 4;
// 1. Always trigger Live Audio Preview (VU Meter + Synth Node)
this.triggerSynthPreview(pitch, velocity);
// 2. Gate recording buffer write behind global transport record state
if (!transport.isRecording || !this.targetTrack) return;
const currentBeat = this.calculateLatencyCompensatedBeat();
// Command 0x9: Note On
if (command === 0x9 && velocity > 0) {
const liveNote = {
id: `note_${Date.now()}_${pitch}`,
pitch: pitch,
start_beat: currentBeat,
duration_beats: 0.25, // Default initial length until Note Off
velocity: velocity / 127.0
};
this.activeNotes.set(pitch, liveNote);
this.targetTrack.activeRecordingItem.source_data.notes.push(liveNote);
}
// Command 0x8: Note Off
else if (command === 0x8 || (command === 0x9 && velocity === 0)) {
if (this.activeNotes.has(pitch)) {
const note = this.activeNotes.get(pitch);
note.duration_beats = Math.max(0.125, currentBeat - note.start_beat);
this.activeNotes.delete(pitch);
}
}
}
```
### Step 3: Real-Time UI Canvas Render Loop
Add real-time item length expansion and live note drawing on the main canvas during recording:
```javascript
function renderLiveRecordingUI() {
if (!transport.isRecording) return;
const currentBar = transport.getCurrentBarPosition();
session.tracks.forEach(track => {
if (track.is_armed && track.activeRecordingItem) {
const item = track.activeRecordingItem;
// Expand item duration on timeline as playhead moves forward
item.duration_bars = Math.max(0.5, currentBar - item.start_bar);
// Draw item bounding box and active/completed MIDI note rectangles
drawTimelineItem(trackCanvasCtx, item);
}
});
requestAnimationFrame(renderLiveRecordingUI);
}
```
---
## 4. Checklist to Fix in Your Application
* [ ] Check if clicking top toolbar **Record + Play** sets `transport.isRecording = true`.
* [ ] Verify that Track 01 generates a temporary `activeRecordingItem` on record start.
* [ ] Confirm `requestAnimationFrame` is re-rendering the canvas continuously while transport is moving.
* [ ] Ensure incoming MIDI events on `MIDIIN2 (SE49)` push notes into Track 01's item note array rather than just playing the synth.