Skip to content

Commit

Permalink
testing! messy!
Browse files Browse the repository at this point in the history
  • Loading branch information
jmgrosen committed Jun 11, 2024
1 parent 450b2d8 commit 160875a
Show file tree
Hide file tree
Showing 20 changed files with 172 additions and 71 deletions.
43 changes: 10 additions & 33 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ use std::process::ExitCode;
use std::{path::PathBuf, fs::File};
use std::io::{Read, Write};

use byteorder::{LittleEndian, ReadBytesExt};

use typed_arena::Arena;

use clap::Parser as CliParser;
Expand Down Expand Up @@ -44,7 +42,7 @@ enum Command {
#[cfg(feature="run")]
Sample {
/// Code file to use
file: Option<PathBuf>,
file: PathBuf,

/// Path to wav file to write to
out: PathBuf,
Expand Down Expand Up @@ -111,34 +109,21 @@ fn cmd_compile<'a>(toplevel: &mut TopLevel<'a>, file: Option<PathBuf>, out: Opti
}

#[cfg(feature="run")]
fn cmd_sample<'a>(toplevel: &mut TopLevel<'a>, file: Option<PathBuf>, out: PathBuf, length: f32) -> TopLevelResult<'a, ()> {
let wasm_bytes = match file.as_ref().map(|p| p.extension()) {
Some(Some(ext)) if ext == "wasm" => {
fn cmd_sample<'a>(toplevel: &mut TopLevel<'a>, file: PathBuf, out: PathBuf, length: f32) -> TopLevelResult<'a, ()> {
let wasm_bytes = match file.extension() {
Some(ext) if ext == "wasm" => {
let mut buf = Vec::new();
File::open(file.unwrap())?.read_to_end(&mut buf)?;
File::open(file)?.read_to_end(&mut buf)?;
buf
},
_ => {
let code = read_file(file.as_deref())?;
let code = read_file(Some(&file))?;
compile(toplevel, code)?
},
};

let engine = wasmtime::Engine::new(
&wasmtime::Config::new()
.profiler(wasmtime::ProfilingStrategy::PerfMap)
).unwrap();
let module = wasmtime::Module::new(&engine, &wasm_bytes).unwrap();
let linker = wasmtime::Linker::new(&engine);
let mut store: wasmtime::Store<()> = wasmtime::Store::new(&engine, ());
let instance = linker.instantiate(&mut store, &module).unwrap();
let memory = instance.get_memory(&mut store, "memory").unwrap();

let alloc = instance.get_typed_func::<u32, u32>(&mut store, "alloc").unwrap();
let sample_scheduler = instance.get_typed_func::<(u32, u32, u32), u32>(&mut store, "sample_scheduler").unwrap();

let samples_ptr = alloc.call(&mut store, 4 * 128).unwrap();
let mut main = instance.get_global(&mut store, "main").unwrap().get(&mut store).unwrap_i32() as u32;
let num_samples = (length * 48000.0) as usize;
let samples = clocky::toplevel::run(&wasm_bytes, num_samples);

let wav_spec = hound::WavSpec {
channels: 1,
Expand All @@ -148,16 +133,8 @@ fn cmd_sample<'a>(toplevel: &mut TopLevel<'a>, file: Option<PathBuf>, out: PathB
};
let mut hound_writer = hound::WavWriter::create(out, wav_spec).unwrap();

const CHUNK_SIZE: u32 = 128;
let num_chunks = (length * 48000.0 / (CHUNK_SIZE as f32)) as usize;
for _ in 0..num_chunks {
main = sample_scheduler.call(&mut store, (main, CHUNK_SIZE, samples_ptr)).unwrap();
let mut samples_buf = [0u8; CHUNK_SIZE as usize*4];
memory.read(&mut store, samples_ptr as usize, &mut samples_buf).unwrap();
let mut remaining_samples = &samples_buf[..];
while remaining_samples.len() > 0 {
hound_writer.write_sample(remaining_samples.read_f32::<LittleEndian>().unwrap()).unwrap();
}
for sample in samples {
hound_writer.write_sample(sample).unwrap();
}

Ok(())
Expand Down
38 changes: 38 additions & 0 deletions src/toplevel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,41 @@ pub fn compile<'a>(toplevel: &mut TopLevel<'a>, code: String) -> TopLevelResult<

Ok(wasm_bytes)
}

#[cfg(feature="run")]
pub fn run(wasm_bytes: &[u8], num_samples: usize) -> Vec<f32> {
use byteorder::{ReadBytesExt, LittleEndian};

let engine = wasmtime::Engine::new(
&wasmtime::Config::new()
.profiler(wasmtime::ProfilingStrategy::PerfMap)
).unwrap();
let module = wasmtime::Module::new(&engine, &wasm_bytes).unwrap();
let linker = wasmtime::Linker::new(&engine);
let mut store: wasmtime::Store<()> = wasmtime::Store::new(&engine, ());
let instance = linker.instantiate(&mut store, &module).unwrap();
let memory = instance.get_memory(&mut store, "memory").unwrap();

let alloc = instance.get_typed_func::<u32, u32>(&mut store, "alloc").unwrap();
let sample_scheduler = instance.get_typed_func::<(u32, u32, u32), u32>(&mut store, "sample_scheduler").unwrap();

let samples_ptr = alloc.call(&mut store, 4 * 128).unwrap();
let mut main = instance.get_global(&mut store, "main").unwrap().get(&mut store).unwrap_i32() as u32;

const CHUNK_SIZE: usize = 128;
let num_chunks = (num_samples + CHUNK_SIZE - 1) / CHUNK_SIZE;
let mut all_samples = Vec::with_capacity(num_chunks * CHUNK_SIZE);
for _ in 0..num_chunks {
main = sample_scheduler.call(&mut store, (main, CHUNK_SIZE as u32, samples_ptr)).unwrap();
let mut samples_buf = [0u8; CHUNK_SIZE*4];
memory.read(&mut store, samples_ptr as usize, &mut samples_buf).unwrap();
// could almost certainly make this more efficient, but it's
// not the bottleneck here. let's not worry about it for now.
let mut remaining_samples = &samples_buf[..];
while remaining_samples.len() > 0 {
all_samples.push(remaining_samples.read_f32::<LittleEndian>().unwrap());
}
}

all_samples
}
26 changes: 0 additions & 26 deletions test.sh

This file was deleted.

4 changes: 2 additions & 2 deletions tests/accept/440.cky
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
def persamp: sample = div (mul 440.0 (mul 2.0 pi)) 48000.0;;
def main: for k : clock. ~^(k) sample = ((&^(k) s. \x. sin x :: `(!(unbox s) (add x persamp))) : sample -> ~^(k) sample) 0.0;;
def persamp: sample = 2. * 3.14159 * 440. / 48000.;;
let main: ~^(audio) sample = ((&^(audio) s. \x. sin x :: `(!(unbox s) (x + persamp))) : sample -> ~^(audio) sample) 0.0;;
Binary file added tests/accept/440.wav
Binary file not shown.
23 changes: 23 additions & 0 deletions tests/accept/batch.cky
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,26 @@ def batch2helper: for k : clock.
: |>^(2k) |>^(2k) ~^(k) [sample; 2])));;
def batch2: for k : clock. ~^(2k) sample -> |>^(2k) ~^(k) [sample; 2] =
let (b, h) = batch2helper @(k) in b;;

let main: ~^(audio) sample =
&^(audio) s. 0. :: `(!(unbox s));;

{-
def unbatch2: for k : clock. ~^(k) [sample; 2] -> ~^(2k) sample =
&^(k) ub. \s.
let (a, sp) = %s in
let [x, y] = a in
x :: `(y :: `(!(unbox ub) sp));;

def persamp: sample = 2. * 3.14159 * 440. / 48000.;;
let unbatched: ~^(audio) sample = ((&^(audio) s. \x. sin x :: `(!(unbox s) (x + persamp))) : sample -> ~^(audio) sample) 0.0;;

let batched: |>^(audio) ~^(1/2 audio) [sample; 2] =
batch2 @(1/2 audio) unbatched;;

