Skip to content

Commit

Permalink
Merge pull request #148 from alnitak/websocket
Browse files Browse the repository at this point in the history
Audio buffer stream
  • Loading branch information
alnitak authored Nov 24, 2024
2 parents 8d9d1ba + fdda070 commit 551f541
Show file tree
Hide file tree
Showing 34 changed files with 2,086 additions and 84 deletions.
4 changes: 2 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"name": "Flutter debug",
"type": "dart",
"request": "launch",
"program": "lib/output_device/output_device.dart",
"program": "lib/buffer_stream/websocket.dart",
"flutterMode": "debug",
"cwd": "${workspaceFolder}/example"
},
Expand All @@ -24,7 +24,7 @@
"name": "Flutter release",
"type": "dart",
"request": "launch",
"program": "lib/main.dart",
"program": "lib/buffer_stream/websocket.dart",
"flutterMode": "release",
"cwd": "${workspaceFolder}/example"
},
Expand Down
9 changes: 7 additions & 2 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
"tasks": [
{
"label": "compile linux debug verbose",
"command": "cd ${workspaceFolder}/example; flutter build linux -t lib/main.dart --debug --verbose",
"command": "cd ${workspaceFolder}/example; flutter build linux -t lib/buffer_stream/websocket.dart --debug --verbose",
// "args": ["build", "linux", "--verbose"],
"type": "shell"
},
{
"label": "compile linux debug",
"command": "cd ${workspaceFolder}/example; flutter build linux -t lib/output_device/output_device.dart --debug",
"command": "cd ${workspaceFolder}/example; flutter build linux -t lib/buffer_stream/websocket.dart --debug",
"type": "shell"
},
{
Expand All @@ -31,6 +31,11 @@
"label": "compile web debug",
"command": "cd ${workspaceFolder}/example; flutter run -d chrome --web-renderer canvaskit --web-browser-flag '--disable-web-security' -t lib/main.dart --release",
"type": "shell"
},
{
"label": "compile WASM",
"command": "sh ${workspaceFolder}/wasm.sh",
"type": "shell"
}
]
}
1 change: 1 addition & 0 deletions android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ list(APPEND PLUGIN_SOURCES
"${SRC_DIR}/analyzer.cpp"
"${SRC_DIR}/synth/basic_wave.cpp"
"${SRC_DIR}/waveform/waveform.cpp"
"${SRC_DIR}/audiobuffer/audiobuffer.cpp"
"${SRC_DIR}/filters/filters.cpp"
"${SRC_DIR}/filters/pitch_shift_filter.cpp"
"${SRC_DIR}/filters/smbPitchShift.cpp"
Expand Down
272 changes: 272 additions & 0 deletions example/lib/buffer_stream/generate.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
import 'dart:developer' as dev;
import 'dart:math';
import 'dart:typed_data';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_soloud/flutter_soloud.dart';
import 'package:flutter_soloud_example/buffer_stream/ui/buffer_widget.dart';
import 'package:logging/logging.dart';

/// Example of how to generate PCM audio inside an `Isolate` and play them.
///
/// The `setBufferStream`, `addAudioDataStream` and `setDataIsEnded` methods
/// can be used inside an `Isolate`. So you can perform complex operations
/// computing audio inside an `Isolate` without freezing the main Isolate.
void main() async {
// The `flutter_soloud` package logs everything
// (from severe warnings to fine debug messages)
// using the standard `package:logging`.
// You can listen to the logs as shown below.
Logger.root.level = kDebugMode ? Level.FINE : Level.INFO;
Logger.root.onRecord.listen((record) {
dev.log(
record.message,
time: record.time,
level: record.level.value,
name: record.loggerName,
zone: record.zone,
error: record.error,
stackTrace: record.stackTrace,
);
});

WidgetsFlutterBinding.ensureInitialized();

/// Initialize the player.
await SoLoud.instance.init();

runApp(
const MaterialApp(
home: Generate(),
),
);
}

@pragma('vm:entry-point')
Future<void> generateTone(Map<String, dynamic> args) async {
final sound = args['sound'] as AudioSource;

// Frequency in Hz
final frequency = args['frequency'] as double;

// Sampling rate in Hz (samples per second)
final sampleRate = args['sampleRate'] as int;

// Duration of the audio in seconds
final duration = args['duration'] as double;

// Total number of samples needed for the given duration
final sampleCount = (sampleRate * duration).ceil();

// List to hold the audio samples
final audioData = Int8List(sampleCount);

// Generate PCM data
for (var i = 0; i < sampleCount; i++) {
// Calculate the amplitude of the wave (between -128 and 127 for 8-bit PCM)
final amplitude = (127 * sin(2 * pi * frequency * i / sampleRate)).toInt();
// Store the amplitude in the audio data buffer
audioData[i] = amplitude;
}

SoLoud.instance.addAudioDataStream(
sound,
audioData.buffer.asUint8List(),
);
SoLoud.instance.setDataIsEnded(sound);
}

@pragma('vm:entry-point')
Future<void> generateBouncingSoundPCM(Map<String, dynamic> args) async {
final sound = args['sound'] as AudioSource;
// Sampling rate in Hz (samples per second)
final sampleRate = args['sampleRate'] as int;
// Duration of the audio in seconds
final duration = args['duration'] as double;
// Total number of samples needed for the given duration
final sampleCount = sampleRate * duration;
// List to hold the audio samples
final audioData = Int8List(sampleCount.ceil());

// Parameters for bouncing effect
const baseFrequency = 300.0; // Base frequency in Hz
const bounceFrequency = 8.0; // Frequency of bounce effect
const bounceDepth = 200.0; // Depth of frequency variation

// Generate PCM data with bouncing frequency modulation
for (var i = 0; i < sampleCount; i++) {
// Calculate time for each sample
final time = i / sampleRate;

// Create a bouncing effect by oscillating the frequency
final modulatedFrequency =
baseFrequency + bounceDepth * sin(2 * pi * bounceFrequency * time);

// Apply an oscillation to the amplitude to make it sound more dynamic
final amplitudeModulation =
0.8 + 0.2 * sin(2 * pi * bounceFrequency * 0.5 * time);

// Generate sample using modulated frequency and amplitude
final amplitude =
(127 * amplitudeModulation * sin(2 * pi * modulatedFrequency * time))
.toInt();

// Store the sample value in the audio data buffer
audioData[i] = amplitude;
}

SoLoud.instance.addAudioDataStream(sound, audioData.buffer.asUint8List());
SoLoud.instance.setDataIsEnded(sound);
}

@pragma('vm:entry-point')
Future<void> generateWailingSirenSoundPCM(Map<String, dynamic> args) async {
final sound = args['sound'] as AudioSource;
// Sampling rate in Hz (samples per second)
final sampleRate = args['sampleRate'] as int;
// Duration of the audio in seconds
final duration = args['duration'] as double;
// Total number of samples needed for the given duration
final sampleCount = sampleRate * duration;
// List to hold the audio samples
final audioData = Int8List(sampleCount.ceil());

// Parameters for the siren effect
const baseFrequency =
600.0; // Higher base frequency for a more noticeable sound
const sirenSpeed = 4.0; // Slower frequency oscillation for a siren effect
const frequencyRange =
150.0; // Moderate frequency range for smooth oscillation

// Generate PCM data with a smooth siren-like frequency modulation
for (var i = 0; i < sampleCount; i++) {
// Calculate time for each sample
final time = i / sampleRate;

// Smoothly oscillate the frequency
final modulatedFrequency =
baseFrequency + frequencyRange * sin(2 * pi * sirenSpeed * time);

// Generate the sample using a sinusoidal wave with modulated frequency
final amplitude = (127 * sin(2 * pi * modulatedFrequency * time)).toInt();

// Store the sample value in the audio data buffer
audioData[i] = amplitude;
}

SoLoud.instance.addAudioDataStream(sound, audioData.buffer.asUint8List());
SoLoud.instance.setDataIsEnded(sound);
}

class Generate extends StatefulWidget {
const Generate({super.key});

@override
State<Generate> createState() => _GenerateState();
}

class _GenerateState extends State<Generate> {
AudioSource? tone;
AudioSource? bouncing;
AudioSource? siren;

@override
Widget build(BuildContext context) {
const gap = SizedBox(height: 16);
return Scaffold(
appBar: AppBar(title: const Text('Generate PCM Data')),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
OutlinedButton(
onPressed: () async {
/// Setup the buffer stream for each streams.
tone = SoLoud.instance.setBufferStream(
maxBufferSize: 1024 * 1024 * 1,
pcmFormat: BufferPcmType.s8,
);
bouncing = SoLoud.instance.setBufferStream(
maxBufferSize: 1024 * 1024 * 1,
pcmFormat: BufferPcmType.s8,
);
siren = SoLoud.instance.setBufferStream(
maxBufferSize: 1024 * 1024 * 1,
pcmFormat: BufferPcmType.s8,
);

/// Generate PCM data inside an Isolate
await Future.wait([
compute(generateTone, {
'sound': tone,
'frequency': 440.0,
'sampleRate': 44100,
'duration': 1.0,
}),
compute(generateBouncingSoundPCM, {
'sound': bouncing,
'frequency': 200.0,
'sampleRate': 44100,
'duration': 3.0,
}),
compute(generateWailingSirenSoundPCM, {
'sound': siren,
'frequency': 200.0,
'sampleRate': 44100,
'duration': 2.0,
}),
]);

/// Just to rebuild [BufferBar] widgets
setState(() {});
},
child: const Text('generate PCM data'),
),
gap,
Row(
mainAxisSize: MainAxisSize.min,
children: [
OutlinedButton(
onPressed: () async {
if (tone == null) return;
await SoLoud.instance.play(tone!, looping: true);
},
child: const Text('Play tone'),
),
OutlinedButton(
onPressed: () async {
if (siren == null) return;
await SoLoud.instance.play(siren!, looping: true);
},
child: const Text('Play siren'),
),
OutlinedButton(
onPressed: () async {
if (bouncing == null) return;
await SoLoud.instance.play(bouncing!, looping: true);
},
child: const Text('Play bouncing'),
),
],
),
gap,
OutlinedButton(
onPressed: () async {
await SoLoud.instance.disposeAllSources();
tone = siren = bouncing = null;
setState(() {});
},
child: const Text('dispose all sounds'),
),
gap,
BufferBar(sound: tone, startingMb: 1, label: 'tone'),
BufferBar(sound: siren, startingMb: 1, label: 'siren'),
BufferBar(sound: bouncing, startingMb: 1, label: 'bouncing'),
],
),
),
);
}
}
Loading

0 comments on commit 551f541

Please sign in to comment.