Skip to content

Commit

Permalink
Get basic interactive wavetable editor working
Browse files Browse the repository at this point in the history
 * Changing sliders for harmonic magnitudes and phases works, generates visible waveform, and plays via wavetable live
 * Move waveform and waveform image rendering to web worker
  • Loading branch information
Ameobea committed Mar 2, 2023
1 parent 46b8b18 commit 1e95d82
Show file tree
Hide file tree
Showing 22 changed files with 504 additions and 121 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,4 @@ public/waveform_renderer.wasm
public/compressor.wasm
public/vocoder.wasm
public/level_detector.wasm
src/wavegen*
public/wavegen.wasm
20 changes: 10 additions & 10 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ build-all:
&& wasm-bindgen ./target/wasm32-unknown-unknown/release/polysynth.wasm --browser --remove-producers-section --out-dir ./build \
&& wasm-bindgen ./target/wasm32-unknown-unknown/release/waveform_renderer.wasm --browser --remove-producers-section --out-dir ./build \
&& wasm-bindgen ./target/wasm32-unknown-unknown/release/note_container.wasm --browser --remove-producers-section --out-dir ./build \
&& wasm-bindgen ./target/wasm32-unknown-unknown/release/wav_decoder.wasm --browser --remove-producers-section --out-dir ./build \
&& wasm-bindgen ./target/wasm32-unknown-unknown/release/wavegen.wasm --browser --remove-producers-section --out-dir ./build
&& wasm-bindgen ./target/wasm32-unknown-unknown/release/wav_decoder.wasm --browser --remove-producers-section --out-dir ./build

