Flutter — let’s make some noise

part I — a basic example

yep, you want make some noise, having a smartphone and flutter.

Why don’t do it with native code or whatever, why just flutter? I love flutter, I love native code, but here I’m to stay how to do it in flutter, that’s it.

Flutter has a very minimal audio support, to not say it hasn’t at all, when you search infos on how to make your device blip, chirp, quack, you always find mediaplayer’s stuff or let play sound with somewhat sync to your game, but that’s not the case.

We’re talking about device generated sound and give some interaction with it, no music files, no drum&bass tracks. That’s how we can make flutter sing for us, well not really flutter alone.

In this first part, we’ll look to some java simple code to generate some sound by primitive waveforms, you know not really music, but we’re just starting.

Let’s attack the beast, it’s supposed we have a ready-to-run flutter in our machine.

  1. create a new flutter prj — flutter application, e.g. call it flutter_make_some_noise, for convenience reading choose it.pixeldump.pocs.fluttermakesomenoise as bundle ID, so far we’ll ignore ios and kotlin, don’t check them
  2. attach your device or vm, the first is preferred for performances, run the project as-is ’cause it’s better to first-build android related project at least to try if your toolchain is ok
  3. the very first time it may take minutes to accomplish, just wait and when done your device will show the classic “button counter” app
  4. stop run/debugging, look at the android folder, get into app, src, main it … fluttermakesomenoise and create a new java class
  5. ToneGenerator.java

it’s a class that run audio in a thread and provide substantial method for play/stop sound. Playing sound is a matter of fit an audio buffer, look into play():

...
while (shouldPlay) {
short[] buffer = looper.getSampleBuffer();
   for(int i = 0; i < mBufferSize; i++){
mAudioTrack.write(buffer, i, 1);
}
}
...

time by time and in-sync, we’ll create a finite size of shorts to fit that buffer, they are computed in AudioStreamLooper helper class.
For audio performance this loop must the lighter as you can to avoid glitches, this is a very crucial point, this code however is lighter enough, see AudioStreamLooper.

6. AudioStreamLooper

the class provide the waveform shape computation sample by sample, you should note there are three waveform supported: sine, saw tooth and square.
To keep code in few lines, we don’t care to cache a buffer with zero-point detection, just fill it on demand in getSampleBuffer().

so, you’ll end with a similar hierarchy:

you’ll note errors and warnings, don’t mind, thats due to flutter editing which is not java aware, you should switch to android perspective, but still continue with some error due to api level complaints, again it’s not an issue.

7. time to glue, open MainActivity.java and edit:

Flutter uses a mechanism called MethodChannel, with a methodCaller that check for invoke names to take decisions, read details here:

8. finally in dart code. Open main.dart and create invokes by flutter side, replace the whole content with this:

native side is now bridged via MethodChannel:

...
static const CHANNEL = 'it.pixeldump.pocs.fluttermakesomenoise';
static const platformChannel = const MethodChannel(CHANNEL);
...

when you need to invoke native methods, with or without parameters:

...
void _updateCurrentNote(){
int pos = noteNames.indexOf(_currentNoteName);
platformChannel.invokeMethod('setFrequency', {'frequency': frequencies[pos]});
}

void _playSound(){
platformChannel.invokeMethod('play');
setState(() {
_playState = true;
});
}
...

* we can get data from native code via Future, filling result and some more advanced approach, but this is out of scope here.

If all went right, you’ll end with this:

let’s flutter whistle

next to come: android libpd and flutter

Thanx for reading.