From 64201f47f5a17bb7dbe257a401986afe1e172111 Mon Sep 17 00:00:00 2001 From: Jeron Aldaron Lau Date: Sat, 4 May 2024 23:11:04 -0500 Subject: [PATCH] Organize `tree` module --- .github/workflows/ci.yml | 6 +- CHANGELOG.md | 4 + Cargo.toml | 5 +- README.md | 4 +- examples/sine.rs | 15 ++- rust-toolchain.toml | 3 + src/file.rs | 11 +-- src/lib.rs | 11 +-- src/math.rs | 9 +- src/next.rs | 11 +-- src/noise.rs | 9 -- src/noise/pink.rs | 9 -- src/noise/white.rs | 9 -- src/ops.rs | 11 +-- src/ops/clip.rs | 9 -- src/ops/far.rs | 9 -- src/ops/gain.rs | 9 -- src/ops/gate.rs | 9 -- src/ops/invert.rs | 9 -- src/ops/limiter.rs | 9 -- src/ops/max.rs | 9 -- src/ops/min.rs | 9 -- src/ops/near.rs | 9 -- src/ops/room.rs | 9 -- src/osc.rs | 11 +-- src/osc/pulse.rs | 9 -- src/osc/sawtooth.rs | 9 -- src/osc/sine.rs | 9 -- src/osc/triangle.rs | 9 -- src/synth.rs | 9 -- src/tree.rs | 192 --------------------------------------- src/tree/chunk.rs | 29 ++++++ src/tree/line.rs | 15 +++ src/tree/mod.rs | 52 +++++++++++ src/tree/osc/mod.rs | 9 ++ src/tree/osc/osc.rs | 28 ++++++ src/tree/osc/sine.rs | 23 +++++ src/tree/synth.rs | 71 +++++++++++++++ 38 files changed, 259 insertions(+), 423 deletions(-) create mode 100644 rust-toolchain.toml delete mode 100644 src/tree.rs create mode 100644 src/tree/chunk.rs create mode 100644 src/tree/line.rs create mode 100644 src/tree/mod.rs create mode 100644 src/tree/osc/mod.rs create mode 100644 src/tree/osc/osc.rs create mode 100644 src/tree/osc/sine.rs create mode 100644 src/tree/synth.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 01801d5..2db2708 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - tc: [1.60.0, stable, beta, nightly] + tc: [beta, nightly] steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 @@ -25,7 +25,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - tc: [1.60.0] + tc: [1.70.0] cc: - aarch64-fuchsia - aarch64-linux-android @@ -52,7 +52,7 @@ jobs: strategy: matrix: os: [macos-latest] - tc: [1.60.0] + tc: [1.70.0] cc: [aarch64-apple-ios] steps: - uses: actions/checkout@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 128347c..8ed8236 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to `twang` will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://github.com/AldaronLau/semver). +## [0.10.0] - Unreleased +### Changed + - Bump MSRV to 1.70.0 + ## [0.9.0] - 2022-10-23 ### Changed - Bump MSRV to 1.60.0 diff --git a/Cargo.toml b/Cargo.toml index 713f8fc..a0c3e16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,10 +9,10 @@ [package] name = "twang" -version = "0.9.0" +version = "0.10.0" license = "Apache-2.0 OR BSL-1.0 OR MIT" edition = "2021" -rust-version = "1.60.0" +rust-version = "1.70.0" documentation = "https://docs.rs/twang" homepage = "https://aldaronlau.github.io/twang" repository = "https://github.com/AldaronLau/twang" @@ -37,6 +37,7 @@ include = ["README.md", "Cargo.toml", "src/*", "build.rs"] [dependencies] libm = "0.2" fon = "0.6" +traitful = "0.3" [dev-dependencies] splotch = "0.0.1" diff --git a/README.md b/README.md index 2c89a29..2d8142c 100644 --- a/README.md +++ b/README.md @@ -104,10 +104,12 @@ Examples can be found in the [Documentation](https://docs.rs/twang) and the examples folder. ## MSRV -The minimum supported Rust version of twang is 1.60.0. MSRV may only be updated +The minimum supported Rust version of twang is 1.70.0. MSRV may only be updated when increasing the leftmost version number of twang. ## License +Copyright © 2018-2024 The Twang Contributors. + Licensed under either of - Apache License, Version 2.0 ([LICENSE_APACHE_2_0.txt](https://github.com/AldaronLau/twang/blob/stable/LICENSE_APACHE_2_0.txt) or diff --git a/examples/sine.rs b/examples/sine.rs index 6fea491..986ffa8 100644 --- a/examples/sine.rs +++ b/examples/sine.rs @@ -1,22 +1,19 @@ use fon::{chan::Ch16, Audio, Frame}; -use twang::tree::{Synth, Sine, Hz, Wave}; +use twang::tree::{osc::{Osc, Sine}, Synth, Wave}; mod wav; - -// Define waveform -const fn waveform() -> impl Wave { - Sine(Hz(440.0)) -} +//mod plot; fn main() { + // Define waveform + let waveform = const { Osc(440.0).sine() }; // Initialize audio, and create synthesizer let mut audio = Audio::::with_silence(48_000, 48_000 * 5); - let mut synth = Synth::new(waveform()); + let mut synth = Synth::new(waveform); // Synthesize 5 seconds of audio synth.stream(audio.sink(), &[]); - // Plot synthesized audio, and write to a WAV file - // plot::write(&audio); + //plot::write(&audio); wav::write(audio, "sine.wav").expect("Failed to write WAV file"); } diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..7a98ea0 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +# Currently using beta for tests, MSRV of examples is 1.79 +channel = "beta" diff --git a/src/file.rs b/src/file.rs index 6b838e4..b351534 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,14 +1,7 @@ -// Copyright © 2018-2022 The Twang Contributors. -// -// Licensed under any of: -// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -// - MIT License (https://mit-license.org/) -// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). - //! Twang synthesis file format +#![allow(warnings)] + use alloc::vec::Vec; use fon::chan::{Ch32, Channel}; use fon::{Audio, Sink}; diff --git a/src/lib.rs b/src/lib.rs index 4fe9844..6bbbd94 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,3 @@ -// Copyright © 2018-2022 The Twang Contributors. -// -// Licensed under any of: -// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -// - MIT License (https://mit-license.org/) -// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). - //! Library for pure Rust advanced audio synthesis. //! //! Most audio DSP (Digital Signal Processing) libraries have a concept of an @@ -105,8 +96,8 @@ variant_size_differences )] -extern crate std; // FIXME: for debugging extern crate alloc; +extern crate std; // FIXME: for debugging mod math; mod synth; diff --git a/src/math.rs b/src/math.rs index 2f5ae6a..50b3e82 100644 --- a/src/math.rs +++ b/src/math.rs @@ -1,11 +1,4 @@ -// Copyright © 2018-2022 The Twang Contributors. -// -// Licensed under any of: -// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -// - MIT License (https://mit-license.org/) -// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). +#![allow(warnings)] use core::ops::Rem; diff --git a/src/next.rs b/src/next.rs index f6c8b02..dee0579 100644 --- a/src/next.rs +++ b/src/next.rs @@ -1,12 +1,3 @@ -// Copyright © 2018-2022 The Twang Contributors. -// -// Licensed under any of: -// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -// - MIT License (https://mit-license.org/) -// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). - //! # Twang File Format (Alpha) //! The twang file format is a simple list of 32-bit instructions and data. The //! file must begin with magic bytes: `\xFF\xFETwAnG\0`. Suggested file @@ -117,6 +108,8 @@ //! ### 17 - MAX //! - `index` points to input node. +#![allow(warnings)] + use fon::Sink; use alloc::vec::Vec; diff --git a/src/noise.rs b/src/noise.rs index acad1c0..96b0a2e 100644 --- a/src/noise.rs +++ b/src/noise.rs @@ -1,12 +1,3 @@ -// Copyright © 2018-2022 The Twang Contributors. -// -// Licensed under any of: -// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -// - MIT License (https://mit-license.org/) -// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). - //! A collection of noise generators. mod pink; diff --git a/src/noise/pink.rs b/src/noise/pink.rs index 2cbd680..8c9c31d 100644 --- a/src/noise/pink.rs +++ b/src/noise/pink.rs @@ -1,12 +1,3 @@ -// Copyright © 2018-2022 The Twang Contributors. -// -// Licensed under any of: -// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -// - MIT License (https://mit-license.org/) -// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). - use fon::chan::Ch16; // Constants PFIRA and PFIRB diff --git a/src/noise/white.rs b/src/noise/white.rs index 544643a..5281c20 100644 --- a/src/noise/white.rs +++ b/src/noise/white.rs @@ -1,12 +1,3 @@ -// Copyright © 2018-2022 The Twang Contributors. -// -// Licensed under any of: -// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -// - MIT License (https://mit-license.org/) -// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). - use core::num::Wrapping; use fon::chan::Ch24; diff --git a/src/ops.rs b/src/ops.rs index 5ffac06..a76d6e3 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -1,14 +1,7 @@ -// Copyright © 2018-2022 The Twang Contributors. -// -// Licensed under any of: -// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -// - MIT License (https://mit-license.org/) -// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). - //! A collection of auditory effects. +#![allow(warnings)] + mod clip; mod far; mod gain; diff --git a/src/ops/clip.rs b/src/ops/clip.rs index ceb43ad..4f44c73 100644 --- a/src/ops/clip.rs +++ b/src/ops/clip.rs @@ -1,12 +1,3 @@ -// Copyright © 2018-2022 The Twang Contributors. -// -// Licensed under any of: -// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -// - MIT License (https://mit-license.org/) -// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). - #[cfg(not(test))] use crate::math::Libm; diff --git a/src/ops/far.rs b/src/ops/far.rs index f396ae9..cb7cd56 100644 --- a/src/ops/far.rs +++ b/src/ops/far.rs @@ -1,12 +1,3 @@ -// Copyright © 2018-2022 The Twang Contributors. -// -// Licensed under any of: -// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -// - MIT License (https://mit-license.org/) -// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). - #[cfg(not(test))] use crate::math::Libm; diff --git a/src/ops/gain.rs b/src/ops/gain.rs index c87a92c..df16604 100644 --- a/src/ops/gain.rs +++ b/src/ops/gain.rs @@ -1,12 +1,3 @@ -// Copyright © 2018-2022 The Twang Contributors. -// -// Licensed under any of: -// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -// - MIT License (https://mit-license.org/) -// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). - #[cfg(not(test))] use crate::math::Libm; diff --git a/src/ops/gate.rs b/src/ops/gate.rs index b25bac2..a22d212 100644 --- a/src/ops/gate.rs +++ b/src/ops/gate.rs @@ -1,12 +1,3 @@ -// Copyright © 2018-2022 The Twang Contributors. -// -// Licensed under any of: -// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -// - MIT License (https://mit-license.org/) -// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). - use fon::chan::{Ch32, Channel}; /// Noise gate. diff --git a/src/ops/invert.rs b/src/ops/invert.rs index 4a7df97..4393bff 100644 --- a/src/ops/invert.rs +++ b/src/ops/invert.rs @@ -1,12 +1,3 @@ -// Copyright © 2018-2022 The Twang Contributors. -// -// Licensed under any of: -// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -// - MIT License (https://mit-license.org/) -// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). - use fon::chan::Ch32; /// Signal inverter. diff --git a/src/ops/limiter.rs b/src/ops/limiter.rs index f548649..0701176 100644 --- a/src/ops/limiter.rs +++ b/src/ops/limiter.rs @@ -1,12 +1,3 @@ -// Copyright © 2018-2022 The Twang Contributors. -// -// Licensed under any of: -// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -// - MIT License (https://mit-license.org/) -// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). - #[cfg(not(test))] use crate::math::Libm; diff --git a/src/ops/max.rs b/src/ops/max.rs index 63f16c5..8656d97 100644 --- a/src/ops/max.rs +++ b/src/ops/max.rs @@ -1,12 +1,3 @@ -// Copyright © 2018-2022 The Twang Contributors. -// -// Licensed under any of: -// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -// - MIT License (https://mit-license.org/) -// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). - use fon::chan::{Ch32, Channel}; /// Maximum value of two samples (warning: -0.25 > -0.5, if you want maximum diff --git a/src/ops/min.rs b/src/ops/min.rs index c95c28f..a7a8f4b 100644 --- a/src/ops/min.rs +++ b/src/ops/min.rs @@ -1,12 +1,3 @@ -// Copyright © 2018-2022 The Twang Contributors. -// -// Licensed under any of: -// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -// - MIT License (https://mit-license.org/) -// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). - use fon::chan::{Ch32, Channel}; /// Minimum value of two samples (warning: -0.5 < -0.25, if you want minimum diff --git a/src/ops/near.rs b/src/ops/near.rs index a52bc67..5cd926d 100644 --- a/src/ops/near.rs +++ b/src/ops/near.rs @@ -1,12 +1,3 @@ -// Copyright © 2018-2022 The Twang Contributors. -// -// Licensed under any of: -// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -// - MIT License (https://mit-license.org/) -// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). - #[cfg(not(test))] use crate::math::Libm; diff --git a/src/ops/room.rs b/src/ops/room.rs index 34dc6e9..4627d23 100644 --- a/src/ops/room.rs +++ b/src/ops/room.rs @@ -1,12 +1,3 @@ -// Copyright © 2018-2022 The Twang Contributors. -// -// Licensed under any of: -// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -// - MIT License (https://mit-license.org/) -// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). - use alloc::collections::VecDeque; use fon::chan::{Ch32, Channel}; diff --git a/src/osc.rs b/src/osc.rs index e32ca7b..4526794 100644 --- a/src/osc.rs +++ b/src/osc.rs @@ -1,14 +1,7 @@ -// Copyright © 2018-2022 The Twang Contributors. -// -// Licensed under any of: -// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -// - MIT License (https://mit-license.org/) -// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). - //! A collection of basic oscillators (wave generators). +#![allow(warnings)] + mod pulse; mod sawtooth; mod sine; diff --git a/src/osc/pulse.rs b/src/osc/pulse.rs index 7b6b7a0..c94d9c0 100644 --- a/src/osc/pulse.rs +++ b/src/osc/pulse.rs @@ -1,12 +1,3 @@ -// Copyright © 2018-2022 The Twang Contributors. -// -// Licensed under any of: -// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -// - MIT License (https://mit-license.org/) -// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). - #[cfg(not(test))] use crate::math::Libm; diff --git a/src/osc/sawtooth.rs b/src/osc/sawtooth.rs index a1b2070..e630012 100644 --- a/src/osc/sawtooth.rs +++ b/src/osc/sawtooth.rs @@ -1,12 +1,3 @@ -// Copyright © 2018-2022 The Twang Contributors. -// -// Licensed under any of: -// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -// - MIT License (https://mit-license.org/) -// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). - use fon::chan::Ch32; /// Sawtooth wave generator. diff --git a/src/osc/sine.rs b/src/osc/sine.rs index 815e262..3d9ea0c 100644 --- a/src/osc/sine.rs +++ b/src/osc/sine.rs @@ -1,12 +1,3 @@ -// Copyright © 2018-2022 The Twang Contributors. -// -// Licensed under any of: -// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -// - MIT License (https://mit-license.org/) -// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). - #[cfg(not(test))] use crate::math::Libm; diff --git a/src/osc/triangle.rs b/src/osc/triangle.rs index f6a6861..4eb1338 100644 --- a/src/osc/triangle.rs +++ b/src/osc/triangle.rs @@ -1,12 +1,3 @@ -// Copyright © 2018-2022 The Twang Contributors. -// -// Licensed under any of: -// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -// - MIT License (https://mit-license.org/) -// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). - #[cfg(not(test))] use crate::math::Libm; diff --git a/src/synth.rs b/src/synth.rs index 278d12a..c0c09e9 100644 --- a/src/synth.rs +++ b/src/synth.rs @@ -1,12 +1,3 @@ -// Copyright © 2018-2022 The Twang Contributors. -// -// Licensed under any of: -// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -// - MIT License (https://mit-license.org/) -// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). - use alloc::boxed::Box; use core::fmt::{Debug, Error, Formatter}; use fon::chan::{Ch32, Channel}; diff --git a/src/tree.rs b/src/tree.rs deleted file mode 100644 index 24a8815..0000000 --- a/src/tree.rs +++ /dev/null @@ -1,192 +0,0 @@ -macro_rules! const_postfix_waveform { - () => { - const fn sine(self) -> Sine { - Sine(self) - } - }; - ($type:ty) => { - impl $type { - const_postfix_waveform!(); - } - }; - ($type:ty, $($generic:ident),+) => { - impl<$($generic),+> $type { - const_postfix_waveform!(); - } - }; -} - -const_postfix_waveform!(Hz); -const_postfix_waveform!(Line); -const_postfix_waveform!(Sine, T); - -use core::{time::Duration, f32::consts}; - -#[derive(Debug)] -pub struct Chunk([f32; 32]); - -impl Chunk { - #[inline(always)] - fn for_each_sample(&mut self, f: impl FnMut(&mut f32)) { - self.0.iter_mut().for_each(f); - } - - #[inline(always)] - fn offset(&mut self, amt: f32) { - self.for_each_sample(|sample| *sample += amt); - } - - #[inline(always)] - fn amplify(&mut self, amt: f32) { - self.for_each_sample(|sample| *sample *= amt); - } - - #[inline(always)] - fn cosine(&mut self) { - self.for_each_sample(|sample| *sample = libm::cosf(*sample)); - } - - #[inline(always)] - fn invert(&mut self) { - self.for_each_sample(|sample| *sample = -*sample); - } -} - -pub trait Wave { - /// Synthesize a chunk of audio. - /// - /// - `elapsed` is nanoseconds since the start of synthesis (up to about - /// 1169 years) - /// - `interval` is nanoseconds in the chunk's interval - #[must_use] - fn synthesize(&self, elapsed: u64, interval: u64) -> Chunk; -} - -impl Wave for &T -where - T: Wave, -{ - fn synthesize(&self, elapsed: u64, interval: u64) -> Chunk { - (**self).synthesize(elapsed, interval) - } -} - -/// Constant signal -#[derive(Debug)] -pub struct Line(pub f32); - -impl Wave for Line { - fn synthesize(&self, _elapsed: u64, _interval: u64) -> Chunk { - Chunk([self.0; 32]) - } -} - -/// Fixed frequency phase counter -/// -/// Produces a sawtooth wave -#[derive(Debug)] -pub struct Hz(pub f32); - -impl Wave for Hz { - fn synthesize(&self, elapsed: u64, interval: u64) -> Chunk { - let hz_32 = (self.0 * 32.0) as u64; - let phase = 32_000_000_000 / hz_32; - let offset = elapsed % phase; - let mut i = 0; - let mut chunk = Chunk([0.0; 32]); - - chunk.for_each_sample(|sample| { - let place = i * interval / 32 + offset; - - *sample = (place as f32 / phase as f32) % 1.0; - i += 1; - }); - chunk.amplify(-2.0); - chunk.offset(1.0); - chunk - } -} - -/// Sine wave -/// -/// Takes phase (-1 to 1) as input -#[derive(Debug)] -pub struct Sine(pub I); - -impl Wave for Sine -where - I: Wave, -{ - fn synthesize(&self, elapsed: u64, interval: u64) -> Chunk { - let mut chunk = self.0.synthesize(elapsed, interval); - - chunk.amplify(consts::PI); - chunk.cosine(); - chunk.invert(); - chunk - } -} - -#[derive(Debug)] -pub struct Synth { - wave: W, - elapsed: u64, - cursor: usize, - chunk: Chunk, -} - -impl Synth -where W: Wave -{ - pub fn new(wave: W) -> Self { - Self { - wave, - elapsed: 0, - cursor: 32, - chunk: Chunk([0.0; 32]), - } - } - - /// Run synthesis with user parameters, streaming output into the provided - /// [`Sink`] - pub fn stream(&mut self, mut sink: K, params: &[f32]) - where - Ch: fon::chan::Channel + From, - K: fon::Sink, - { - let sample_rate: u32 = sink.sample_rate().into(); - let synth_iter = SynthIter(self, sample_rate); - - sink.sink_with(&mut synth_iter.map(|x| x.to())); - } - - fn synthesize(&mut self, sample_rate: u32) -> f32 { - if self.cursor == 32 { - self.cursor = 0; - - let interval = 32_000_000_000 / u64::from(sample_rate); - - self.chunk = self.wave.synthesize(self.elapsed, interval); - self.elapsed += interval; - } - - let cursor = self.cursor; - - self.cursor += 1; - self.chunk.0[cursor] - } -} - -struct SynthIter<'a, W>(&'a mut Synth, u32); - -impl Iterator for SynthIter<'_, W> -where W: Wave -{ - type Item = fon::Frame; - - fn next(&mut self) -> Option { - let Self(synth, sample_rate) = self; - - Some(synth.synthesize(*sample_rate).into()) - } -} diff --git a/src/tree/chunk.rs b/src/tree/chunk.rs new file mode 100644 index 0000000..0164376 --- /dev/null +++ b/src/tree/chunk.rs @@ -0,0 +1,29 @@ +#[derive(Debug)] +pub struct Chunk(pub(super) [f32; 32]); + +impl Chunk { + #[inline(always)] + pub(super) fn for_each_sample(&mut self, f: impl FnMut(&mut f32)) { + self.0.iter_mut().for_each(f); + } + + #[inline(always)] + pub(super) fn offset(&mut self, amt: f32) { + self.for_each_sample(|sample| *sample += amt); + } + + #[inline(always)] + pub(super) fn amplify(&mut self, amt: f32) { + self.for_each_sample(|sample| *sample *= amt); + } + + #[inline(always)] + pub(super) fn cosine(&mut self) { + self.for_each_sample(|sample| *sample = libm::cosf(*sample)); + } + + #[inline(always)] + pub(super) fn invert(&mut self) { + self.for_each_sample(|sample| *sample = -*sample); + } +} diff --git a/src/tree/line.rs b/src/tree/line.rs new file mode 100644 index 0000000..56c9f41 --- /dev/null +++ b/src/tree/line.rs @@ -0,0 +1,15 @@ +//! Line signals + +const_postfix_waveform!(Line); + +use crate::tree::{Wave, Chunk}; + +/// Constant signal +#[derive(Debug)] +pub struct Line(pub f32); + +impl Wave for Line { + fn synthesize(&self, _elapsed: u64, _interval: u64) -> Chunk { + Chunk([self.0; 32]) + } +} diff --git a/src/tree/mod.rs b/src/tree/mod.rs new file mode 100644 index 0000000..f185408 --- /dev/null +++ b/src/tree/mod.rs @@ -0,0 +1,52 @@ +//! Library for pure Rust advanced audio synthesis. + +macro_rules! const_postfix_waveform { + () => { + /// Postfix helper for wrapping synth instruction with [`osc::Sine`]. + /// + /// [`osc::Sine`]: crate::tree::osc::Sine + pub const fn sine(self) -> crate::tree::osc::Sine { + crate::tree::osc::Sine(self) + } + }; + ($type:ty) => { + impl $type { + const_postfix_waveform!(); + } + }; + ($type:ty, $($generic:ident),+) => { + impl<$($generic),+> $type { + const_postfix_waveform!(); + } + }; +} + +mod chunk; +mod synth; +pub mod osc; +pub mod line; + +use self::chunk::Chunk; +pub use self::synth::Synth; + +/// Trait implemented by all waveforms +#[traitful::seal(osc::Osc, for osc::Sine, line::Line, for &T)] +pub trait Wave { + /// Synthesize a chunk of audio. + /// + /// - `elapsed` is nanoseconds since the start of synthesis (up to about + /// 1169 years) + /// - `interval` is nanoseconds in the chunk's interval + #[must_use] + #[doc(hidden)] + fn synthesize(&self, elapsed: u64, interval: u64) -> Chunk; +} + +impl Wave for &T +where + T: Wave, +{ + fn synthesize(&self, elapsed: u64, interval: u64) -> Chunk { + (**self).synthesize(elapsed, interval) + } +} diff --git a/src/tree/osc/mod.rs b/src/tree/osc/mod.rs new file mode 100644 index 0000000..317f630 --- /dev/null +++ b/src/tree/osc/mod.rs @@ -0,0 +1,9 @@ +//! Basic oscillators (wave generators) + +const_postfix_waveform!(Osc); +const_postfix_waveform!(Sine, T); + +mod sine; +mod osc; + +pub use self::{sine::Sine, osc::Osc}; diff --git a/src/tree/osc/osc.rs b/src/tree/osc/osc.rs new file mode 100644 index 0000000..86e0111 --- /dev/null +++ b/src/tree/osc/osc.rs @@ -0,0 +1,28 @@ +use crate::tree::{Wave, Chunk}; + +/// Fixed frequency oscillator (sawtooth wave) +/// +/// This is the most basic oscillator, which all other oscillators depend on for +/// their phase. +#[derive(Debug)] +pub struct Osc(pub f32); + +impl Wave for Osc { + fn synthesize(&self, elapsed: u64, interval: u64) -> Chunk { + let hz_32 = (self.0 * 32.0) as u64; + let phase = 32_000_000_000 / hz_32; + let offset = elapsed % phase; + let mut i = 0; + let mut chunk = Chunk([0.0; 32]); + + chunk.for_each_sample(|sample| { + let place = i * interval / 32 + offset; + + *sample = (place as f32 / phase as f32) % 1.0; + i += 1; + }); + chunk.amplify(-2.0); + chunk.offset(1.0); + chunk + } +} diff --git a/src/tree/osc/sine.rs b/src/tree/osc/sine.rs new file mode 100644 index 0000000..8babf9d --- /dev/null +++ b/src/tree/osc/sine.rs @@ -0,0 +1,23 @@ +use core::f32::consts; + +use crate::tree::{Wave, Chunk}; + +/// Sine wave +/// +/// Takes phase (-1 to 1) as input +#[derive(Debug)] +pub struct Sine(pub I); + +impl Wave for Sine +where + I: Wave, +{ + fn synthesize(&self, elapsed: u64, interval: u64) -> Chunk { + let mut chunk = self.0.synthesize(elapsed, interval); + + chunk.amplify(consts::PI); + chunk.cosine(); + chunk.invert(); + chunk + } +} diff --git a/src/tree/synth.rs b/src/tree/synth.rs new file mode 100644 index 0000000..72d06c6 --- /dev/null +++ b/src/tree/synth.rs @@ -0,0 +1,71 @@ +use fon::{Sink, chan::{Channel, Ch32}}; + +use crate::tree::{Chunk, Wave}; + +/// A streaming synthesizer +#[derive(Debug)] +pub struct Synth { + wave: W, + elapsed: u64, + cursor: usize, + chunk: Chunk, +} + +impl Synth +where + W: Wave, +{ + /// Create a new synthesizer based on a waveform + pub fn new(wave: W) -> Self { + Self { + wave, + elapsed: 0, + cursor: 32, + chunk: Chunk([0.0; 32]), + } + } + + /// Run synthesis with user parameters, streaming output into the provided + /// [`Sink`] + pub fn stream(&mut self, mut sink: K, params: &[f32]) + where + Ch: Channel + From, + K: Sink, + { + let sample_rate: u32 = sink.sample_rate().into(); + let synth_iter = SynthIter(self, sample_rate); + + sink.sink_with(&mut synth_iter.map(|x| x.to())); + } + + fn synthesize(&mut self, sample_rate: u32) -> f32 { + if self.cursor == 32 { + self.cursor = 0; + + let interval = 32_000_000_000 / u64::from(sample_rate); + + self.chunk = self.wave.synthesize(self.elapsed, interval); + self.elapsed += interval; + } + + let cursor = self.cursor; + + self.cursor += 1; + self.chunk.0[cursor] + } +} + +struct SynthIter<'a, W>(&'a mut Synth, u32); + +impl Iterator for SynthIter<'_, W> +where + W: Wave, +{ + type Item = fon::Frame; + + fn next(&mut self) -> Option { + let Self(synth, sample_rate) = self; + + Some(synth.synthesize(*sample_rate).into()) + } +}