3 Commits

Author SHA1 Message Date
BjoernSchilberg
efbc5f19f5 Merge 937ccf60c9 into 0878aa3f02 2024-05-26 22:19:42 +02:00
0878aa3f02 add support for mono-only audio devices 2024-05-26 12:54:59 +02:00
BjoernSchilberg
937ccf60c9 Added a tinygo example. 2023-02-12 22:24:16 +01:00
4 changed files with 165 additions and 38 deletions

6
examples/tinygo/build.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/bash
tinygo build -o cart.wasm -target target.json ./main.go
uw8 filter-exports cart.wasm cart.wasm && \
#wasm-opt -Oz --fast-math --strip-producers -o cart.wasm cart.wasm && \
uw8 pack -l 9 cart.wasm cart.uw8

38
examples/tinygo/main.go Normal file
View File

@@ -0,0 +1,38 @@
package main
import (
"math"
"unsafe"
)
//go:wasm-module env
//export atan2
func atan2(x, y float32) float32
//go:wasm-module env
//export time
func time() float32
func sqrt(v float32) float32 {
return float32(math.Sqrt(float64(v)))
}
var FRAMEBUFFER = (*[320 * 240]byte)(unsafe.Pointer(uintptr(120)))
//export upd
func upd() {
var i int
for i < 320*240 {
t := time() * 63.0
x := float32(i%320 - 160)
y := float32(i/320 - 120)
d := float32(40000.0) / sqrt(x*x+y*y)
u := atan2(x, y) * 512.0 / 3.141
c := uint8((int(d+t*2.0) ^ int(u+t)) >> 4)
FRAMEBUFFER[i] = c
i++
}
}
func main() {
}

View File

@@ -0,0 +1,23 @@
{
"llvm-target": "wasm32--wasi",
"build-tags": [ "tinygo.wasm" ],
"goos": "js",
"goarch": "wasm",
"linker": "wasm-ld",
"libc": "wasi-libc",
"cflags": [
"--target=wasm32--wasi",
"--sysroot={root}/lib/wasi-libc/sysroot",
"-Oz"
],
"ldflags": [
"--no-entry",
"--export-all",
"--import-memory",
"--initial-memory=262144",
"--global-base=81920",
"-zstack-size=4096",
"--strip-all"
],
"wasm-abi": "js"
}

View File

