feat: implement Produce Sound functionality in Wave Generator#3123
Merged
marcnause merged 2 commits intofossasia:flutterfrom Mar 9, 2026
Merged
feat: implement Produce Sound functionality in Wave Generator#3123marcnause merged 2 commits intofossasia:flutterfrom
marcnause merged 2 commits intofossasia:flutterfrom
Conversation
Contributor
Reviewer's GuideImplements the missing "Produce Sound" feature in the Wave Generator by integrating the mp_audio_stream package for real-time audio synthesis, wiring it to the UI toggle button, and adding localization for the new stop-sound label across all supported languages. Sequence diagram for Produce Sound toggle and audio streamingsequenceDiagram
actor User
participant WaveGeneratorScreen
participant WaveGeneratorStateProvider
participant AudioStream
User->>WaveGeneratorScreen: tapProduceSoundButton
WaveGeneratorScreen->>WaveGeneratorStateProvider: toggleSound()
alt isPlayingSound is false
WaveGeneratorStateProvider->>WaveGeneratorStateProvider: set isPlayingSound true
WaveGeneratorStateProvider->>AudioStream: getAudioStream()
WaveGeneratorStateProvider->>AudioStream: init(bufferMilliSec, channels, sampleRate)
WaveGeneratorStateProvider->>AudioStream: resume()
loop prefillBuffers
WaveGeneratorStateProvider->>WaveGeneratorStateProvider: _fillAudioBuffer(buffer, bufferSize)
WaveGeneratorStateProvider->>AudioStream: push(buffer)
end
loop while isPlayingSound
WaveGeneratorStateProvider->>WaveGeneratorStateProvider: _fillAudioBuffer(buffer, bufferSize)
WaveGeneratorStateProvider->>AudioStream: push(buffer)
end
else isPlayingSound is true
WaveGeneratorStateProvider->>WaveGeneratorStateProvider: set isPlayingSound false
WaveGeneratorStateProvider->>WaveGeneratorStateProvider: _stopAudioStream()
WaveGeneratorStateProvider->>AudioStream: uninit()
end
WaveGeneratorStateProvider-->>WaveGeneratorScreen: notifyListeners()
WaveGeneratorScreen-->>User: updateButtonLabelAndStyle
Class diagram for WaveGeneratorStateProvider audio streaming additionsclassDiagram
class WaveGeneratorStateProvider {
+bool isPlayingSound
-AudioStream _audioStream
-double _audioAngle
+void toggleSound()
+void dispose()
-Future<void> _startAudioStream()
-void _fillAudioBuffer(Float32List buffer, int bufferSize)
-void _stopAudioStream()
}
class AudioStream {
+void init(int bufferMilliSec, int channels, int sampleRate)
+void resume()
+void push(Float32List buffer)
+void uninit()
}
WaveGeneratorStateProvider --> AudioStream : uses_for_realtime_audio_streaming
File-Level Changes
Assessment against linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Contributor
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- In
_startAudioStream, the long-runningwhile (isPlayingSound)loop assumes_audioStreamstays non-null; ifdispose()is called while sound is playing,_stopAudioStream()sets_audioStreamto null without changingisPlayingSound, so consider also clearingisPlayingSound(or having_stopAudioStreamdo it) and/or checking_audioStreamfor null in the loop to avoid pushing to a disposed stream. - It would be safer to bail out early in
_startAudioStreamifgetAudioStream()returns null orinit()fails, and to guard all_audioStream!.pushcalls with a null check so that audio backend failures don’t result in runtime exceptions.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `_startAudioStream`, the long-running `while (isPlayingSound)` loop assumes `_audioStream` stays non-null; if `dispose()` is called while sound is playing, `_stopAudioStream()` sets `_audioStream` to null without changing `isPlayingSound`, so consider also clearing `isPlayingSound` (or having `_stopAudioStream` do it) and/or checking `_audioStream` for null in the loop to avoid pushing to a disposed stream.
- It would be safer to bail out early in `_startAudioStream` if `getAudioStream()` returns null or `init()` fails, and to guard all `_audioStream!.push` calls with a null check so that audio backend failures don’t result in runtime exceptions.
## Individual Comments
### Comment 1
<location path="lib/providers/wave_generator_state_provider.dart" line_range="108-117" />
<code_context>
+ notifyListeners();
+ }
+
+ Future<void> _startAudioStream() async {
+ _audioStream = getAudioStream();
+
+ _audioStream!.init(bufferMilliSec: 1000, channels: 1, sampleRate: 44100);
+
+ _audioStream!.resume();
+
+ await Future.delayed(const Duration(milliseconds: 100));
+
+ _audioAngle = 0.0;
+
+ final int bufferSize = 4096;
+
+ final double bufferDurationMs = (bufferSize / 44100.0) * 1000.0;
+
+ final List<Float32List> bufferPool =
+ List.generate(5, (_) => Float32List(bufferSize));
+
+ int poolIndex = 0;
+
+ double generatedAudioMs = 0.0;
+
+ Stopwatch stopwatch = Stopwatch()..start();
+
+ for (int i = 0; i < 3; i++) {
+ final buffer = bufferPool[poolIndex];
+
+ poolIndex = (poolIndex + 1) % bufferPool.length;
+
+ _fillAudioBuffer(buffer, bufferSize);
+
+ _audioStream!.push(buffer);
+
+ generatedAudioMs += bufferDurationMs;
</code_context>
<issue_to_address>
**issue (bug_risk):** Guard `_audioStream!.push` against a null/teardown state to avoid crashes when stop/dispose races with the loop.
Because `_stopAudioStream` can run while this loop is active and set `_audioStream` to null, `_audioStream!.push(buffer)` can throw. Either cache a local `final stream = _audioStream;` after initialization and always use that, or add a null check (e.g., `if (_audioStream == null) break;`) before pushing and exit the loop safely.
</issue_to_address>
### Comment 2
<location path="lib/providers/wave_generator_state_provider.dart" line_range="227-231" />
<code_context>
@override
String get pin_res_desc => 'Resistance Measurement Pin';
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Align dispose semantics so that the playback flag and loop terminate when the provider is disposed.
In `dispose()`, `_stopAudioStream()` is called but `isPlayingSound` is not updated. This can leave the UI and the `_startAudioStream` loop out of sync (loop condition stays true, UI still shows playing). Please ensure `isPlayingSound` is set to `false` when disposing so both the loop and UI state stop consistently.
Suggested implementation:
```
@override
void dispose() {
// Ensure playback state and control loop terminate when the provider is disposed
isPlayingSound = false;
_stopAudioStream();
super.dispose();
}
```
If the actual `dispose()` implementation in your file differs from the one in the SEARCH block (e.g., additional cleanup logic or a different order of calls), you should still:
1. Insert `isPlayingSound = false;` at the start of `dispose()` (before `_stopAudioStream()` or any loop depending on the flag).
2. Ensure there is a single source of truth for the playback state (i.e., no other flags controlling the `_startAudioStream` loop without being reset in `dispose()`).
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Contributor
Build StatusBuild successful. APKs to test: https://github.com/fossasia/pslab-app/actions/runs/22868054938/artifacts/5835634823. Screenshots |
3cc25a4 to
bb87761
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.







-1_instruments_screen.png?raw=true)
-2_nav_drawer.png?raw=true)
-3_accelerometer.png?raw=true)
-4_power_source.png?raw=true)
-5_multimeter.png?raw=true)
-6_wave_generator.png?raw=true)
-7_oscilloscope.png?raw=true)
Fixes #3101
Changes
Previously, the "Produce Sound" feature was missing from the Wave Generator. It has now been implemented using
mp_audio_streampackage . The following functions were added inwave_generator_state_provider.dartto handle audio generation.Functions
toggleSound()
Controls the audio playback state. It updates the
isPlayingSoundflag._startAudioStream()
Initializes the audio using an
AudioStream._fillAudioBuffer()
Generates the waveform samples. It calculates the amplitude for each of the samples based on frequency and duty cycle, using trigonometric calculations for Sine and Triangular waves and conditional logic (High& Low) for PWM.
_stopAudioStream() & dispose()
Handles cleanup.
_stopAudioStreamshuts down the audio hardware, whiledisposeensures audio stops immediately if the user leaves the screen, preventing memory leaks and unnecessary battery usage.UI Update
The
wave_generator_screen.dartfile was also updated because theonPressedmethod for the Produce Sound button was missing. It has now been connected to the audio functionality.Screenshots / Recordings
Produce_Sound.mp4
Checklist:
constants.dartor localization files instead of hard-coded values.dart formator the IDE formatter.flutter analyzeand tests run influtter test.Summary by Sourcery
Add audio playback support to the Wave Generator so it can produce and stop sound based on the configured waveform.
New Features:
Enhancements:
Build: