Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Audio buffer stream #148

Merged
merged 32 commits into from
Nov 24, 2024
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e8f5f71
Merge branch 'choose_output' into websocket
alnitak Oct 14, 2024
07eaa7e
wip
alnitak Oct 16, 2024
1428b3d
wip
alnitak Oct 17, 2024
cb6ef49
start testing
alnitak Oct 18, 2024
518a482
ok receivin PCM
alnitak Oct 19, 2024
e882c8e
first chunk is now loaded by addData
alnitak Oct 20, 2024
1548dd6
buffer is now of int8_t type instead of float
alnitak Oct 21, 2024
f33e96d
only PCM
alnitak Oct 21, 2024
ae406b6
ok
alnitak Oct 21, 2024
5bbc790
Merge branch 'only_PCM'
alnitak Oct 21, 2024
51bbb97
Merge branch 'only_PCM' into websocket
alnitak Oct 21, 2024
5cb0d77
added pcmBufferFullOrStreamEnded error
alnitak Oct 22, 2024
8580826
wip
alnitak Oct 23, 2024
97b6089
onBuffering callback wip
alnitak Oct 24, 2024
e4f4bda
Merge branch 'choose_output' into callback
alnitak Oct 24, 2024
c806ead
getBufferSize and relative widget example
alnitak Oct 24, 2024
6445c19
Merge branch 'callback' into websocket
alnitak Oct 24, 2024
9a775b9
PCM generator & fixes
alnitak Oct 25, 2024
e0ab490
win & android ok
alnitak Oct 26, 2024
7ff279c
WIP added native buffering
alnitak Oct 28, 2024
509b5f5
Merge branch v2.1.7 into websocket
alnitak Oct 29, 2024
a652b35
added `bufferingTimeNeeds` to `setBufferStream`
alnitak Oct 29, 2024
a208dae
onBuffering callback now gives back `isBuffering`, `handle` and time`
alnitak Oct 29, 2024
5056cd9
default params in setBufferStream
alnitak Oct 29, 2024
ef53f7f
chhore
alnitak Oct 30, 2024
29103cf
wasm ok!
alnitak Nov 4, 2024
0b55b1b
wasm ok!
alnitak Nov 4, 2024
2f321a3
++
alnitak Nov 9, 2024
3da7e57
review fixes
alnitak Nov 13, 2024
6255e54
fix
alnitak Nov 13, 2024
687934b
commented `buffer_stream/websocket.dat` example
alnitak Nov 13, 2024
fdda070
commented `buffer_stream/websocket.dat` example
alnitak Nov 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
263 changes: 263 additions & 0 deletions example/lib/buffer_stream/generate.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
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';

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;
alnitak marked this conversation as resolved.
Show resolved Hide resolved

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

// 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.addAudioDataStreamS8(sound, audioData);
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.addAudioDataStreamS8(sound, audioData);
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.addAudioDataStreamS8(sound, audioData);
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