@@ -2,7 +2,7 @@ use std::sync::{mpsc, Arc, Mutex};
use std::time::Duration; use std::time::Duration;
use std::{thread, time::Instant}; use std::{thread, time::Instant};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, bail, Result};
use cpal::traits::*; use cpal::traits::*;
use rubato::Resampler; use rubato::Resampler;
use uw8_window::{Window, WindowConfig}; use uw8_window::{Window, WindowConfig};
@@ -328,26 +328,38 @@ fn init_sound(
let mut configs: Vec<_> = device let mut configs: Vec<_> = device
.supported_output_configs()? .supported_output_configs()?
.filter(|config| { .filter(|config| {
config.channels() == 2 config.channels() <= 2
&& (config.sample_format() == cpal::SampleFormat::F32 && (config.sample_format() == cpal::SampleFormat::F32
|| config.sample_format() == cpal::SampleFormat::I16) || config.sample_format() == cpal::SampleFormat::I16)
}) })
.collect(); .collect();
if configs.is_empty() {
eprintln!(
"No suitable audio output config found on device \"{}\", available configs:",
device.name()?
);
for config in device.supported_output_configs()? {
eprintln!(" {}ch {}", config.channels(), config.sample_format());
}
bail!("Failed to configure audio out");
}
configs.sort_by_key(|config| { configs.sort_by_key(|config| {
let rate = 44100 let rate = 44100
.max(config.min_sample_rate().0) .max(config.min_sample_rate().0)
.min(config.max_sample_rate().0); .min(config.max_sample_rate().0);
let prio = if rate >= 44100 { let rate_prio = if rate >= 44100 {
rate - 44100 rate - 44100
} else { } else {
(44100 - rate) * 1000 (44100 - rate) * 1000
}; };
prio + (config.sample_format() == cpal::SampleFormat::I16) as u32 let format_prio = (config.sample_format() == cpal::SampleFormat::I16) as u32;
let channels_prio = (config.channels() != 2) as u32 * 16777216;
rate_prio + format_prio + channels_prio
}); });
let config = configs let config = configs.into_iter().next().unwrap();
.into_iter()
.next()
.ok_or_else(|| anyhow!("Could not find float or 16bit signed output config"))?;
let sample_rate = cpal::SampleRate(44100) let sample_rate = cpal::SampleRate(44100)
.max(config.min_sample_rate()) .max(config.min_sample_rate())
.min(config.max_sample_rate()); .min(config.max_sample_rate());
@@ -359,6 +371,7 @@ fn init_sound(
} }
}; };
let sample_format = config.sample_format(); let sample_format = config.sample_format();
let num_channels = config.channels();
let config = cpal::StreamConfig { let config = cpal::StreamConfig {
buffer_size, buffer_size,
..config.config() ..config.config()
@@ -392,7 +405,7 @@ fn init_sound(
let mut pending_updates: Vec<RegisterUpdate> = Vec::with_capacity(30); let mut pending_updates: Vec<RegisterUpdate> = Vec::with_capacity(30);
let mut current_time = 0; let mut current_time = 0;
let mut callback = move |mut outer_buffer: &mut [f32], _: &_| { let mut callback = move |mut outer_buffer: &mut [f32]| {
let mut first_update = true; let mut first_update = true;
while let Ok(update) = rx.try_recv() { while let Ok(update) = rx.try_recv() {
if first_update { if first_update {
@@ -484,10 +497,46 @@ fn init_sound(
current_time = current_time.wrapping_add((step_size * 500 / sample_rate).max(1) as i32); current_time = current_time.wrapping_add((step_size * 500 / sample_rate).max(1) as i32);
} }
}; };
fn f32_to_i16<F>(mut buffer: &mut [i16], callback: &mut F)
where
F: FnMut(&mut [f32]),
{
let mut float_buffer = [0f32; 256];
while !buffer.is_empty() {
let step_size = buffer.len().min(float_buffer.len());
let step_buffer = &mut float_buffer[..step_size];
callback(step_buffer);
for (dest, src) in buffer.iter_mut().take(step_size).zip(step_buffer.iter()) {
*dest = (src.max(-1.0).min(1.0) * 32767.0) as i16;
}
buffer = &mut buffer[step_size..];
}
}
fn stereo_to_mono<F>(mut buffer: &mut [f32], callback: &mut F)
where
F: FnMut(&mut [f32]),
{
let mut in_buffer = [0f32; 256];
while !buffer.is_empty() {
let step_size = buffer.len().min(in_buffer.len() / 2);
let step_buffer = &mut in_buffer[..step_size * 2];
callback(step_buffer);
for (index, dest) in buffer.iter_mut().take(step_size).enumerate() {
*dest = (step_buffer[index * 2] + step_buffer[index * 2 + 1]) * 0.5;
}
buffer = &mut buffer[step_size..];
}
}
let stream = if sample_format == cpal::SampleFormat::F32 { let stream = if sample_format == cpal::SampleFormat::F32 {
if num_channels == 2 {
device.build_output_stream( device.build_output_stream(
&config, &config,
callback, move |buffer: &mut [f32], _| callback(buffer),
move |err| { move |err| {
dbg!(err); dbg!(err);
}, },
@@ -496,24 +545,35 @@ fn init_sound(
} else { } else {
device.build_output_stream( device.build_output_stream(
&config, &config,
move |mut buffer: &mut [i16], info| { move |buffer: &mut [f32], _| stereo_to_mono(buffer, &mut callback),
let mut float_buffer = [0f32; 256]; move |err| {
dbg!(err);
while !buffer.is_empty() { },
let step_size = buffer.len().min(float_buffer.len()); None,
let step_buffer = &mut float_buffer[..step_size]; )?
callback(step_buffer, info);
for (dest, src) in buffer.iter_mut().take(step_size).zip(step_buffer.iter()) {
*dest = (src.max(-1.0).min(1.0) * 32767.0) as i16;
}
buffer = &mut buffer[step_size..];
} }
} else {
if num_channels == 2 {
device.build_output_stream(
&config,
move |buffer: &mut [i16], _| f32_to_i16(buffer, &mut callback),
move |err| {
dbg!(err);
},
None,
)?
} else {
device.build_output_stream(
&config,
move |buffer: &mut [i16], _| {
f32_to_i16(buffer, &mut |b| stereo_to_mono(b, &mut callback))
}, },
move |err| { move |err| {
dbg!(err); dbg!(err);
}, },
None, None,
)? )?
}
}; };
Ok(Uw8Sound { stream, tx }) Ok(Uw8Sound { stream, tx })