let reunbatched: |>^(audio) ~^(audio) sample =
`(unbatch2 @(1/2 audio) batched);;

let main: ~^(audio) sample =
0. :: reunbatched;;
-}
2 changes: 1 addition & 1 deletion tests/accept/const.cky
Original file line number Diff line number Diff line change
@@ -1 +1 @@
def main: for k : clock. ~^(k) sample = &^(k) s. 42.0 :: `!(unbox s);;
let main: ~^(audio) sample = &^(audio) s. 42.0 :: `!(unbox s);;
2 changes: 2 additions & 0 deletions tests/accept/double_good.cky
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ def doubleup: for k : clock. ~^(k) sample -> ~^(2k) sample =
&^(k) d. \s.
let (x, sp) = %s in
x :: `(x :: `(!(unbox d) !sp));;

let main: ~^(audio) sample = &^(audio) s. 0. :: `(!(unbox s));;
7 changes: 6 additions & 1 deletion tests/accept/exist.cky
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@ def enum_stream : for a : type. for c : clock. ~^(c) a -> ~^(c) (index * a) =
def enum_some_stream : for a : type. (?c. ~^(c) a) -> ?c. ~^(c) (index * a) =
\s.
let clock c and sp = s in
clock c and enum_stream $(a) @(c) sp;;
clock c and enum_stream $(a) @(c) sp;;

def bang_bang : for c : clock. ~^(c) unit =
&^(c) s. () :: `(!(unbox s));;

let main: ~^(audio) sample = &^(audio) s. 0. :: `(!(unbox s));;
4 changes: 2 additions & 2 deletions tests/accept/map.cky
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ def mapsig: for k : clock. [](sample -> sample) -> ~^(k) sample -> ~^(k) sample
(unbox f) x :: `(!(unbox mappedsig) !siginp));;

def lin: for k : clock. ~^(k) sample =
((&^(k) s. \x. x :: `(!(unbox s) (addone x))) : sample -> ~^(k) sample) 2.0;;
((&^(k) s. \x. x :: `(!(unbox s) (x + 1.0))) : sample -> ~^(k) sample) 0.0;;

def main: for k : clock. ~^(k) sample = mapsig @(k) (box (\x. sin (addone (addone x)))) (lin @(k));;
let main: ~^(audio) sample = mapsig @(audio) (box (\x. sin x))) (lin @(audio));;
Binary file added tests/accept/map.wav
Binary file not shown.
2 changes: 2 additions & 0 deletions tests/accept/poly.cky
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ def map: for a : type. for b : type. for k : clock.
\f. &^(k) m. \s.
let (x, sp) = %s in
unbox f x :: `(!(unbox m) !sp);;

let main: ~^(audio) sample = &^(audio) s. 0. :: `(!(unbox s));;
34 changes: 34 additions & 0 deletions tests/accept/sample.cky
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
def batch2helper: for k : clock.
(~^(2k) sample -> |>^(2k) ~^(k) [sample; 2]) *
(sample -> ~^(2k) sample -> ~^(k) [sample; 2]) =
(&^(2k) bh.
((\s0.
let (x0, s1) = %s0 in
`((let (b, h) = !(unbox bh) in h) x0 !s1)),
(\x0. \s1.
let (x1, s2) = %s1 in
[x0, x1] :: (`((let (b, h) = !(unbox bh) in b) !s2))
: |>^(2k) |>^(2k) ~^(k) [sample; 2])));;

def batch2: for k : clock. ~^(2k) sample -> |>^(2k) ~^(k) [sample; 2] =
let (b, h) = batch2helper @(k) in b;;

def sample: for k1 : clock. for k2 : clock. ~^(k1) (~^(k2) sample) -> ~^(k1) sample =
&^(k1) sam. \s_out.
let (s_in, s_outp) = %s_out in
let (x, s_inp) = %s_in in
x :: `(!(unbox sam) !s_outp);;

def switch: for k : clock. ~^(k) sample -> ~^(k) (unit + ~^(k) sample) -> ~^(k) sample =
&^(k) swi. \s. \e.
let (maybe_switch, ep) = %e in
case maybe_switch
{ inl a => let (x, sp) = %s in
x :: `(!(unbox swi) !sp !ep)
| inr ns => let (x, nsp) = %ns in
x :: `(!(unbox swi) !nsp !ep)
};;

def foo: for c : clock. ~^(6c) sample -> |>^(6c) ~^(3c) [sample; 2] = batch2 @(3c);;

let main: ~^(audio) sample = &^(audio) s. 0. :: `(!(unbox s));;
4 changes: 2 additions & 2 deletions tests/accept/sched.cky
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ def wavesunfiltered : for k : clock. ~^(k) sample =
def shared : for k : clock. ~^(k) sample =
let wv = wavesunfiltered @(k) in
sum @(k)
(maps @(k) (box (\x. add (0.5 * x) 0.0)) wv)
(maps @(k) (box (\x. add (0.5 * x) 0.0)) wv);;
(maps @(k) (box (\x. 0.5 * x)) wv)
(maps @(k) (box (\x. 0.5 * x)) wv);;

def regen_on_tick : for k1 : clock. for k2 : clock. [](~^(k1) sample) -> ~^(k1) sample =
\gen.
Expand Down
Binary file added tests/accept/sched.wav
Binary file not shown.
6 changes: 4 additions & 2 deletions tests/accept/transformer.cky
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@ def sinsig: for k : clock. ~^(k) sample -> ~^(k) sample =
((&^(k) sinsig. \sigin.
let (x, siginp) = %sigin in
sin x :: `(!(unbox sinsig) !siginp)));;
def persamp: sample = (div (mul 440.0 (mul 2.0 pi)) 48000.0);;
def lin: for k : clock. ~^(k) sample = ((&^(k) s. \x. x :: `(!(unbox s) (add x persamp))) : sample -> ~^(k) sample) 0.0;;
def persamp: sample = 2. * 3.14159 * 440. / 48000.;;
def lin: for k : clock. ~^(k) sample = ((&^(k) s. \x. x :: `(!(unbox s) (x + persamp))) : sample -> ~^(k) sample) 0.0;;

let main: ~^(audio) sample = lin @(audio);;
Binary file added tests/accept/transformer.wav
Binary file not shown.
4 changes: 2 additions & 2 deletions tests/accept/waves.cky
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,5 @@ def wavesunfiltered : for k : clock. ~^(k) sample =
(maps @(k) (box amp) (phasor @(k) (0.1 / 48000.)))
(maps @(k) (box (\x. x - 0.5)) (rands @(k) 1337));;

def main : for k : clock. ~^(k) sample =
0. :: `(!(onepole @(k) 0.4 (wavesunfiltered @(k))));;
let main : ~^(audio) sample =
0. :: `(!(onepole @(audio) 0.4 (wavesunfiltered @(audio))));;
Binary file added tests/accept/waves.wav
Binary file not shown.
44 changes: 44 additions & 0 deletions tests/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use std::fs::{self, File};
use std::io::Read;
#[cfg(feature = "run")]
use std::io;

use clocky::toplevel::{compile, TopLevel};
#[cfg(feature = "run")]
use clocky::toplevel::run;
#[cfg(feature = "run")]
use hound::{Error::IoError, WavReader};
use typed_arena::Arena;

#[test]
fn test_accepts() {
for test_file in fs::read_dir("tests/accept").unwrap() {
let test_file_path = test_file.unwrap().path();
// yes this weird construction is necessary, `ext != Some("rs")` will not work
println!("found file {:?}", &test_file_path);
if !matches!(test_file_path.extension(), Some(ext) if ext == "cky") {
continue;
}
println!("testing file {:?}", &test_file_path);
let mut code = String::new();
File::open(&test_file_path).unwrap().read_to_string(&mut code).unwrap();
let arena = Arena::new();
let mut toplevel = TopLevel::new(&arena);
let wasm_bytes = compile(&mut toplevel, code).unwrap();
#[cfg(feature = "run")]
{
let wav_file = match WavReader::open(test_file_path.with_extension("wav")) {
Ok(f) => f,
Err(IoError(err)) if err.kind() == io::ErrorKind::NotFound => {
println!("...no wav file, not running the compilation result");
continue;
},
Err(err) => panic!("hound error: {}", err),
};
let ran_samples = run(&wasm_bytes, 48000 * 2);
let expected_samples = wav_file.into_samples().collect::<Result<Vec<f32>, _>>().unwrap();
assert_eq!(ran_samples, expected_samples);
}
drop(wasm_bytes);
}
}

0 comments on commit 160875a

Please sign in to comment.