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

Bandlimited oscillators #127

Open
PaulBatchelor opened this issue Apr 4, 2023 · 7 comments
Open

Bandlimited oscillators #127

PaulBatchelor opened this issue Apr 4, 2023 · 7 comments
Labels
good first issue Good for newcomers

Comments

@PaulBatchelor
Copy link

Hi! Big fan, long time lurker.

I took a peak at some of your DSP code. The oscillators I've seen so far are good and fast waveshape implementations, and while they work well for things like LFOs, they are also prone to aliasing artifacts at higher frequencies. It doesn't look like you have any bandlimited oscillators, which are designed in a way to suppress this kind of aliasing noise. Sometimes they are known as "virtual analog oscillators". PolyBLEPs are a pretty common way to implement bandlimited oscillators.

If you don't have these oscillators already, I have working C code for PolyBLEP saw, triangle, and square wave oscillators that I'd love to try and port to Rust and Glicol.

@chaosprint
Copy link
Owner

Thanks for bringing up the issue! That makes a lot of sense. It would be great if you could port it to Rust. Perhaps as an independent crate? I can then just rely on the crate.

@PaulBatchelor
Copy link
Author

I know exactly enough Rust to have made it this far: https://github.com/paulbatchelor/boing

My intention is to eventually populate it with a few more DSP goodies. Right now, it's just a bandlimited sawtooth oscillator as a proof-of-concept.

The example I have included with the library produces a bandlimited sawtooth oscillator and writes it to disk as raw PCM audio, which can be converted to WAV using SoX. I'm pretty sure it works:

https://github.com/PaulBatchelor/Boing/blob/main/examples/blsaw.rs

What steps do I take to make this working in Glicol?

I'm still very new to Rust, so if you have any suggestions, let me know!

@chaosprint
Copy link
Owner

chaosprint commented Apr 6, 2023

I know exactly enough Rust to have made it this far: https://github.com/paulbatchelor/boing

My intention is to eventually populate it with a few more DSP goodies. Right now, it's just a bandlimited sawtooth oscillator as a proof-of-concept.

The example I have included with the library produces a bandlimited sawtooth oscillator and writes it to disk as raw PCM audio, which can be converted to WAV using SoX. I'm pretty sure it works:

https://github.com/PaulBatchelor/Boing/blob/main/examples/blsaw.rs

What steps do I take to make this working in Glicol?

I'm still very new to Rust, so if you have any suggestions, let me know!

I think describing "the process of how a saw osc" is made can best illustrate the point here.

glicol_parser

For my workflow, I always start with the parser.

Inside rs/parser/src/glicol.pest, try to search for all the saw and then add in parallel your fancy new node.

Can test here: https://pest.rs/#editor

Then in rs/parser/src/lib.rs, you will see code like this:

Rule::saw => one_para_number_or_ref!("saw"),

Also, use the same macro and add your own node.

Rule::saw => one_para_number_or_ref!("saw"),
Rule::yournode => one_para_number_or_ref!("yournode"),

So for this kind of node, it takes one number or a reference.

Done with the parser.

glicol_synth

In glicol_synth, you can find the folder rs/synth/node/oscillator/, and create a yournode.rs.

You can write in a similar style to saw_osc.rs. I would recommend some copy paste and then modifying the process function for the main DSP and send_msg to handle the update.

Remember to expose yournode.rs to the mod.rs there. Then you are good to go.

glicol main crate

Finally, in the main repo, there is this rs/main/src/util.rs.

Try to find saw and write yournode in parallel. Remember to import your Struct at the top (I always forget).

move to wasm

Then you can use the script in wasm to compile the code. Nothing needs to be changed there for adding a new node.

Then cd to the js folder. In js/src/glicol.rs comment out the window.version = "v0.12.12" line to use local WASM.

Inside js folder, run pnpm dev (I use this) or just npm run dev. You can now use your node in the browser.

Hope I didn't miss anything. Let me know if you encounter any issues.

@PaulBatchelor
Copy link
Author

@chaosprint awesome information. Let me digest this. This seems pretty comprehensive, but if I run into anything, I'll let you know.

@chaosprint
Copy link
Owner

@chaosprint awesome information. Let me digest this. This seems pretty comprehensive, but if I run into anything, I'll let you know.

If you'd like to keep it light and focus more on DSP, just look at glicol_synth crate, playing with the examples folder there.

And here's an example to combine glicol_synth and cpal to get sound on desktop platforms:

https://github.com/chaosprint/cpal/blob/glicol_techno_example/examples/glicol_synth.rs

@PaulBatchelor
Copy link
Author

@chaosprint It took some fiddling, but I got some initial sound working:

main...PaulBatchelor:glicol:main

For troubleshooting, it was helpful for me to write bytes to disk offline, so I whipped up this program that I run as an example in main:

use glicol::Engine;
use std::fs::File;
use std::io::prelude::*;

fn main() {
    let mut engine = Engine::<32>::new();

    let mut bytes: [u8; 128] = [0; 128];
    let nblks = (44100 * 5) / 32;
    let file = File::create("test.bin");

    engine.update_with_code(r#"o: blsaw 440 >> mul 0.1"#);


    for _ in 0..nblks {
        let blk = engine.next_block(vec![]).0;
        for n in 0..32 {
            let pos = n * 4;
            let b = blk[0][n].to_le_bytes();
            bytes[pos] = b[0];
            bytes[pos + 1] = b[1];
            bytes[pos + 2] = b[2];
            bytes[pos + 3] = b[3];
        }

        _ = file.as_ref().unwrap().write_all(&bytes);
    }
}

which I can convert to a WAV file with:

sox -t raw -r 44100 -c 1 -e floating-point -b 32 test.bin test.wav

@chaosprint
Copy link
Owner

@chaosprint It took some fiddling, but I got some initial sound working:

main...PaulBatchelor:glicol:main

For troubleshooting, it was helpful for me to write bytes to disk offline, so I whipped up this program that I run as an example in main:

use glicol::Engine;
use std::fs::File;
use std::io::prelude::*;

fn main() {
    let mut engine = Engine::<32>::new();

    let mut bytes: [u8; 128] = [0; 128];
    let nblks = (44100 * 5) / 32;
    let file = File::create("test.bin");

    engine.update_with_code(r#"o: blsaw 440 >> mul 0.1"#);


    for _ in 0..nblks {
        let blk = engine.next_block(vec![]).0;
        for n in 0..32 {
            let pos = n * 4;
            let b = blk[0][n].to_le_bytes();
            bytes[pos] = b[0];
            bytes[pos + 1] = b[1];
            bytes[pos + 2] = b[2];
            bytes[pos + 3] = b[3];
        }

        _ = file.as_ref().unwrap().write_all(&bytes);
    }
}

which I can convert to a WAV file with:

sox -t raw -r 44100 -c 1 -e floating-point -b 32 test.bin test.wav

Exciting! The offline workflow is very smart.

I am now rewriting the whole repo. This will be super helpful in the next version.

@chaosprint chaosprint added the good first issue Good for newcomers label May 4, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
good first issue Good for newcomers
Projects
None yet
Development

No branches or pull requests

2 participants