diff --git a/kernel/src/Drivers/Audio/IntelHda.cpp b/kernel/src/Drivers/Audio/IntelHda.cpp index d3968a8..0ef2fbc 100644 --- a/kernel/src/Drivers/Audio/IntelHda.cpp +++ b/kernel/src/Drivers/Audio/IntelHda.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace Drivers::Audio::IntelHda { @@ -24,6 +25,7 @@ namespace Drivers::Audio::IntelHda { // ========================================================================= static bool g_initialized = false; + static kcp::Spinlock g_codecLock; static volatile uint8_t* g_mmioBase = nullptr; static uint8_t g_bus, g_dev, g_func; @@ -199,16 +201,20 @@ namespace Drivers::Audio::IntelHda { // Send a verb and wait for the response static uint32_t CodecCommand(uint8_t codec, uint8_t nid, uint32_t verb) { + g_codecLock.Acquire(); + uint32_t fullVerb = ((uint32_t)codec << 28) | ((uint32_t)nid << 20) | verb; SendVerb(fullVerb); uint32_t response = 0; if (!ReadResponse(&response, nullptr)) { + g_codecLock.Release(); KernelLogStream(WARNING, "HDA") << "Verb timeout: codec=" << base::dec << (uint64_t)codec << " nid=" << (uint64_t)nid << " verb=" << base::hex << (uint64_t)verb; return 0; } + g_codecLock.Release(); return response; } @@ -652,6 +658,7 @@ namespace Drivers::Audio::IntelHda { // never runs and unsolicited responses (jack events) pile up unread. // This consumes them and sets g_jackEventPending if any are found. static void DrainUnsolicitedResponses() { + g_codecLock.Acquire(); for (;;) { uint16_t wp = Read16(REG_RIRBWP) & 0xFF; if (g_rirbReadPtr == wp) @@ -666,6 +673,7 @@ namespace Drivers::Audio::IntelHda { g_rirbReadPtr = next; g_jackEventPending = true; } + g_codecLock.Release(); } // Poll jack state; call when an unsolicited response fires. diff --git a/kernel/src/Drivers/Audio/Mixer.cpp b/kernel/src/Drivers/Audio/Mixer.cpp index 10a4851..fc764b3 100644 --- a/kernel/src/Drivers/Audio/Mixer.cpp +++ b/kernel/src/Drivers/Audio/Mixer.cpp @@ -69,6 +69,7 @@ namespace Drivers::Audio::Mixer { static int g_masterVolume = 80; static bool g_masterMute = false; static int g_activeCount = 0; + static uint64_t g_masterHwSeq = 0; // Monotonically increasing serial. Bumped (and waiters woken) on every // mutation of mixer state. Clients use it to detect changes without @@ -113,11 +114,34 @@ namespace Drivers::Audio::Mixer { if (!IntelHda::IsInitialized()) return false; g_hdaHandle = IntelHda::Open(MIX_RATE, MIX_CHANNELS, MIX_BITS); if (g_hdaHandle < 0) return false; - IntelHda::Control(g_hdaHandle, IntelHda::AUDIO_CTL_SET_VOLUME, g_masterMute ? 0 : g_masterVolume); + // Master volume is applied in software during mixdown. Keep the codec + // amp at unity and use it only as an immediate hard-mute gate. + IntelHda::Control(g_hdaHandle, IntelHda::AUDIO_CTL_SET_VOLUME, g_masterMute ? 0 : 100); g_hdaOpened = true; return true; } + static void SyncHdaMasterMute() { + for (;;) { + g_lock.Acquire(); + bool hdaOpen = g_hdaOpened; + int handle = g_hdaHandle; + bool muted = g_masterMute; + uint64_t seq = g_masterHwSeq; + g_lock.Release(); + + if (!hdaOpen) return; + + IntelHda::Control(handle, IntelHda::AUDIO_CTL_SET_VOLUME, + muted ? 0 : 100); + + g_lock.Acquire(); + bool done = (seq == g_masterHwSeq); + g_lock.Release(); + if (done) return; + } + } + // Convert one chunk of raw input bytes from a stream into int16 stereo at // the stream's native rate, then push into its ring. Caller holds g_lock. // Returns the number of *input bytes* successfully ingested. @@ -157,9 +181,28 @@ namespace Drivers::Audio::Mixer { // ========================================================================= // // Compute the number of frames we can safely write to the HDA DMA buffer - // (free space in the ring, minus a small guard), then for each active - // stream resample and mix it into the scratch buffer. Finally hand the - // result to IntelHda::Write(). + // (free space in the ring, minus a small guard), then cap that to the + // amount of stream data actually available. Muted and zero-volume streams + // still advance their read cursors so they cannot build up stale audio. + // Finally hand the result to IntelHda::Write(). + + static uint32_t ProducibleFrames(const VirtualStream& s, uint32_t maxFrames) { + if (!s.active || s.paused) return 0; + + uint32_t available = s.writeFrame - s.readFrame; + if (available < 2) return 0; + + uint64_t pos = s.posQ32; + uint64_t step = s.stepQ32; + uint32_t frames = 0; + while (frames < maxFrames) { + uint64_t intPart = pos >> 32; + if (intPart + 1 >= available) break; + frames++; + pos += step; + } + return frames; + } static void Pump() { if (!g_hdaOpened) return; @@ -175,11 +218,27 @@ namespace Drivers::Audio::Mixer { if (frames == 0) return; if (frames > MAX_PUMP_FRAMES) frames = MAX_PUMP_FRAMES; - // Always write to HDA, even when no streams are active, so the - // hardware ring stays filled with silence instead of looping the - // last mixed audio. Without this, closing the last app would leave - // its tail playing on repeat. - if (g_activeCount == 0) { + uint32_t streamFrames = 0; + bool hasUnpaused = false; + bool hasAudible = false; + for (int i = 0; i < MAX_STREAMS; i++) { + VirtualStream& s = g_streams[i]; + if (!s.active || s.paused) continue; + hasUnpaused = true; + if (!s.muted && s.volume > 0) hasAudible = true; + uint32_t n = ProducibleFrames(s, frames); + if (n > streamFrames) streamFrames = n; + } + + // Write silence intentionally when there is no stream data to mix: + // either the mixer is idle, every stream is paused/muted/silent, or an + // audible producer has genuinely under-run. When data exists, write no + // more than the longest available stream window so we don't append a + // large zero tail after a small real-time write. + bool silenceOnly = (streamFrames == 0); + if (!silenceOnly) { + frames = streamFrames; + } else if (g_activeCount == 0 || !hasAudible || !hasUnpaused) { memset(g_outScratch, 0, frames * 2 * sizeof(int16_t)); IntelHda::Write(g_hdaHandle, (const uint8_t*)g_outScratch, frames * 4); return; @@ -190,17 +249,18 @@ namespace Drivers::Audio::Mixer { for (int i = 0; i < MAX_STREAMS; i++) { VirtualStream& s = g_streams[i]; - if (!s.active || s.paused || s.muted || s.volume == 0) continue; + if (!s.active || s.paused) continue; // posQ32 is the fractional read cursor; integer part is the index // of the *next* input frame to consume. uint64_t pos = s.posQ32; uint64_t step = s.stepQ32; uint32_t available = s.writeFrame - s.readFrame; - if (available == 0) continue; + if (available < 2) continue; // Per-stream gain in Q15 (post-master mix). int32_t gain = (int32_t)s.volume; // 0..100 + bool emit = !s.muted && gain > 0; for (uint32_t f = 0; f < frames; f++) { uint64_t intPart = pos >> 32; @@ -221,12 +281,14 @@ namespace Drivers::Audio::Mixer { int32_t l = (l0 * inv + l1 * frac) >> 16; int32_t r = (r0 * inv + r1 * frac) >> 16; - // Apply per-stream volume. - l = (l * gain) / 100; - r = (r * gain) / 100; + if (emit) { + // Apply per-stream volume. + l = (l * gain) / 100; + r = (r * gain) / 100; - g_mixScratch[f * 2 + 0] += l; - g_mixScratch[f * 2 + 1] += r; + g_mixScratch[f * 2 + 0] += l; + g_mixScratch[f * 2 + 1] += r; + } pos += step; } @@ -456,25 +518,12 @@ namespace Drivers::Audio::Mixer { if (percent < 0) percent = 0; if (percent > 100) percent = 100; - // Take the mixer lock just long enough to update software state and - // capture a snapshot for the HW write. The IntelHda codec command - // path busy-waits on the RIRB for a few hundred microseconds, so - // doing it inside the lock would freeze interrupts (BCIS, scheduler, - // input) for the whole duration of every drag tick — visible as - // slider lag. g_lock.Acquire(); bool changed = (g_masterVolume != percent); g_masterVolume = percent; - int curVol = g_masterVolume; - bool curMute = g_masterMute; - bool hdaOpen = g_hdaOpened; if (changed) BumpSerialLocked(); g_lock.Release(); - if (changed && hdaOpen) { - IntelHda::Control(g_hdaHandle, IntelHda::AUDIO_CTL_SET_VOLUME, - curMute ? 0 : curVol); - } if (changed) Sched::WakeObjectWaiters((void*)&g_serial); } @@ -486,16 +535,13 @@ namespace Drivers::Audio::Mixer { g_lock.Acquire(); bool changed = (g_masterMute != muted); g_masterMute = muted; - int curVol = g_masterVolume; - bool curMute = g_masterMute; - bool hdaOpen = g_hdaOpened; - if (changed) BumpSerialLocked(); + if (changed) { + g_masterHwSeq++; + BumpSerialLocked(); + } g_lock.Release(); - if (changed && hdaOpen) { - IntelHda::Control(g_hdaHandle, IntelHda::AUDIO_CTL_SET_VOLUME, - curMute ? 0 : curVol); - } + if (changed) SyncHdaMasterMute(); if (changed) Sched::WakeObjectWaiters((void*)&g_serial); } diff --git a/programs/src/desktop/input.cpp b/programs/src/desktop/input.cpp index db2158e..56e1644 100644 --- a/programs/src/desktop/input.cpp +++ b/programs/src/desktop/input.cpp @@ -399,7 +399,10 @@ void gui::desktop_handle_mouse(DesktopState* ds) { int v = ((mx - slider_abs_x) * 100) / (200 - 32); if (v < 0) v = 0; if (v > 100) v = 100; - ds->vol_muted = false; + if (ds->vol_muted) { + ds->vol_muted = false; + montauk::audio_set_master_mute(false); + } ds->vol_level = v; montauk::audio_set_master_volume(v); return; @@ -420,7 +423,10 @@ void gui::desktop_handle_mouse(DesktopState* ds) { int v = ((mx - slider_abs_x) * 100) / slider_w; if (v < 0) v = 0; if (v > 100) v = 100; - ds->vol_muted = false; + if (ds->vol_muted) { + ds->vol_muted = false; + montauk::audio_set_master_mute(false); + } ds->vol_level = v; montauk::audio_set_master_volume(v); ds->vol_dragging = true; @@ -437,7 +443,10 @@ void gui::desktop_handle_mouse(DesktopState* ds) { if (my >= btn_y && my < btn_y + btn_h) { // [-] if (mx >= bx && mx < bx + minus_w) { - ds->vol_muted = false; + if (ds->vol_muted) { + ds->vol_muted = false; + montauk::audio_set_master_mute(false); + } int v = ds->vol_level - 5; if (v < 0) v = 0; ds->vol_level = v; @@ -447,7 +456,10 @@ void gui::desktop_handle_mouse(DesktopState* ds) { bx += minus_w + gap; // [+] if (mx >= bx && mx < bx + plus_w) { - ds->vol_muted = false; + if (ds->vol_muted) { + ds->vol_muted = false; + montauk::audio_set_master_mute(false); + } int v = ds->vol_level + 5; if (v > 100) v = 100; ds->vol_level = v;