From c7d470da2f0e3c1822173f159ea68351aec9be81 Mon Sep 17 00:00:00 2001 From: zeph Date: Wed, 8 Apr 2026 15:53:50 +0200 Subject: [PATCH] fix(waveform): legal notices and reformatting This commit effectively grants [the "Atoll" project](https://github.com/Ebullioscopic/Atoll) permission from [ZephyrCodesStuff](https://github.com/ZephyrCodesStuff) for using, modifying and distributing the source code contained in [`rtaudio`](https://github.com/ZephyrCodesStuff/rtaudio) in compliance with the terms of the GPLv3 license, as an alternative to its original AGPLv3 license for this specific integration. --- DynamicIsland/audio/AudioBridge.h | 16 +- DynamicIsland/audio/AudioBridge.mm | 16 +- DynamicIsland/audio/AudioProcessor.cpp | 132 ++++++++++++++++- DynamicIsland/audio/AudioProcessor.hpp | 138 +++++------------- DynamicIsland/audio/AudioTap.swift | 14 +- DynamicIsland/audio/AudioVisualizerView.swift | 3 + .../audio/RealTimeAudioSpectrum.swift | 14 +- 7 files changed, 214 insertions(+), 119 deletions(-) diff --git a/DynamicIsland/audio/AudioBridge.h b/DynamicIsland/audio/AudioBridge.h index 2f17375..0134d0d 100644 --- a/DynamicIsland/audio/AudioBridge.h +++ b/DynamicIsland/audio/AudioBridge.h @@ -1,14 +1,22 @@ /* * Atoll (DynamicIsland) - * Copyright (C) 2024-2026 Atoll Contributors and zeph - * Adapted from rtaudio (https://github.com/ZephyrCodesStuff/rtaudio) + * Original work Copyright (C) 2026 ZephyrCodesStuff (https://github.com/ZephyrCodesStuff/rtaudio) + * Modified work Copyright (C) 2026 Atoll Contributors * * Objective-C bridge to expose C++ AudioProcessor to Swift. * * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by + * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ #import @@ -19,8 +27,6 @@ NS_ASSUME_NONNULL_BEGIN @interface AudioBridge : NSObject - (void)processBuffer:(const float *)buffer count:(int)count; - -// Reads atomically from the processor — safe to call from any thread - (simd_float4)getSmoothedMagnitudes; @end diff --git a/DynamicIsland/audio/AudioBridge.mm b/DynamicIsland/audio/AudioBridge.mm index e3456f9..6998f5c 100644 --- a/DynamicIsland/audio/AudioBridge.mm +++ b/DynamicIsland/audio/AudioBridge.mm @@ -1,14 +1,22 @@ /* * Atoll (DynamicIsland) - * Copyright (C) 2024-2026 Atoll Contributors and zeph - * Adapted from rtaudio (https://github.com/ZephyrCodesStuff/rtaudio) + * Original work Copyright (C) 2026 ZephyrCodesStuff (https://github.com/ZephyrCodesStuff/rtaudio) + * Modified work Copyright (C) 2026 Atoll Contributors * * Objective-C++ implementation bridging AudioProcessor to Swift. * * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by + * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ #import "AudioBridge.h" @@ -31,8 +39,6 @@ } - (simd_float4)getSmoothedMagnitudes { - // Calls getBand() which does memory_order_relaxed atomic loads — - // no heap allocation, safe to call from the render thread return simd_make_float4( processor->getBand(0), processor->getBand(1), diff --git a/DynamicIsland/audio/AudioProcessor.cpp b/DynamicIsland/audio/AudioProcessor.cpp index 0f9f9d0..eaf3ce2 100644 --- a/DynamicIsland/audio/AudioProcessor.cpp +++ b/DynamicIsland/audio/AudioProcessor.cpp @@ -1,11 +1,141 @@ /* * Atoll (DynamicIsland) - * Copyright (C) 2024-2026 Atoll Contributors + * Original work Copyright (C) 2026 ZephyrCodesStuff (https://github.com/ZephyrCodesStuff/rtaudio) + * Modified work Copyright (C) 2026 Atoll Contributors + * + * Real-time audio processor using Accelerate framework for efficient RMS + * and biquad filtering. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ #include "AudioProcessor.hpp" + +#include +#include +#include +#include + +AudioProcessor::AudioProcessor( float sr ) : sampleRate( sr ) +{ + // Single biquad per band + setupBiquad( 0, FilterType::LowPass, 250.0f, 0.707f ); + setupBiquad( 1, FilterType::BandPass, 707.0f, 0.40f ); + setupBiquad( 2, FilterType::BandPass, 3464.0f, 0.85f ); + setupBiquad( 3, FilterType::HighPass, 6000.0f, 0.707f ); +} + +AudioProcessor::~AudioProcessor() +{ + for ( int i = 0; i < kBands; ++i ) + { + vDSP_biquad_DestroySetup( setups[i] ); + } +} + +void AudioProcessor::process( const float* __restrict__ buffer, + int totalSamples ) +{ + if ( __builtin_expect( totalSamples <= 0, 0 ) ) + { + return; + } + + const float* src = buffer; + int remaining = totalSamples; + + while ( remaining > 0 ) + { + int toCopy = std::min( remaining, kBlockSize - writePos ); + memcpy( mono + writePos, src, toCopy * sizeof( float ) ); + writePos += toCopy; + src += toCopy; + remaining -= toCopy; + + if ( writePos >= kBlockSize ) + { + processBlock(); + writePos = 0; + } + } +} + +float AudioProcessor::getBand( int i ) const +{ + return bandParams[i].load( std::memory_order_relaxed ); +} + +void AudioProcessor::processBlock() +{ + for ( int i = 0; i < kBands; ++i ) + { + vDSP_biquad( setups[i], delays[i], mono, 1, filtered, 1, kBlockSize ); + + // RMS over the block — more stable than peak for driving animation + float rms = 0.0f; + vDSP_rmsqv( filtered, 1, &rms, kBlockSize ); + + if ( __builtin_expect( !std::isfinite( rms ), 0 ) ) + { + rms = 0.0f; + } + + float boostedRms = rms * kGains[i]; + boostedRms = std::min( boostedRms, 1.0f ); + + // Asymmetric envelope: attack fast, release slow + float prev = envelopes[i]; + envelopes[i] = ( boostedRms > prev ) + ? prev * ( 1.0f - kAttack ) + boostedRms * kAttack + : prev * ( 1.0f - kRelease ) + boostedRms * kRelease; + + bandParams[i].store( envelopes[i], std::memory_order_relaxed ); + } +} + +void AudioProcessor::setupBiquad( int idx, FilterType type, float freq, + float q ) +{ + const double w0 = 2.0 * M_PI * freq / sampleRate; + const double sinw = std::sin( w0 ); + const double cosw = std::cos( w0 ); + const double alpha = sinw / ( 2.0 * q ); + + double b0, b1, b2; + const double a0 = 1.0 + alpha; + const double a1 = -2.0 * cosw; + const double a2 = 1.0 - alpha; + + switch ( type ) + { + case FilterType::LowPass: + b0 = ( 1.0 - cosw ) * 0.5; + b1 = 1.0 - cosw; + b2 = b0; + break; + case FilterType::HighPass: + b0 = ( 1.0 + cosw ) * 0.5; + b1 = -( 1.0 + cosw ); + b2 = b0; + break; + case FilterType::BandPass: + b0 = alpha; + b1 = 0.0; + b2 = -alpha; + break; + } + + const double coeffs[5] = { b0 / a0, b1 / a0, b2 / a0, a1 / a0, a2 / a0 }; + setups[idx] = vDSP_biquad_CreateSetup( coeffs, 1 ); +} diff --git a/DynamicIsland/audio/AudioProcessor.hpp b/DynamicIsland/audio/AudioProcessor.hpp index 72d40f1..a429613 100644 --- a/DynamicIsland/audio/AudioProcessor.hpp +++ b/DynamicIsland/audio/AudioProcessor.hpp @@ -1,134 +1,68 @@ /* * Atoll (DynamicIsland) - * Copyright (C) 2024-2026 Atoll Contributors and zeph + * Original work Copyright (C) 2026 ZephyrCodesStuff (https://github.com/ZephyrCodesStuff/rtaudio) + * Modified work Copyright (C) 2026 Atoll Contributors * - * Real-time audio processor using Accelerate framework for efficient FFT - * and band filtering. Adapted from rtaudio project (https://github.com/ZephyrCodesStuff/rtaudio) + * Real-time audio processor using Accelerate framework for efficient RMS + * and biquad filtering. * * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by + * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ -#ifndef AudioProcessor_hpp -#define AudioProcessor_hpp +#ifndef AUDIOPROCESSOR_HPP +#define AUDIOPROCESSOR_HPP #include #include -#include class AudioProcessor { -private: - static constexpr int kBlockSize = 512; // lower latency than 1024 - static constexpr int kBands = 4; - static constexpr float kAttack = 0.85f; // fast rise - static constexpr float kRelease = 0.40f; // slower fall — more musical - static constexpr float kGains[kBands] = {3.5f, 6.0f, 9.0f, 20.0f}; + private: + static constexpr int kBlockSize = 512; + static constexpr int kBands = 4; + static constexpr float kAttack = 0.85f; // fast rise + static constexpr float kRelease = 0.40f; // slower fall — more musical + static constexpr float kGains[kBands] = { 3.5f, 6.0f, 9.0f, 20.0f }; - float sampleRate; - int writePos = 0; - float mono[kBlockSize] = {}; - float filtered[kBlockSize] = {}; + float sampleRate; + int writePos = 0; + float mono[kBlockSize] = {}; + float filtered[kBlockSize] = {}; vDSP_biquad_Setup setups[kBands]; - alignas(16) float delays[kBands][4] = {}; + alignas( 16 ) float delays[kBands][4] = {}; float envelopes[kBands] = {}; // Lock-free: audio thread writes, render thread reads // One atomic per band — fits in a cache line - alignas(64) std::atomic bandParams[kBands] = {}; + alignas( 64 ) std::atomic< float > bandParams[kBands] = {}; -public: - explicit AudioProcessor(float sr = 48000.0f) : sampleRate(sr) { - // Single biquad per band — just enough separation, minimal CPU - setupBiquad(0, FilterType::LowPass, 250.0f, 0.707f); - setupBiquad(1, FilterType::BandPass, 707.0f, 0.40f); - setupBiquad(2, FilterType::BandPass, 3464.0f, 0.85f); - setupBiquad(3, FilterType::HighPass, 6000.0f, 0.707f); - } - - ~AudioProcessor() { - for (int i = 0; i < kBands; ++i) - vDSP_biquad_DestroySetup(setups[i]); - } + public: + explicit AudioProcessor( float sr = 48000.0f ); + ~AudioProcessor(); // Called by CoreAudio — must be real-time safe (no alloc, no locks) - void process(const float* __restrict__ buffer, int totalSamples) { - if (__builtin_expect(totalSamples <= 0, 0)) return; - - const float* src = buffer; - int remaining = totalSamples; - - while (remaining > 0) { - int toCopy = std::min(remaining, kBlockSize - writePos); - memcpy(mono + writePos, src, toCopy * sizeof(float)); - writePos += toCopy; - src += toCopy; - remaining -= toCopy; - - if (writePos >= kBlockSize) { - processBlock(); - writePos = 0; - } - } - } + void process( const float* __restrict__ buffer, int totalSamples ); // Called by render thread — lock-free read - float getBand(int i) const { - return bandParams[i].load(std::memory_order_relaxed); - } + float getBand( int i ) const; -private: + private: enum class FilterType { LowPass, BandPass, HighPass }; - void processBlock() { - for (int i = 0; i < kBands; ++i) { - vDSP_biquad(setups[i], delays[i], mono, 1, filtered, 1, kBlockSize); - - // RMS over the block — more stable than peak for driving animation - float rms = 0.0f; - vDSP_rmsqv(filtered, 1, &rms, kBlockSize); - - if (__builtin_expect(!std::isfinite(rms), 0)) rms = 0.0f; - - float boostedRms = rms * kGains[i]; - boostedRms = std::min(boostedRms, 1.0f); - - // Asymmetric envelope: attack fast, release slow - float prev = envelopes[i]; - envelopes[i] = (boostedRms > prev) - ? prev * (1.0f - kAttack) + boostedRms * kAttack - : prev * (1.0f - kRelease) + boostedRms * kRelease; - - bandParams[i].store(envelopes[i], std::memory_order_relaxed); - } - } - - void setupBiquad(int idx, FilterType type, float freq, float q) { - const double w0 = 2.0 * M_PI * freq / sampleRate; - const double sinw = std::sin(w0); - const double cosw = std::cos(w0); - const double alpha = sinw / (2.0 * q); - - double b0, b1, b2; - const double a0 = 1.0 + alpha; - const double a1 = -2.0 * cosw; - const double a2 = 1.0 - alpha; - - switch (type) { - case FilterType::LowPass: - b0 = (1.0 - cosw) * 0.5; b1 = 1.0 - cosw; b2 = b0; break; - case FilterType::HighPass: - b0 = (1.0 + cosw) * 0.5; b1 = -(1.0 + cosw); b2 = b0; break; - case FilterType::BandPass: - b0 = alpha; b1 = 0.0; b2 = -alpha; break; - } - - const double coeffs[5] = { b0/a0, b1/a0, b2/a0, a1/a0, a2/a0 }; - setups[idx] = vDSP_biquad_CreateSetup(coeffs, 1); - } + void processBlock(); + void setupBiquad( int idx, FilterType type, float freq, float q ); }; -#endif // !AudioProcessor_hpp +#endif // AUDIOPROCESSOR_HPP diff --git a/DynamicIsland/audio/AudioTap.swift b/DynamicIsland/audio/AudioTap.swift index 60613d4..7d4f2e3 100644 --- a/DynamicIsland/audio/AudioTap.swift +++ b/DynamicIsland/audio/AudioTap.swift @@ -1,15 +1,23 @@ /* * Atoll (DynamicIsland) - * Copyright (C) 2024-2026 Atoll Contributors and zeph + * Original work Copyright (C) 2026 ZephyrCodesStuff (https://github.com/ZephyrCodesStuff/rtaudio) + * Modified work Copyright (C) 2026 Atoll Contributors * * CoreAudio tap for capturing real-time audio from music applications. * Uses macOS 14.2+ Process Tap API for efficient audio capture. - * Adapted from rtaudio project (https://github.com/ZephyrCodesStuff/rtaudio) * * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by + * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ import AppKit diff --git a/DynamicIsland/audio/AudioVisualizerView.swift b/DynamicIsland/audio/AudioVisualizerView.swift index 48fa1f1..3433382 100644 --- a/DynamicIsland/audio/AudioVisualizerView.swift +++ b/DynamicIsland/audio/AudioVisualizerView.swift @@ -9,6 +9,9 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ import SwiftUI diff --git a/DynamicIsland/audio/RealTimeAudioSpectrum.swift b/DynamicIsland/audio/RealTimeAudioSpectrum.swift index fb6688f..407bc4e 100644 --- a/DynamicIsland/audio/RealTimeAudioSpectrum.swift +++ b/DynamicIsland/audio/RealTimeAudioSpectrum.swift @@ -1,14 +1,22 @@ /* * Atoll (DynamicIsland) - * Copyright (C) 2024-2026 Atoll Contributors and zeph + * Original work Copyright (C) 2026 ZephyrCodesStuff (https://github.com/ZephyrCodesStuff/rtaudio) + * Modified work Copyright (C) 2026 Atoll Contributors * * Real-time audio spectrum visualization using CoreAudio tap data. - * Adapted from rtaudio project (https://github.com/ZephyrCodesStuff/rtaudio) * * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by + * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ import AppKit