cd -
cp ./engine/target/wasm32-unknown-unknown/release/*.wasm ./public
Expand All @@ -80,6 +79,8 @@ build-all:
cp ./engine/target/wasm32-unknown-unknown/release/midi_quantizer.wasm ./public
cp ./engine/target/wasm32-unknown-unknown/release/quantizer.wasm ./public
cp ./engine/target/wasm32-unknown-unknown/release/compressor.wasm ./public
cp ./engine/target/wasm32-unknown-unknown/release/level_detector.wasm ./public
cp ./engine/target/wasm32-unknown-unknown/release/wavegen.wasm ./public
cp ./engine/build/* ./src

just build-sinsy
Expand Down Expand Up @@ -109,15 +110,13 @@ run:
&& cp ./target/wasm32-unknown-unknown/release/waveform_renderer.wasm /tmp/wasm \
&& cp ./target/wasm32-unknown-unknown/release/note_container.wasm /tmp/wasm \
&& cp ./target/wasm32-unknown-unknown/release/wav_decoder.wasm /tmp/wasm \
&& cp ./target/wasm32-unknown-unknown/release/wavegen.wasm /tmp/wasm \
&& wasm-bindgen /tmp/wasm/engine.wasm --browser --remove-producers-section --out-dir ./build \
&& wasm-bindgen /tmp/wasm/midi.wasm --browser --remove-producers-section --out-dir ./build \
&& wasm-bindgen /tmp/wasm/spectrum_viz.wasm --browser --remove-producers-section --out-dir ./build \
&& wasm-bindgen /tmp/wasm/polysynth.wasm --browser --remove-producers-section --out-dir ./build \
&& wasm-bindgen /tmp/wasm/waveform_renderer.wasm --browser --remove-producers-section --out-dir ./build \
&& wasm-bindgen /tmp/wasm/note_container.wasm --browser --remove-producers-section --out-dir ./build \
&& wasm-bindgen /tmp/wasm/wav_decoder.wasm --browser --remove-producers-section --out-dir ./build \
&& wasm-bindgen /tmp/wasm/wavegen.wasm --browser --remove-producers-section --out-dir ./build
&& wasm-bindgen /tmp/wasm/wav_decoder.wasm --browser --remove-producers-section --out-dir ./build

cd -
cp ./engine/build/* ./src/
Expand All @@ -137,6 +136,8 @@ run:
cp ./engine/target/wasm32-unknown-unknown/release/midi_quantizer.wasm ./public
cp ./engine/target/wasm32-unknown-unknown/release/quantizer.wasm ./public
cp ./engine/target/wasm32-unknown-unknown/release/compressor.wasm ./public
cp ./engine/target/wasm32-unknown-unknown/release/level_detector.wasm ./public
cp ./engine/target/wasm32-unknown-unknown/release/wavegen.wasm ./public

just debug-sinsy

Expand Down Expand Up @@ -245,11 +246,6 @@ build-wav-decoder:
cd - && wasm-bindgen ./engine/target/wasm32-unknown-unknown/debug/wav_decoder.wasm --browser --remove-producers-section --out-dir ./engine/build
cp ./engine/build/wav_decoder* ./src/

build-wavegen:
cd ./engine/wavegen && cargo build --release --target wasm32-unknown-unknown && \
cd - && wasm-bindgen ./engine/target/wasm32-unknown-unknown/release/wavegen.wasm --browser --remove-producers-section --out-dir ./engine/build
cp ./engine/build/wavegen* ./src/

build-event-scheduler:
cd ./engine/event_scheduler && cargo build --release --target wasm32-unknown-unknown && \
cp ../target/wasm32-unknown-unknown/release/event_scheduler.wasm ../../public
Expand Down Expand Up @@ -289,3 +285,7 @@ debug-vocoder:
build-level-detector:
cd ./engine/level_detector && cargo build --release --target wasm32-unknown-unknown && \
cp ../target/wasm32-unknown-unknown/release/level_detector.wasm ../../public

build-wavegen:
cd ./engine/wavegen && cargo build --release --target wasm32-unknown-unknown && \
cp ../target/wasm32-unknown-unknown/release/wavegen.wasm ../../public
4 changes: 2 additions & 2 deletions engine/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions engine/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ edition = "2021"
uuid = { version = "1.2" }
rand = "0.7.3"
rand_pcg = "0.2.1"
wasm-bindgen = { version = "0.2.82", optional = true }

[features]
bindgen = ["wasm-bindgen"]
4 changes: 3 additions & 1 deletion engine/release.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
cargo build --release --target wasm32-unknown-unknown --workspace --exclude common --exclude dsp --exclude wbg_logging &&
cargo build --release --target wasm32-unknown-unknown --workspace --exclude common --exclude dsp --exclude wbg_logging --exclude wavegen &&
mv ./target/wasm32-unknown-unknown/release/wavetable.wasm ./target/wasm32-unknown-unknown/release/wavetable_no_simd.wasm &&
cd wavegen && cargo build --target wasm32-unknown-unknown --release &&
cd .. &&
cd wavetable && RUSTFLAGS="-Ctarget-feature=+simd128" cargo build --target wasm32-unknown-unknown --release --features=simd
6 changes: 3 additions & 3 deletions engine/waveform_renderer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ crate-type = ["cdylib", "rlib"]

[dependencies]
wasm-bindgen = { version = "=0.2.82", optional = true }
common = { path = "../common" }
wbg_logging = { path = "../wbg_logging" }
common = { path = "../common", default_features = false, features = [] }
wbg_logging = { path = "../wbg_logging", optional = true }
log = { version = "0.4", features = ["release_max_level_off"] }

[features]
bindgen = ["wasm-bindgen"]
bindgen = ["wasm-bindgen", "common/bindgen", "wbg_logging"]
default = ["bindgen"]
16 changes: 11 additions & 5 deletions engine/waveform_renderer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,16 @@ impl WaveformRendererCtx {
}
}

#[cfg_attr(feature = "bindgen", wasm_bindgen)]
#[cfg(feature = "bindgen")]
#[wasm_bindgen]
pub fn create_waveform_renderer_ctx(
waveform_length_samples: u32,
sample_rate: u32,
width_px: u32,
height_px: u32,
) -> *mut WaveformRendererCtx {
common::maybe_init(None);

wbg_logging::maybe_init();

let mut ctx =
Expand All @@ -56,19 +58,22 @@ pub fn create_waveform_renderer_ctx(
Box::into_raw(ctx)
}

#[cfg_attr(feature = "bindgen", wasm_bindgen)]
#[cfg(feature = "bindgen")]
#[wasm_bindgen]
pub fn append_samples_to_waveform(ctx: *mut WaveformRendererCtx, new_samples: &[f32]) -> usize {
let ctx = unsafe { &mut *ctx };
ctx.waveform_buf.extend_from_slice(new_samples);
ctx.waveform_buf.len()
}

#[cfg_attr(feature = "bindgen", wasm_bindgen)]
#[cfg(feature = "bindgen")]
#[wasm_bindgen]
pub fn free_waveform_renderer_ctx(ctx: *mut WaveformRendererCtx) {
drop(unsafe { Box::from_raw(ctx) })
}

#[cfg_attr(feature = "bindgen", wasm_bindgen)]
#[cfg(feature = "bindgen")]
#[wasm_bindgen]
pub fn get_waveform_buf_ptr(ctx: *mut WaveformRendererCtx) -> *mut f32 {
unsafe { (*ctx).waveform_buf.as_mut_ptr() }
}
Expand Down Expand Up @@ -151,7 +156,8 @@ pub fn render_waveform(ctx: *mut WaveformRendererCtx, start_ms: u32, end_ms: u32
ctx.image_data_buf.as_mut_ptr()
}

#[cfg_attr(feature = "bindgen", wasm_bindgen)]
#[cfg(feature = "bindgen")]
#[wasm_bindgen]
pub fn get_sample_count(ctx: *const WaveformRendererCtx) -> usize {
unsafe { (*ctx).waveform_buf.len() }
}
6 changes: 1 addition & 5 deletions engine/wavegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,10 @@ crate-type = ["cdylib", "rlib"]

[dependencies]
rustfft = "6.1"
wasm-bindgen = { version = "=0.2.82", optional = true }
waveform_renderer = { path = "../waveform_renderer", default-features = false, features = [] }
console_error_panic_hook = "0.1.6"
fastapprox = "0.3"
common = { path = "../common", default-features = false, features = [] }

[dev-dependencies]
textplots = "0.8"

[features]
bindgen = ["wasm-bindgen"]
default = ["bindgen"]
49 changes: 40 additions & 9 deletions engine/wavegen/src/bindings.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,45 @@
use std::f32::consts::PI;

use wasm_bindgen::prelude::*;
use waveform_renderer::WaveformRendererCtx;

extern "C" {
fn log_err(s: *const u8, len: usize);
}

const WAVEFORM_LENGTH_SAMPLES: usize = 1024 * 4;
const HARMONIC_COUNT: usize = 64;
const WAVEFORM_HEIGHT_PX: u32 = 256;
const WAVEFORM_WIDTH_PX: u32 = 1024;
const SAMPLE_RATE: u32 = 44_100;

fn build_waveform(buf: &mut Vec<f32>, magnitudes: &[f32], phases: &[f32]) {
fn build_waveform(buf: &mut Vec<f32>, magnitudes: &[f32], phases: &[f32], fast: bool) {
buf.fill(0.);
buf.resize(WAVEFORM_LENGTH_SAMPLES, 0.);
for (harmonic_ix, (magnitude, phase)) in magnitudes.iter().zip(phases).enumerate() {
if *magnitude == 0. {
continue;
}

for (sample_ix, sample) in buf.iter_mut().enumerate() {
let phase =
(sample_ix as f32 / WAVEFORM_LENGTH_SAMPLES as f32) * PI * 2. * harmonic_ix as f32
+ -*phase * PI * 2.;
// *sample += magnitude * phase.sin();
*sample += magnitude * fastapprox::fast::sinfull(phase);
if fast {
*sample += magnitude * fastapprox::fast::sinfull(phase);
} else {
*sample += magnitude * phase.sin();
}
}
}
}

static mut WAVEFORM_RENDERER_CTX: *mut WaveformRendererCtx = std::ptr::null_mut();
static mut ENCODED_STATE_BUF: [f32; HARMONIC_COUNT * 2] = [0.; HARMONIC_COUNT * 2];

#[no_mangle]
pub extern "C" fn get_encoded_state_buf_ptr() -> *mut f32 {
unsafe { ENCODED_STATE_BUF.as_mut_ptr() }
}

fn get_waveform_renderer_ctx() -> &'static mut WaveformRendererCtx {
if !unsafe { WAVEFORM_RENDERER_CTX.is_null() } {
Expand All @@ -43,15 +59,30 @@ fn get_waveform_renderer_ctx() -> &'static mut WaveformRendererCtx {
/// State format:
/// [WAVEFORM_SIZE * HARMONIC_COUNT] f32s for the magnitudes
/// [WAVEFORM_SIZE * HARMONIC_COUNT] f32s for the phases
#[wasm_bindgen]
pub fn render_waveform(state: &[f32]) -> *const u8 {
console_error_panic_hook::set_once();
#[no_mangle]
pub extern "C" fn wavegen_render_waveform() -> *const u8 {
common::set_raw_panic_hook(log_err);

assert_eq!(state.len(), HARMONIC_COUNT * 2, "Invalid state length");
let state = unsafe { &mut ENCODED_STATE_BUF };
let magnitudes = &mut state[..HARMONIC_COUNT];
// normalize magnitudes
let max_magnitude = magnitudes.iter().fold(0.0f32, |acc, x| acc.max(*x));
if max_magnitude > 0. {
for magnitude in magnitudes.iter_mut() {
*magnitude /= max_magnitude;
}
}
drop(magnitudes);
let magnitudes = &state[..HARMONIC_COUNT];
let phases = &state[HARMONIC_COUNT..];

let ctx = get_waveform_renderer_ctx();
build_waveform(&mut ctx.waveform_buf, magnitudes, phases);
build_waveform(&mut ctx.waveform_buf, magnitudes, phases, true);
waveform_renderer::render_waveform(ctx, 0, 100_000_000)
}

#[no_mangle]
pub extern "C" fn wavegen_get_waveform_buf_ptr() -> *const f32 {
let ctx = get_waveform_renderer_ctx();
ctx.waveform_buf.as_ptr()
}
4 changes: 1 addition & 3 deletions engine/wavegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

//! Utilities for generating waveforms and wavetables using inverse FFT.
mod bindings;
#[cfg(test)]
mod tests;

#[cfg(feature = "bindgen")]
mod bindings;
2 changes: 1 addition & 1 deletion engine/wavetable/src/fm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use self::{
};

extern "C" {
fn log_err(ptr: *const u8, len: usize);
pub(crate) fn log_err(ptr: *const u8, len: usize);

fn on_gate_cb(midi_number: usize, voice_ix: usize);

Expand Down
21 changes: 14 additions & 7 deletions engine/wavetable/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
#![feature(
box_syntax,
stdsimd,
const_maybe_uninit_assume_init,
get_mut_unchecked
)]
#![feature(box_syntax, stdsimd, const_maybe_uninit_assume_init, get_mut_unchecked)]

pub mod fm;
pub mod lookup_tables;
Expand Down Expand Up @@ -175,6 +170,8 @@ pub fn init_wavetable(
waveform_length: usize,
base_frequency: f32,
) -> *mut WaveTable {
common::set_raw_panic_hook(crate::fm::log_err);

let settings = WaveTableSettings {
waveforms_per_dimension,
dimension_count,
Expand All @@ -190,6 +187,11 @@ pub fn get_data_table_ptr(handle_ptr: *mut WaveTable) -> *mut f32 {
unsafe { (*handle_ptr).samples.as_mut_ptr() }
}

#[no_mangle]
pub extern "C" fn set_base_frequency(handle_ptr: *mut WaveTable, base_frequency: f32) {
unsafe { (*handle_ptr).settings.base_frequency = base_frequency }
}

#[no_mangle]
pub fn drop_wavetable(table: *mut WaveTable) { drop(unsafe { Box::from_raw(table) }) }

Expand Down Expand Up @@ -237,14 +239,19 @@ pub fn get_samples(handle_ptr: *mut WaveTableHandle, sample_count: usize) -> *co
}

for sample_ix in 0..sample_count {
let frequency = handle.frequencies_buffer[sample_ix];
if frequency == 0.0 {
handle.sample_buffer[sample_ix] = 0.0;
continue;
}

for dimension_ix in 0..handle.table.settings.dimension_count {
handle.mixes_for_sample[dimension_ix * 2] =
handle.mixes[(dimension_ix * 2 * sample_count) + sample_ix];
handle.mixes_for_sample[dimension_ix * 2 + 1] =
handle.mixes[(dimension_ix * 2 * sample_count) + sample_count + sample_ix];
}

let frequency = handle.frequencies_buffer[sample_ix];
handle.sample_buffer[sample_ix] = handle.get_sample(frequency);
}

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
"ace-builds": "^1.15",
"ameo-utils": "^0.8.0",
"chartist": "^0.11.4",
"comlink": "^4.4.1",
"d3": "^7.8.2",
"dexie": "^3.2.3",
"downloadjs": "^1.4.7",
Expand Down
Loading

0 comments on commit 1e95d82

Please sign in to comment.