alnitak / flutter_soloud

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

use case: metronome #100

Closed lukepighetti closed 2 months ago

lukepighetti commented 3 months ago

I have yet to find a flutter package that can keep time accurately enough for use as a metronome. I think we should add such an example to flutter_soloud to both ensure the package maintains best-in-industry playback scheduling accuracy, but to also show people what techniques are needed to play with accuracy

alnitak commented 3 months ago

Hi @lukepighetti, thanks for your feedback!

Some brief examples are expected soon. We will open an issue to ask which one could be more relevant for the devs. Meanwhile, I would refer you to my reply of #99 regarding this issue.

lukepighetti commented 3 months ago

do you have any immediate recommendations for how to get the best metronome like performance out of flutter_soloud?

alnitak commented 3 months ago

do you have any immediate recommendations for how to get the best metronome like performance out of flutter_soloud?

IIRC a good choice is to set a buffer of 512 (instead of the default 2048) here. Of course, there is some troubles because you need to clone this repo locally and point it to your app's pubspec.yaml after modifying player.cpp.

I also think you could try using the SoLoud.play([your-short-metronome-sound]) inside a Timer.periodic().

Let me know!

alnitak commented 2 months ago

I tried to make a metronome using a buffer of 512 and since the web platform is almost ready, I made the example available here.

I tried something like this: ```dart void main() async { WidgetsFlutterBinding.ensureInitialized(); /// Initialize the player await SoLoud.instance.init(); runApp( const MaterialApp( home: Metronome(), ), ); } class Metronome extends StatefulWidget { const Metronome({super.key}); @override State createState() => _MetronomeState(); } class _MetronomeState extends State { /// delay between ticks. final delay = ValueNotifier(100); /// duration of the tick sound. final tickDurationMs = ValueNotifier(45); Timer? timer; AudioSource? tick; SoundHandle? tickHandle; @override void initState() { super.initState(); SoLoud.instance .loadWaveform(WaveForm.fSquare, true, 0.25, 1) .then((value) async { final octave = 2; final noteIndex = 0; final startingFreq = 55.0 * (pow(2, (12 * octave) / 12)); final freq = startingFreq * (pow(2, noteIndex / 12)); tick = value; /// start playing the tick in a paused state, so it can be /// unpaused/paused in the `Timer` callback. tickHandle = await SoLoud.instance.play(tick!, paused: true); SoLoud.instance.setWaveformFreq(tick!, freq); }); } @override Widget build(BuildContext context) { return Scaffold( body: Stack( children: [ Padding( padding: const EdgeInsets.all(16), child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text('Metronome example', textScaler: TextScaler.linear(3)), const SizedBox(height: 32), ValueListenableBuilder( valueListenable: delay, builder: (_, ms, __) { return Row( children: [ Text('delay ms: $ms BPM: ${60000 ~/ ms}'), Expanded( child: Slider.adaptive( min: 15, max: 500, value: ms.toDouble(), onChanged: (value) { delay.value = value.toInt(); start(); }, ), ), ], ); }, ), ValueListenableBuilder( valueListenable: tickDurationMs, builder: (_, duration, __) { return Row( children: [ Text('tic duration ms $duration'), Expanded( child: Slider.adaptive( min: 15, max: 500, value: duration.toDouble(), onChanged: (value) { tickDurationMs.value = value.toInt(); start(); }, ), ), ], ); }, ), ], ), ), ), ], ), ); } void start() { timer?.cancel(); timer = Timer.periodic(Duration(milliseconds: delay.value), (_) { if (tickHandle != null) { SoLoud.instance.setPause(tickHandle!, false); SoLoud.instance.schedulePause( tickHandle!, Duration(milliseconds: tickDurationMs.value), ); } }); } } ```

Soon there will be there will be the option to choose the buffer size while initializing the player.

lukepighetti commented 2 months ago

Some things I noticed using this on iOS simulator using the default buffer size