Skip to content

Commit

Permalink
Lots of WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
ahicks92 committed Dec 15, 2024
1 parent fc561d2 commit 825d975
Show file tree
Hide file tree
Showing 20 changed files with 721 additions and 128 deletions.
31 changes: 28 additions & 3 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ resolver = "2"
[workspace.dependencies]
ahash = "0.8.3"
anyhow = "1.0.79"
arc-swap = "1.6.0"
arc-swap = "1.7.1"
audio_synchronization = { path = "crates/audio_synchronization" }
arrayvec = "0.7.2"
atomic_refcell = "0.1.9"
Expand Down Expand Up @@ -42,6 +42,7 @@ rand = "0.8.5"
rand_xoshiro = "0.6.0"
rayon = "1.8.0"
reciprocal = "0.1.2"
rpds = "1.1.0"
rubato = "0.14.1"
sharded-slab = "0.1.4"
smallvec = { version = "1.10.0", features = ["write"] }
Expand Down
1 change: 1 addition & 0 deletions crates/synthizer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ num.workspace = true
rand.workspace = true
rand_xoshiro.workspace = true
rayon.workspace = true
rpds.workspace = true
rubato.workspace = true
sharded-slab.workspace = true
smallvec.workspace = true
Expand Down
24 changes: 24 additions & 0 deletions crates/synthizer/examples/two_sine_waves.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use synthizer::*;

fn main() {
let pi2 = 2.0f64 * std::f64::consts::PI;
let chain1 = Chain::new(500f64)
.divide_by_sr()
.periodic_sum(pi2, 0.0f64)
.sin();
let chain2 = Chain::new(600f64)
.divide_by_sr()
.periodic_sum(pi2, 0.0)
.sin();
let added = chain1 + chain2;
let ready = added * Chain::new(0.5f64);
let to_dev = ready.to_audio_device();

let mut synth = Synthesizer::new_audio_defaults();
let _handle = {
let mut batch = synth.batch();
batch.mount(to_dev)
};

std::thread::sleep(std::time::Duration::from_secs(5));
}
92 changes: 90 additions & 2 deletions crates/synthizer/src/chain.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::config;
use crate::core_traits::*;
use crate::signals as sigs;

