From 0a900e981d27cec95d13f49d3a596ab746425784 Mon Sep 17 00:00:00 2001 From: Marco Bavagnoli Date: Wed, 22 Jan 2025 20:54:20 +0100 Subject: [PATCH 1/3] Limiter filter makeupGain->outputCeiling + attackTime --- .vscode/launch.json | 2 +- example/lib/filters/limiter.dart | 64 ++++++++++---- lib/src/filter_params.dart | 9 +- lib/src/filters/filters.dart | 11 ++- lib/src/filters/limiter.dart | 49 +++++++---- src/filters/limiter.cpp | 139 ++++++++++++++++++++----------- src/filters/limiter.h | 18 ++-- xiph/ogg | 1 + xiph/opus | 1 + 9 files changed, 197 insertions(+), 97 deletions(-) create mode 160000 xiph/ogg create mode 160000 xiph/opus diff --git a/.vscode/launch.json b/.vscode/launch.json index 72ab720a..1ceba2ed 100755 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,7 +8,7 @@ "name": "Flutter debug", "type": "dart", "request": "launch", - "program": "lib/main.dart", + "program": "lib/filters/limiter.dart", "flutterMode": "debug", "cwd": "${workspaceFolder}/example" }, diff --git a/example/lib/filters/limiter.dart b/example/lib/filters/limiter.dart index d7046342..9d101343 100644 --- a/example/lib/filters/limiter.dart +++ b/example/lib/filters/limiter.dart @@ -18,8 +18,8 @@ import 'package:logging/logging.dart'; /// - `wet`: Wet/dry mix ratio, 1.0 means fully wet, 0.0 means fully dry /// - `threshold`: The threshold in dB. Signals above this level are reduced /// in gain. A lower value means more aggressive limiting. -/// - `makeupGain`: The make-up gain in dB applied after limiting to bring up -/// the output level. +/// - `outputCeiling`: The maximum output level in dB (should be < 0dB to +/// prevent clipping) /// - `kneeWidth`: The width of the knee in dB. A larger value results in a /// softer transition into limiting. /// - `releaseTime`: The release time in milliseconds. Determines how quickly @@ -72,9 +72,10 @@ class _LimiterExampleState extends State { AudioSource? sound; late double wet; late double threshold; - late double makeupGain; + late double outputCeiling; late double kneeWidth; late double releaseTime; + late double attackTime; bool isFilterActive = false; @override @@ -83,9 +84,10 @@ class _LimiterExampleState extends State { wet = limiter.queryWet.def; threshold = limiter.queryThreshold.def; - makeupGain = limiter.queryMakeupGain.def; + outputCeiling = limiter.queryOutputCeiling.def; kneeWidth = limiter.queryKneeWidth.def; releaseTime = limiter.queryReleaseTime.def; + attackTime = limiter.queryAttackTime.def; } @override @@ -105,7 +107,11 @@ class _LimiterExampleState extends State { children: [ const Text( 'WARNING: lower the volume down!', - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.red, + ), ), Row( mainAxisSize: MainAxisSize.min, @@ -118,9 +124,10 @@ class _LimiterExampleState extends State { limiter.activate(); limiter.wet.value = wet; limiter.threshold.value = threshold; - limiter.makeupGain.value = makeupGain; + limiter.outputCeiling.value = outputCeiling; limiter.kneeWidth.value = kneeWidth; limiter.releaseTime.value = releaseTime; + limiter.attackTime.value = attackTime; } else { limiter.deactivate(); } @@ -143,11 +150,16 @@ class _LimiterExampleState extends State { /// ElevatedButton( onPressed: () { - SoLoud.instance.play(sound!, looping: true); - SoLoud.instance.play(sound!, looping: true); - SoLoud.instance.play(sound!, looping: true); - SoLoud.instance.play(sound!, looping: true); - SoLoud.instance.play(sound!, looping: true); + SoLoud.instance.play(sound!, looping: true, volume: 2); + SoLoud.instance.play(sound!, looping: true, volume: 2); + SoLoud.instance.play(sound!, looping: true, volume: 2); + SoLoud.instance.play(sound!, looping: true, volume: 2); + SoLoud.instance.play(sound!, looping: true, volume: 2); + SoLoud.instance.play(sound!, looping: true, volume: 2); + SoLoud.instance.play(sound!, looping: true, volume: 2); + SoLoud.instance.play(sound!, looping: true, volume: 2); + SoLoud.instance.play(sound!, looping: true, volume: 2); + SoLoud.instance.play(sound!, looping: true, volume: 2); }, child: const Text('play sound'), ), @@ -199,16 +211,16 @@ class _LimiterExampleState extends State { ), Row( children: [ - Text('Makeup gain ${makeupGain.toStringAsFixed(2)}'), + Text('Outpout ceiling ${outputCeiling.toStringAsFixed(2)}'), Expanded( child: Slider( - value: makeupGain, - min: limiter.queryMakeupGain.min, - max: limiter.queryMakeupGain.max, + value: outputCeiling, + min: limiter.queryOutputCeiling.min, + max: limiter.queryOutputCeiling.max, onChanged: (value) { setState(() { - makeupGain = value; - limiter.makeupGain.value = value; + outputCeiling = value; + limiter.outputCeiling.value = value; }); }, ), @@ -251,6 +263,24 @@ class _LimiterExampleState extends State { ), ], ), + Row( + children: [ + Text('Attack time ${attackTime.toStringAsFixed(2)}'), + Expanded( + child: Slider( + value: attackTime, + min: limiter.queryAttackTime.min, + max: limiter.queryAttackTime.max, + onChanged: (value) { + setState(() { + attackTime = value; + limiter.attackTime.value = value; + }); + }, + ), + ), + ], + ), ], ), ), diff --git a/lib/src/filter_params.dart b/lib/src/filter_params.dart index 689804ff..20f477ba 100644 --- a/lib/src/filter_params.dart +++ b/lib/src/filter_params.dart @@ -137,13 +137,14 @@ const FxParams fxLimiter = ( names: [ 'Wet', 'Threshold', - 'Makeup Gain', + 'Output Ceiling', 'Knee Width', 'Release Time', + 'Attack Time', ], - mins: [0, -60, -60, 0, 1], - maxs: [1, 0, 30, 30, 1000], - defs: [1, -6, 0, 2, 100], + mins: [0, -60, -60, 0, 1, 0.1], + maxs: [1, 0, 30, 0, 1000, 200], + defs: [1, -6, 0, -1, 100, 1], ); /// Compressor filter diff --git a/lib/src/filters/filters.dart b/lib/src/filters/filters.dart index 0398d793..f398fcf2 100644 --- a/lib/src/filters/filters.dart +++ b/lib/src/filters/filters.dart @@ -107,11 +107,14 @@ final class FiltersSingle { /// - `threshold`: The threshold in dB. Signals above this level are reduced /// in gain. A lower value means more aggressive limiting. /// - /// - `makeupGain`: The make-up gain in dB applied after limiting to bring up - /// the output level. + /// - `outputCeiling`: The maximum output level in dB (should be < 0dB to + /// prevent clipping) /// /// - `kneeWidth`: The width of the knee in dB. A larger value results in a /// softer transition into limiting. + /// + /// - `attackTime`: The attack time in milliseconds. Determines how quickly + /// the gain reduction recovers after a signal peaks above the threshold. /// /// - `releaseTime`: The release time in milliseconds. Determines how quickly /// the gain reduction recovers after a signal drops below the threshold. @@ -188,8 +191,8 @@ final class FiltersGlobal { /// - `threshold`: The threshold in dB. Signals above this level are reduced /// in gain. A lower value means more aggressive limiting. /// - /// - `makeupGain`: The make-up gain in dB applied after limiting to bring up - /// the output level. + /// - `outputCeiling`: The maximum output level in dB (should be < 0dB to + /// prevent clipping) /// /// - `kneeWidth`: The width of the knee in dB. A larger value results in a /// softer transition into limiting. diff --git a/lib/src/filters/limiter.dart b/lib/src/filters/limiter.dart index dcba1f7d..039fe178 100644 --- a/lib/src/filters/limiter.dart +++ b/lib/src/filters/limiter.dart @@ -7,13 +7,14 @@ import 'package:flutter_soloud/src/sound_hash.dart'; enum Limiter { wet, threshold, - makeupGain, + outputCeiling, kneeWidth, - releaseTime; + releaseTime, + attackTime; - final List _mins = const [0, -60, -60, 0, 1]; - final List _maxs = const [1, 0, 30, 30, 1000]; - final List _defs = const [1, -6, 0, 2, 100]; + final List _mins = const [0, -60, -60, 0, 1, 0.1]; + final List _maxs = const [1, 0, 0, 30, 1000, 200]; + final List _defs = const [1, -6, -1, 2, 100, 1]; double get min => _mins[index]; double get max => _maxs[index]; @@ -23,9 +24,10 @@ enum Limiter { String toString() => switch (this) { Limiter.wet => 'Wet', Limiter.threshold => 'Threshold', - Limiter.makeupGain => 'Makeup Gain', + Limiter.outputCeiling => 'Output Ceiling', Limiter.kneeWidth => 'Knee Width', Limiter.releaseTime => 'Release Time', + Limiter.attackTime => 'Attack Time', }; } @@ -35,9 +37,10 @@ abstract class _LimiterInternal extends FilterBase { Limiter get queryWet => Limiter.wet; Limiter get queryThreshold => Limiter.threshold; - Limiter get queryMakeupGain => Limiter.makeupGain; + Limiter get queryOutputCeiling => Limiter.outputCeiling; Limiter get queryKneeWidth => Limiter.kneeWidth; Limiter get queryReleaseTime => Limiter.releaseTime; + Limiter get queryAttackTime => Limiter.attackTime; } class LimiterSingle extends _LimiterInternal { @@ -59,12 +62,12 @@ class LimiterSingle extends _LimiterInternal { Limiter.threshold.max, ); - FilterParam makeupGain({SoundHandle? soundHandle}) => FilterParam( + FilterParam outputCeiling({SoundHandle? soundHandle}) => FilterParam( soundHandle, filterType, - Limiter.makeupGain.index, - Limiter.makeupGain.min, - Limiter.makeupGain.max, + Limiter.outputCeiling.index, + Limiter.outputCeiling.min, + Limiter.outputCeiling.max, ); FilterParam kneeWidth({SoundHandle? soundHandle}) => FilterParam( @@ -82,6 +85,14 @@ class LimiterSingle extends _LimiterInternal { Limiter.releaseTime.min, Limiter.releaseTime.max, ); + + FilterParam attackTime({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + Limiter.attackTime.index, + Limiter.attackTime.min, + Limiter.attackTime.max, + ); } class LimiterGlobal extends _LimiterInternal { @@ -103,12 +114,12 @@ class LimiterGlobal extends _LimiterInternal { Limiter.threshold.max, ); - FilterParam get makeupGain => FilterParam( + FilterParam get outputCeiling => FilterParam( null, filterType, - Limiter.makeupGain.index, - Limiter.makeupGain.min, - Limiter.makeupGain.max, + Limiter.outputCeiling.index, + Limiter.outputCeiling.min, + Limiter.outputCeiling.max, ); FilterParam get kneeWidth => FilterParam( @@ -126,4 +137,12 @@ class LimiterGlobal extends _LimiterInternal { Limiter.releaseTime.min, Limiter.releaseTime.max, ); + + FilterParam get attackTime => FilterParam( + null, + filterType, + Limiter.attackTime.index, + Limiter.attackTime.min, + Limiter.attackTime.max, + ); } diff --git a/src/filters/limiter.cpp b/src/filters/limiter.cpp index 2c44187c..3fbfe999 100644 --- a/src/filters/limiter.cpp +++ b/src/filters/limiter.cpp @@ -8,12 +8,13 @@ LimiterInstance::LimiterInstance(Limiter *aParent) { mParent = aParent; - initParams(5); + initParams(6); mParam[Limiter::WET] = aParent->mWet; mParam[Limiter::THRESHOLD] = aParent->mThreshold; - mParam[Limiter::MAKEUP_GAIN] = aParent->mMakeupGain; + mParam[Limiter::OUTPUT_CEILING] = aParent->mOutputCeiling; mParam[Limiter::KNEE_WIDTH] = aParent->mKneeWidth; mParam[Limiter::RELEASE_TIME] = aParent->mReleaseTime; + mParam[Limiter::ATTACK_TIME] = aParent->mAttackTime; } void LimiterInstance::filter( @@ -27,47 +28,69 @@ void LimiterInstance::filter( updateParams(aTime); const float threshold = std::pow(10.0f, mParam[Limiter::THRESHOLD] / 20.0f); // Convert threshold dB to linear - const float makeupGain = std::pow(10.0f, mParam[Limiter::MAKEUP_GAIN] / 20.0f); // Convert make-up gain dB to linear + const float outputCeiling = std::pow(10.0f, mParam[Limiter::OUTPUT_CEILING] / 20.0f); // Convert output ceiling dB to linear const float kneeWidth = mParam[Limiter::KNEE_WIDTH]; // In dB, defines the width of the knee - const float releaseTime = mParam[Limiter::RELEASE_TIME] / 1000.0f; // Convert release time to seconds + const float kneeStart = mParam[Limiter::THRESHOLD] - (kneeWidth / 2.0f); + const float kneeEnd = mParam[Limiter::THRESHOLD] + (kneeWidth / 2.0f); - // Variables for the release smoothing - float releaseCoef = std::exp(-1.0f / (aSamplerate * releaseTime)); - float gain = 1.0f; // Current gain adjustment for limiting + // Time constants + const float releaseTime = mParam[Limiter::RELEASE_TIME] / 1000.0f; // Convert release time to seconds + const float attackTime = mParam[Limiter::ATTACK_TIME] / 1000.0f; // Convert attack time to seconds + const float releaseCoef = std::exp(-1.0f / (aSamplerate * releaseTime)); + const float attackCoef = std::exp(-1.0f / (aSamplerate * attackTime)); + + // Initialize per-channel gain tracking if needed + if (mCurrentGain.size() != aChannels) { + mCurrentGain.resize(aChannels, 1.0f); + } // Process each channel for (unsigned int ch = 0; ch < aChannels; ++ch) { + float ¤tGain = mCurrentGain[ch]; + for (unsigned int sample = 0; sample < aSamples; ++sample) { unsigned int index = sample * aChannels + ch; // Index for the sample in the interleaved buffer // Get the sample value for this channel - float sampleValue = aBuffer[index]; + float input = aBuffer[index]; + float inputAbs = std::fabs(input); - // Calculate the level of the sample in linear scale - float level = std::fabs(sampleValue); + // Calculate input level in dB + float inputDB = 20.0f * std::log10(inputAbs + 1e-6f); - // Apply the knee function if within knee range - float reduction = 0.0f; - if (level > threshold) { - reduction = 1.0f; // No reduction if above threshold - } else if (level > threshold - kneeWidth) { - // Soft knee (compress gradually as the level approaches the threshold) - reduction = (level - (threshold - kneeWidth)) / kneeWidth; + // Calculate gain reduction + float targetGain = 1.0f; + if (inputDB > kneeEnd) { + // Full limiting above knee + float excess = inputDB - mParam[Limiter::OUTPUT_CEILING]; + targetGain = std::pow(10.0f, -excess / 20.0f); + } + else if (inputDB > kneeStart) { + // Soft knee zone + float kneePosition = (inputDB - kneeStart) / kneeWidth; + float excess = (inputDB - mParam[Limiter::OUTPUT_CEILING]) * kneePosition; + targetGain = std::pow(10.0f, -excess / 20.0f); } - // Apply release time for the smooth transition - gain = releaseCoef * (gain - reduction) + reduction; - - // Apply the makeup gain after limiting - float outputSample = sampleValue * gain * makeupGain; + // Smooth gain changes + if (targetGain < currentGain) { + // Attack phase + currentGain = attackCoef * currentGain + (1.0f - attackCoef) * targetGain; + } else { + // Release phase + currentGain = releaseCoef * currentGain + (1.0f - releaseCoef) * targetGain; + } - // Apply the wet/dry mix - aBuffer[index] = (mParam[Limiter::WET] * outputSample) + ((1.0f - mParam[Limiter::WET]) * sampleValue); + // Apply the gain and wet/dry mix + float outputSample = input * currentGain; + aBuffer[index] = (mParam[Limiter::WET] * outputSample) + + ((1.0f - mParam[Limiter::WET]) * input); } } - // printf("LimiterInstance::filter(%f, %f, %f, gain=%f)\n", + // printf("LimiterInstance::filter(%f, %f, %f, %f, gain=%f)\n", // mParam[Limiter::THRESHOLD], + // mParam[Limiter::OUTPUT_CEILING], // mParam[Limiter::KNEE_WIDTH], // mParam[Limiter::RELEASE_TIME], gain); } @@ -92,11 +115,11 @@ void LimiterInstance::setFilterParameter(unsigned int aAttributeId, float aValue return; mParam[Limiter::THRESHOLD] = aValue; break; - case Limiter::MAKEUP_GAIN: - if (aValue < mParent->getParamMin(Limiter::MAKEUP_GAIN) || - aValue > mParent->getParamMax(Limiter::MAKEUP_GAIN)) + case Limiter::OUTPUT_CEILING: + if (aValue < mParent->getParamMin(Limiter::OUTPUT_CEILING) || + aValue > mParent->getParamMax(Limiter::OUTPUT_CEILING)) return; - mParam[Limiter::MAKEUP_GAIN] = aValue; + mParam[Limiter::OUTPUT_CEILING] = aValue; break; case Limiter::KNEE_WIDTH: if (aValue < mParent->getParamMin(Limiter::KNEE_WIDTH) || @@ -110,6 +133,12 @@ void LimiterInstance::setFilterParameter(unsigned int aAttributeId, float aValue return; mParam[Limiter::RELEASE_TIME] = aValue; break; + case Limiter::ATTACK_TIME: + if (aValue < mParent->getParamMin(Limiter::ATTACK_TIME) || + aValue > mParent->getParamMax(Limiter::ATTACK_TIME)) + return; + mParam[Limiter::ATTACK_TIME] = aValue; + break; } mParamChanged |= 1 << aAttributeId; @@ -129,10 +158,10 @@ SoLoud::result Limiter::setParam(unsigned int aParamIndex, float aValue) return SoLoud::INVALID_PARAMETER; mThreshold = aValue; break; - case MAKEUP_GAIN: - if (aValue < getParamMin(MAKEUP_GAIN) || aValue > getParamMax(MAKEUP_GAIN)) + case OUTPUT_CEILING: + if (aValue < getParamMin(OUTPUT_CEILING) || aValue > getParamMax(OUTPUT_CEILING)) return SoLoud::INVALID_PARAMETER; - mMakeupGain = aValue; + mOutputCeiling = aValue; break; case KNEE_WIDTH: if (aValue < getParamMin(KNEE_WIDTH) || aValue > getParamMax(KNEE_WIDTH)) @@ -144,13 +173,18 @@ SoLoud::result Limiter::setParam(unsigned int aParamIndex, float aValue) return SoLoud::INVALID_PARAMETER; mReleaseTime = aValue; break; + case ATTACK_TIME: + if (aValue < getParamMin(ATTACK_TIME) || aValue > getParamMax(ATTACK_TIME)) + return SoLoud::INVALID_PARAMETER; + mAttackTime = aValue; + break; } return SoLoud::SO_NO_ERROR; } int Limiter::getParamCount() { - return 5; + return 6; } const char *Limiter::getParamName(unsigned int aParamIndex) @@ -161,12 +195,14 @@ const char *Limiter::getParamName(unsigned int aParamIndex) return "Wet"; case THRESHOLD: return "Threshold"; - case MAKEUP_GAIN: - return "Makeup Gain"; + case OUTPUT_CEILING: + return "Output Ceiling"; case KNEE_WIDTH: return "Knee Width"; case RELEASE_TIME: return "Release Time"; + case ATTACK_TIME: + return "Attack Time"; } return "Wet"; } @@ -181,15 +217,17 @@ float Limiter::getParamMax(unsigned int aParamIndex) switch (aParamIndex) { case WET: - return 1.f; + return 1.0f; case THRESHOLD: - return 0.f; - case MAKEUP_GAIN: - return 30.f; + return 0.0f; + case OUTPUT_CEILING: + return 0.0f; case KNEE_WIDTH: return 30.0f; case RELEASE_TIME: - return 1000.f; + return 1000.0f; + case ATTACK_TIME: + return 200.0f; } return 1; } @@ -199,15 +237,17 @@ float Limiter::getParamMin(unsigned int aParamIndex) switch (aParamIndex) { case WET: - return 0.f; + return 0.0f; case THRESHOLD: return -60.0f; - case MAKEUP_GAIN: - return -60.f; + case OUTPUT_CEILING: + return -60.0f; case KNEE_WIDTH: - return 0.f; + return 0.0f; case RELEASE_TIME: - return 1.f; + return 1.0f; + case ATTACK_TIME: + return 0.1f; } return 1; } @@ -215,10 +255,11 @@ float Limiter::getParamMin(unsigned int aParamIndex) Limiter::Limiter() { mWet = 1.0f; - mThreshold = -6.f; - mMakeupGain = 0.0f; - mKneeWidth = 2.0f; - mReleaseTime = 100.f; + mThreshold = -6.0f; + mOutputCeiling = -1.0f; + mKneeWidth = 6.0f; // Wider knee for smoother transition + mReleaseTime = 50.0f; // Faster release + mAttackTime = 1.0f; // Fast attack to catch peaks } SoLoud::FilterInstance *Limiter::createInstance() diff --git a/src/filters/limiter.h b/src/filters/limiter.h index f7ea7ef4..7c937cb9 100644 --- a/src/filters/limiter.h +++ b/src/filters/limiter.h @@ -2,12 +2,14 @@ #define LIMITER_H #include "soloud.h" +#include class Limiter; class LimiterInstance : public SoLoud::FilterInstance { Limiter *mParent; + std::vector mCurrentGain; // Store gain per channel public: virtual void filter( @@ -29,15 +31,17 @@ class Limiter : public SoLoud::Filter { WET = 0, THRESHOLD = 1, - MAKEUP_GAIN = 2, + OUTPUT_CEILING = 2, KNEE_WIDTH = 3, - RELEASE_TIME = 4 + RELEASE_TIME = 4, + ATTACK_TIME = 5 }; - float mWet; // Wet/dry mix ratio, 1.0 means fully wet, 0.0 means fully dry - float mThreshold; // The threshold in dB. Signals above this level are reduced in gain. A lower value means more aggressive limiting. - float mMakeupGain; // The make-up gain in dB applied after limiting to bring up the output level. - float mKneeWidth; // The width of the knee in dB. A larger value results in a softer transition into limiting. - float mReleaseTime; // The release time in milliseconds. Determines how quickly the gain reduction recovers after a signal drops below the threshold. + float mWet; // Wet/dry mix ratio, 1.0 means fully wet, 0.0 means fully dry + float mThreshold; // The threshold in dB. Signals above this level are reduced in gain. A lower value means more aggressive limiting. + float mOutputCeiling; // The maximum output level in dB (should be < 0dB to prevent clipping) + float mKneeWidth; // The width of the knee in dB. A larger value results in a softer transition into limiting. + float mReleaseTime; // The release time in milliseconds. Determines how quickly the gain reduction recovers after a signal drops below the threshold. + float mAttackTime; // Attack time in milliseconds virtual int getParamCount(); virtual const char *getParamName(unsigned int aParamIndex); diff --git a/xiph/ogg b/xiph/ogg new file mode 160000 index 00000000..db5c7a49 --- /dev/null +++ b/xiph/ogg @@ -0,0 +1 @@ +Subproject commit db5c7a49ce7ebda47b15b78471e78fb7f2483e22 diff --git a/xiph/opus b/xiph/opus new file mode 160000 index 00000000..7db26934 --- /dev/null +++ b/xiph/opus @@ -0,0 +1 @@ +Subproject commit 7db26934e4156597cb0586bb4d2e44dccdde1a59 From 4f970ce49410b25b651e8b74fb1e1cc78760ad8e Mon Sep 17 00:00:00 2001 From: Marco Bavagnoli Date: Wed, 22 Jan 2025 21:10:07 +0100 Subject: [PATCH 2/3] dart fix --- example/lib/main.dart | 1 - lib/src/filters/filters.dart | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index eb46fc91..3b7d4cad 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,4 +1,3 @@ -import 'dart:async'; import 'dart:developer' as dev; import 'package:flutter/foundation.dart'; diff --git a/lib/src/filters/filters.dart b/lib/src/filters/filters.dart index f398fcf2..7e22851c 100644 --- a/lib/src/filters/filters.dart +++ b/lib/src/filters/filters.dart @@ -112,7 +112,7 @@ final class FiltersSingle { /// /// - `kneeWidth`: The width of the knee in dB. A larger value results in a /// softer transition into limiting. - /// + /// /// - `attackTime`: The attack time in milliseconds. Determines how quickly /// the gain reduction recovers after a signal peaks above the threshold. /// From 5f3c3642e7e7393282ac3a9a72102f1e34aa7d0e Mon Sep 17 00:00:00 2001 From: Marco Bavagnoli Date: Wed, 22 Jan 2025 21:19:09 +0100 Subject: [PATCH 3/3] xiph dir shouldn't be there! --- xiph/ogg | 1 - xiph/opus | 1 - 2 files changed, 2 deletions(-) delete mode 160000 xiph/ogg delete mode 160000 xiph/opus diff --git a/xiph/ogg b/xiph/ogg deleted file mode 160000 index db5c7a49..00000000 --- a/xiph/ogg +++ /dev/null @@ -1 +0,0 @@ -Subproject commit db5c7a49ce7ebda47b15b78471e78fb7f2483e22 diff --git a/xiph/opus b/xiph/opus deleted file mode 160000 index 7db26934..00000000 --- a/xiph/opus +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7db26934e4156597cb0586bb4d2e44dccdde1a59