From e97dc5695f2c1d6f6329e6d3aacd4f64a387c764 Mon Sep 17 00:00:00 2001 From: Skyler Lehmkuhl Date: Thu, 6 Nov 2025 09:12:48 -0500 Subject: [PATCH] draw midi input indicator --- daw-backend/src/io/midi_input.rs | 20 +++++++++++++ src/main.js | 22 ++++++++++++++ src/state.js | 2 ++ src/widgets.js | 50 ++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+) diff --git a/daw-backend/src/io/midi_input.rs b/daw-backend/src/io/midi_input.rs index d2498b2..4a5c496 100644 --- a/daw-backend/src/io/midi_input.rs +++ b/daw-backend/src/io/midi_input.rs @@ -74,6 +74,26 @@ impl MidiInputManager { // Get all available MIDI input ports let ports = midi_in.ports(); + // Get list of currently available device names + let mut available_devices = Vec::new(); + for port in &ports { + if let Ok(port_name) = midi_in.port_name(port) { + available_devices.push(port_name); + } + } + + // Remove disconnected devices from our connections list + { + let mut conns = connections.lock().unwrap(); + let before_count = conns.len(); + conns.retain(|conn| available_devices.contains(&conn.device_name)); + let after_count = conns.len(); + + if before_count != after_count { + println!("MIDI: Removed {} disconnected device(s)", before_count - after_count); + } + } + // Get list of already connected device names let connected_devices: Vec = { let conns = connections.lock().unwrap(); diff --git a/src/main.js b/src/main.js index 34fe61a..7e44a11 100644 --- a/src/main.js +++ b/src/main.js @@ -1306,6 +1306,28 @@ async function handleAudioEvent(event) { context.pianoRedraw(); } } + // Update MIDI activity timestamp + context.lastMidiInputTime = Date.now(); + console.log('[NoteOn] Set lastMidiInputTime to:', context.lastMidiInputTime); + + // Start animation loop to keep redrawing the MIDI indicator + if (!context.midiIndicatorAnimating) { + context.midiIndicatorAnimating = true; + const animateMidiIndicator = () => { + if (context.timelineWidget && context.timelineWidget.requestRedraw) { + context.timelineWidget.requestRedraw(); + } + + // Keep animating for 1 second after last MIDI input + const elapsed = Date.now() - context.lastMidiInputTime; + if (elapsed < 1000) { + requestAnimationFrame(animateMidiIndicator); + } else { + context.midiIndicatorAnimating = false; + } + }; + requestAnimationFrame(animateMidiIndicator); + } break; case 'NoteOff': diff --git a/src/state.js b/src/state.js index f287cc2..603bd6b 100644 --- a/src/state.js +++ b/src/state.js @@ -41,6 +41,8 @@ export let context = { recordingTrackId: null, recordingClipId: null, playPauseButton: null, // Reference to play/pause button for updating appearance + // MIDI activity indicator + lastMidiInputTime: 0, // Timestamp (Date.now()) of last MIDI input }; // Application configuration diff --git a/src/widgets.js b/src/widgets.js index ae2afeb..b5aeda8 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -799,6 +799,56 @@ class TimelineWindowV2 extends Widget { ctx.fillText(typeText, typeX, y + this.trackHierarchy.trackHeight / 2) } + + // Draw MIDI activity indicator for active MIDI track + if (track.type === 'audio' && track.object && track.object.type === 'midi') { + + if (this.context && this.context.lastMidiInputTime > 0) { + + // Check if this is the selected/active MIDI track + const isActiveMidiTrack = isSelected && track.object && track.object.audioTrackId !== undefined + + + if (isActiveMidiTrack) { + const elapsed = Date.now() - this.context.lastMidiInputTime + const fadeTime = 1000 // Fade out over 1 second (increased for visibility) + + if (elapsed < fadeTime) { + // const mindicatorSize = 12 // Made larger + // const mindicatorX = this.trackHeaderWidth - 35 // Position to the left of buttons + // const mindicatorY = y + this.trackHierarchy.trackHeight / 2 + + // console.log(`[MIDI mIndicator] Drawing at (${mindicatorX}, ${mindicatorY}) with alpha ${1}`) + + // // Draw pulsing circle with border + // ctx.strokeStyle = `rgba(0, 255, 0, ${1})` + // ctx.fillStyle = `rgba(0, 255, 0, ${1 * 0.5})` + // ctx.lineWidth = 2 + // ctx.beginPath() + // ctx.arc(mindicatorX, mindicatorY, mindicatorSize / 2, 0, Math.PI * 2) + // ctx.fill() + // ctx.stroke() + + const alpha = Math.max(0.2, 1 - (elapsed / fadeTime)) // Minimum alpha of 0.3 for visibility + const indicatorSize = 10 + const indicatorX = this.trackHeaderWidth - 35 // Position to the left of buttons + const indicatorY = y + this.trackHierarchy.trackHeight / 2 + + console.log(`[MIDI Indicator] Drawing at (${indicatorX}, ${indicatorY}) with alpha ${alpha}`) + + // Draw pulsing circle with border + ctx.strokeStyle = `rgba(0, 255, 0, ${alpha})` + ctx.fillStyle = `rgba(0, 255, 0, ${alpha})` + ctx.lineWidth = 2 + ctx.beginPath() + ctx.arc(indicatorX, indicatorY, indicatorSize / 2, 0, Math.PI * 2) + ctx.fill() + ctx.stroke() + } + } + } + } + // Draw toggle buttons for object/shape/audio/midi tracks (Phase 3) if (track.type === 'object' || track.type === 'shape' || track.type === 'audio' || track.type === 'midi') { const buttonSize = 14