alnitak / flutter_soloud

Flutter low-level audio plugin using SoLoud C++ library and FFI
MIT License
197 stars 19 forks source link

feat: make filters easier to use #108

Closed alnitak closed 2 weeks ago

alnitak commented 1 month ago

Description

As for now methods like setFilterParameter() have parameters like int attributeId which identifies the index number to the filter parameter. This is difficult to choose since parameters are stored in records for each filter. For example, Freeverb parameters are stored in fxFreeverb in filter_params.dart and are accessible only by knowing the index and reading the code.

Would be nice to have classes easily accessible to get parameter names, mins, maxs, etc for each filter.

alnitak commented 1 month ago

I was thinking of something like this:

code
```dart sealed class Filter { const Filter( this.filterType, this.filterName, this.numParameters, this.parameterNames, this.mins, this.maxs, this.defs, ); final FilterType filterType; final String filterName; final int numParameters; final List parameterNames; final List mins; final List maxs; final List defs; FilterType get getfilterType => filterType; String get getfilterName => filterName; int get getNumParameters => numParameters; String getParameterName(int id) => parameterNames[id]; double getMin(int id) => mins[id]; double getMax(int id) => maxs[id]; double getDef(int id) => defs[id]; } final class FreeverbFilter extends Filter { FreeverbFilter() : super( FilterType.freeverbFilter, 'Freeverb', 5, ['Wet', 'Freeze', 'Room Size', 'Damp', 'Width'], [0, 0, 0, 0, 0], [1, 1, 1, 1, 1], [1, 0, 0.5, 0.5, 1], ); static int wet = 0; static int freeze = 1; static int roomSize = 2; static int damp = 3; static int width = 4; } [ ... other filter classes ] ```

then setFilterParameter() could be called as:

code
```dart SoLoud.setFilterParameter( FreeverbFilter().filterType, FreeverbFilter.damp, 0.5, ); ```

The first parameter could be changed to be of type Filter. This will permit us to get rid of the FilterType enum (flagging it as internal) but lead us to a breaking change. The use of Filter could make sense also for these calls:

code
```dart SoLoud.addGlobalFilter( FreeverbFilter() ); SoLoud.removeGlobalFilter( FreeverbFilter() ); SoLoud.isFilterActive( FreeverbFilter() ); // and also for the coming single audio filters: sound.addFilter( FreeverbFilter() ); // ... and so on ```

What are your thoughts about this @filiph?

filiph commented 1 month ago

Hmm, let me think about this. Since there are always at most 1 instances of any type of filter, it feels a bit weird to "add" filters.

I need more time to elaborate, but maybe something like this could work better?

// Global.
SoLoud.instance.filters.reverb.wet = 0.6;
SoLoud.instance.filters.reverb.activate();

// Single audio voice.
sound.filters.echo.activate();

Basically, there's a (lazily initialized) Filters member on both SoLoud and on sounds, and it gives access to the filters.

My thinking is that, since there's unlikely to be a lot of movement in the set of available filters, we can build the N filter classes with ergonomic APIs.

alnitak commented 1 month ago

I ended up using one enhanced enum for each filter type. This could be useful for pattern matching for example. The enums look like this:

enum PitchShiftFilter {
  wet,
  shift,
  semitones;

  final List<String> _parameterNames = const ['Wet', 'Shift', 'Semitones'];
  final List<double> _mins = const [0, 0, -48];
  final List<double> _maxs = const [1, 3, 48];
  final List<double> _defs = const [1, 1, 0];

  String parameterName() => _parameterNames[index];
  double min() => _mins[index];
  double max() => _maxs[index];
  double def() => _defs[index];
  int get attributeId => index;
}

These make it easier to access single-parameter values.

Basically, there's a (lazily initialized) Filters member on both SoLoud and on sounds, and it gives access to the filters.

this sounds great! This implies a breaking change since the filter methods will be moved to the Filters class, but I think this new logic worth it?

misterfourtytwo commented 1 week ago

@alnitak, How do you now track filter statuses between hot restarts? i.e. I'm following codelab from google io 2024, and there were a line that we could check status with isFilterActive(filter) method, which is now deprecated.

alnitak commented 1 week ago

@misterfourtytwo I opened a new issue for that! Thank you.