├── .gitignore
├── examples
└── saw.synt
├── Cargo.toml
├── README.md
├── src
├── interpreter
│ ├── lib.rs
│ ├── audio
│ │ ├── filewriter.rs
│ │ ├── stream.rs
│ │ └── mod.rs
│ ├── ident.rs
│ ├── types.rs
│ ├── common.rs
│ ├── issue.rs
│ ├── lexer.rs
│ ├── tests.rs
│ ├── functions.rs
│ ├── scope.rs
│ ├── ast.rs
│ ├── compiler.rs
│ ├── tokens.rs
│ ├── typecheck.rs
│ ├── parser.rs
│ └── codegen.rs
└── cli
│ └── main.rs
├── tests
├── lexer.rs
├── parser.rs
├── typecheck.rs
└── codegen.rs
├── Cargo.lock
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | *.swp
3 |
--------------------------------------------------------------------------------
/examples/saw.synt:
--------------------------------------------------------------------------------
1 | pi = 3.141592653589;
2 |
3 | // additively synthesize a sawtooth wave
4 | // n is number of harmonics
5 | saw freq, amp, time, n=50 {
6 | sin(freq*n*time*pi*2)*amp/n/pi;
7 | saw(freq, amp, time, n-1) if n > 1 else 0;
8 | }
9 |
10 | // use simple math to make a sawtooth wave
11 | fastsaw freq, amp, time {
12 | freq*time%1*amp;
13 | }
14 |
15 | main time {
16 | saw [
17 | freq=256,
18 | amp=0.8,
19 | time
20 | ];
21 | }
22 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 |
3 | name = "synthizer"
4 | version = "0.0.1"
5 | authors = ["Noah Weninger"]
6 |
7 | [dependencies]
8 | rustc-serialize = "*"
9 | regex = "*"
10 | regex_macros = "*"
11 | cbox = "*"
12 | bit-set = "*"
13 | sound_stream = "*"
14 | hound = "*"
15 | vec_map = "*"
16 | docopt = "*"
17 | docopt_macros = "*"
18 | llvm-sys = "*"
19 | clippy = "*"
20 |
21 | [dependencies.llvm-alt]
22 | git = "https://github.com/nwoeanhinnogaehr/llvm-rs"
23 |
24 | [lib]
25 | name = "interpreter"
26 | path = "src/interpreter/lib.rs"
27 |
28 | [[bin]]
29 | name = "synthizer"
30 | path = "src/cli/main.rs"
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Synthizer
2 | A simple experimental language for real time additive audio synthesis, intended for the creation of unique mathematical sounds.
3 |
4 | ## What works so far
5 | - Lexing
6 | - Parsing
7 | - Typechecking
8 | - Codegen (LLVM backend)
9 | - Audio output (WAV)
10 | - Real time audio output
11 |
12 | ## Todo
13 | - Documentation
14 | - GPU backend
15 | - GUI
16 | - MIDI (maybe) or at least some interface for a DAW
17 | - Graphical representations of the sound
18 |
19 | ## Example code
20 | Check the examples directory for example synths.
21 | You can listen to them in real time with:
22 | ```
23 | cargo run -- stream examples/???.synt
24 | ```
25 |
--------------------------------------------------------------------------------
/src/interpreter/lib.rs:
--------------------------------------------------------------------------------
1 | #![feature(plugin, optin_builtin_traits, vec_push_all)]
2 | #![plugin(regex_macros, docopt_macros)]
3 |
4 | extern crate regex;
5 | extern crate llvm;
6 | extern crate cbox;
7 | extern crate bit_set;
8 | extern crate sound_stream;
9 | extern crate hound;
10 | extern crate vec_map;
11 | extern crate llvm_sys;
12 |
13 | pub mod common;
14 | pub mod ident;
15 | #[macro_use] pub mod types;
16 | pub mod tokens;
17 | pub mod ast;
18 | pub mod issue;
19 | pub mod lexer;
20 | pub mod parser;
21 | pub mod functions;
22 | pub mod typecheck;
23 | pub mod codegen;
24 | pub mod scope;
25 | pub mod compiler;
26 | pub mod audio;
27 |
28 | #[macro_use]
29 | pub mod tests;
30 |
--------------------------------------------------------------------------------
/tests/lexer.rs:
--------------------------------------------------------------------------------
1 | #[macro_use(run_test)]
2 | extern crate interpreter;
3 |
4 | #[test]
5 | fn all_tokens() {
6 | run_test!(
7 | should_pass(lex)
8 | => r"
9 | 1
10 | 1.1
11 | .1
12 | 1.
13 | 1.1e5
14 | 1e5
15 | .1e5
16 | 1.e5
17 | 1E5
18 |
19 | abcABC_~'0123
20 |
21 | + - * / ^ ^^ >= <= < > ! % && || == !=
22 | if else . , = : ; ? ( ) { } [ ] \ @
23 | true false
24 | // #&*GR^@&(G#^&(G@&*YFD*B@Y^(VT@^(f367g9@&*
25 | "
26 | );
27 | }
28 |
29 | #[test]
30 | fn non_tokens() {
31 | run_test!(
32 | should_fail(lex)
33 | => "` # $ & | ' \""
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/src/interpreter/audio/filewriter.rs:
--------------------------------------------------------------------------------
1 | use super::super::compiler::Compiler;
2 | use super::render_samples;
3 |
4 | use hound;
5 |
6 | pub fn write_wav(compiler: &Compiler, filename: String, length: f32) {
7 | let spec = hound::WavSpec {
8 | channels: 1,
9 | sample_rate: 44100,
10 | bits_per_sample: 16
11 | };
12 | let rx = render_samples(compiler, spec.sample_rate).unwrap();
13 |
14 | let mut writer = hound::WavWriter::create(filename, spec).unwrap();
15 | let mut buffer = rx.recv().unwrap();
16 | let mut buf_ptr = 0;
17 | for _ in 0..(length*spec.sample_rate as f32) as usize {
18 | let sample = buffer[buf_ptr].max(-1.0).min(1.0);
19 | let amplitude = ::std::i16::MAX as f32;
20 | writer.write_sample((sample * amplitude) as i16).unwrap();
21 | buf_ptr += 1;
22 | if buf_ptr >= buffer.len() {
23 | buffer = rx.recv().unwrap();
24 | buf_ptr = 0;
25 | }
26 | }
27 | writer.finalize().unwrap();
28 | }
29 |
--------------------------------------------------------------------------------
/src/interpreter/audio/stream.rs:
--------------------------------------------------------------------------------
1 | use super::super::compiler::Compiler;
2 | use super::render_samples;
3 |
4 | use sound_stream::{CallbackFlags, CallbackResult, SoundStream, Settings, StreamParams};
5 |
6 | pub fn play_stream(compiler: &Compiler) {
7 | let rx = render_samples(compiler, 48000).unwrap();
8 | let mut buf_ptr = 0usize;
9 | let mut buffer = rx.recv().unwrap();
10 | let callback = Box::new(move |output: &mut[f32], settings: Settings, _: f64, _: CallbackFlags| {
11 | let mut max = 0f32;
12 | for frame in output.chunks_mut(settings.channels as usize) {
13 | let amp = buffer[buf_ptr];
14 | if amp > max {
15 | max = amp;
16 | }
17 | for channel in frame {
18 | *channel = amp;
19 | }
20 | buf_ptr += 1;
21 | if buf_ptr >= buffer.len() {
22 | buf_ptr = 0;
23 | buffer = rx.recv().unwrap();
24 | }
25 | }
26 | //if max < 0.0001 { CallbackResult::Complete } else { CallbackResult::Continue }
27 | CallbackResult::Continue
28 | });
29 |
30 | // Construct the default, non-blocking output stream and run our callback.
31 | let params = StreamParams::new().suggest_latency(0.05);
32 | let stream = SoundStream::new().output(params).run_callback(callback).unwrap();
33 |
34 | while let Ok(true) = stream.is_active() {}
35 | }
36 |
--------------------------------------------------------------------------------
/src/cli/main.rs:
--------------------------------------------------------------------------------
1 | #![feature(plugin, optin_builtin_traits)]
2 | #![plugin(regex_macros, docopt_macros)]
3 |
4 | extern crate docopt;
5 | extern crate rustc_serialize;
6 | extern crate vec_map;
7 | #[macro_use]
8 | extern crate interpreter;
9 |
10 | docopt!(Args, "
11 | Usage:
12 | synthizer stream
13 | synthizer write