Expand Down Expand Up @@ -43,10 +44,97 @@ impl<S: IntoSignal> Chain<S> {
/// Start a chain.
///
/// `initial` can be one of a few things. The two most common are another chain or a constant.
pub fn new(initial: S) -> Self {
Self { inner: initial }
pub fn new(initial: S) -> Chain<S> {
Chain { inner: initial }
}

/// Send this chain to the audio device.
pub fn to_audio_device(
self,
) -> Chain<
impl IntoSignal<
Signal = impl Signal<
Input = IntoSignalInput<S>,
Output = (),
Parameters = IntoSignalParameters<S>,
State = IntoSignalState<S>,
>,
>,
>
where
S::Signal: Signal<Output = f64>,
{
Chain {
inner: sigs::AudioOutputSignalConfig::new(self.inner),
}
}

/// Convert this chain's input type to another type, capping the chain with a signal that will use the `Default`
/// implementation on whatever input type is currently wanted.
///
/// This annoying function exists because Rust does not have specialization. What we want to be able to do is to
/// combine signals which don't have inputs with signals that do when performing mathematical operations. Ideally,
/// we would specialize the mathematical traits. Unfortunately we cannot do that. The primary use of this method
/// is essentially to say "okay, I know the other side has some bigger input, but this side doesn't need any input, I
/// promise".
///
/// That is not the only use: sometimes you do legitimately want to feed a signal zeros or some other default value.
pub fn discard_and_default<NewInputType>(
self,
) -> Chain<
impl IntoSignal<
Signal = impl Signal<
Input = NewInputType,
Output = IntoSignalOutput<S>,
State = IntoSignalState<S>,
Parameters = IntoSignalParameters<S>,
>,
>,
>
where
IntoSignalInput<S>: Default,
{
Chain {
inner: sigs::ConsumeInputSignalConfig::<_, NewInputType>::new(self.inner),
}
}

/// Divide this chain's output by the sample rate of the library.
///
/// This is mostly used to convert a frequency (HZ) to an increment per sample, e.g. when building sine waves.
pub fn divide_by_sr(
self,
) -> Chain<impl IntoSignal<Signal = impl Signal<Input = IntoSignalInput<S>, Output = f64>>>
where
S::Signal: Signal<Output = f64>,
IntoSignalInput<S>: Default,
{
let converted = self.output_into::<f64>();
let sr = Chain::new(config::SR as f64).discard_and_default::<IntoSignalInput<S>>();
let done = converted / sr;
Chain { inner: done.inner }
}

/// Convert the output of this chain into a different type.
pub fn output_into<T>(
self,
) -> Chain<
impl IntoSignal<
Signal = impl Signal<
Input = IntoSignalInput<S>,
Output = T,
State = IntoSignalState<S>,
Parameters = IntoSignalParameters<S>,
>,
>,
>
where
T: From<IntoSignalOutput<S>>,
{
Chain {
inner: sigs::ConvertOutputConfig::<S, T>::new(self.inner),
}
}
/// Push a periodic summation onto this chain.
///
/// The input will be taken from whatever signal is here already, and the period is specified herer as a constant.
Expand Down
90 changes: 74 additions & 16 deletions crates/synthizer/src/chain_mathops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,79 @@ use std::ops::*;
use crate::chain::Chain;
use crate::core_traits::*;

pub struct AddSig<L, R>(L, R);
pub struct AddSigConfig<L, R>(L, R);

impl<A, B> Add<Chain<B>> for Chain<A>
where
A: IntoSignal,
B: IntoSignal,
A::Signal: Signal<Output = IntoSignalOutput<B>>,
IntoSignalOutput<A>: Add<IntoSignalOutput<B>>,
{
type Output = Chain<AddSigConfig<A, B>>;

fn add(self, rhs: Chain<B>) -> Self::Output {
Chain {
inner: AddSigConfig(self.inner, rhs.inner),
macro_rules! impl_mathop {
($trait: ident, $signal_name: ident, $signal_config:ident, $method: ident) => {
pub struct $signal_name<L, R>(L, R);
pub struct $signal_config<L, R>(L, R);

impl<A, B> $trait<Chain<B>> for Chain<A>
where
A: IntoSignal,
B: IntoSignal,
A::Signal: Signal<Output = IntoSignalOutput<B>>,
IntoSignalOutput<A>: $trait<IntoSignalOutput<B>>,
{
type Output = Chain<$signal_config<A, B>>;

fn $method(self, rhs: Chain<B>) -> Self::Output {
Chain {
inner: $signal_config(self.inner, rhs.inner),
}
}
}

unsafe impl<S1, S2> Signal for $signal_name<S1, S2>
where
S1: Signal,
S2: Signal<Input = SignalSealedInput<S1>>,
SignalSealedOutput<S1>: $trait<SignalSealedOutput<S2>>,
{
type Input = SignalSealedInput<S1>;
type Output = <SignalSealedOutput<S1> as $trait<SignalSealedOutput<S2>>>::Output;
type Parameters = (SignalSealedParameters<S1>, SignalSealedParameters<S2>);
type State = (SignalSealedState<S1>, SignalSealedState<S2>);

fn tick1<D: SignalDestination<Self::Output>>(
ctx: &mut SignalExecutionContext<'_, '_, Self::State, Self::Parameters>,
input: &'_ Self::Input,
destination: D,
) {
// The complexity here is that we cannot project the context twice. We need the left value first.
let mut left = None;
S1::tick1(&mut ctx.wrap(|s| &mut s.0, |p| &p.0), input, |y| {
left = Some(y)
});
S2::tick1(&mut ctx.wrap(|s| &mut s.1, |p| &p.1), input, |y| {
destination.send(left.unwrap().$method(y));
})
}
}
}

impl<S1, S2> IntoSignal for $signal_config<S1, S2>
where
S1: IntoSignal,
S2: IntoSignal,
$signal_name<S1::Signal, S2::Signal>: Signal,
{
type Signal = $signal_name<S1::Signal, S2::Signal>;

fn into_signal(self) -> crate::Result<Self::Signal> {
let l = self.0.into_signal()?;
let r = self.1.into_signal()?;
Ok($signal_name(l, r))
}
}
};
}

impl_mathop!(Add, AddSig, AddSigConfig, add);
impl_mathop!(Sub, SubSig, SubSigConfig, sub);
impl_mathop!(Mul, MulSig, MulSigConfig, mul);
impl_mathop!(Div, DivSig, DivSigConfig, div);
impl_mathop!(BitAnd, BitAndSig, BitAndSigConfig, bitand);
impl_mathop!(BitOr, BitOrSig, BitOrSigConfig, bitor);
impl_mathop!(BitXor, BitXorSig, BitXorSigConfig, bitxor);

impl_mathop!(Rem, RemSig, RemSigConfig, rem);
impl_mathop!(Shl, ShlSig, ShlSigConfig, shl);
impl_mathop!(Shr, ShrSig, ShrSigConfig, shr);
Loading

0 comments on commit 825d975

Please sign in to comment.