Skip to content

Commit

Permalink
Add a .join(), then modify audio output to work over frames and use i…
Browse files Browse the repository at this point in the history
…t in a binaural beats example
  • Loading branch information
ahicks92 committed Dec 27, 2024
1 parent fc33612 commit 8cc462b
Show file tree
Hide file tree
Showing 10 changed files with 194 additions and 16 deletions.
6 changes: 5 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@
"editor.rulers": [
120
],
"editor.insertSpaces": true
"editor.insertSpaces": true,
"Lua.workspace.userThirdParty": [
"c:\\Users\\Austin\\AppData\\Roaming\\Code\\User\\workspaceStorage\\4ec82e1d7f7f9e70c608dd5d24e4a9d2\\justarandomgeek.factoriomod-debug\\sumneko-3rd"
],
"Lua.workspace.checkThirdParty": "ApplyInMemory"
}
37 changes: 37 additions & 0 deletions crates/synthizer/examples/binaural_beats.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use anyhow::Result;
use synthizer::*;

const L_FREQ: f64 = 200.0;
const R_FREQ: f64 = 203.0;

fn main() -> Result<()> {
let mut synth = Synthesizer::new_default_output().unwrap();

let pi2 = 2.0f64 * std::f64::consts::PI;

let left = Chain::new(L_FREQ)
.divide_by_sr()
.periodic_sum(1.0f64, 0.0f64)
.inline_mul(Chain::new(pi2))
.sin()
.boxed();
let right = Chain::new(R_FREQ)
.divide_by_sr()
.periodic_sum(1.0f64, 0.0)
.inline_mul(Chain::new(pi2))
.sin()
.boxed();

let ready = left.join(right).discard_and_default::<()>();

let to_dev = ready.to_audio_device(ChannelFormat::Stereo);

let _handle = {
let mut batch = synth.batch();
batch.mount(to_dev)?
};

std::thread::sleep(std::time::Duration::from_secs(5));

Ok(())
}
2 changes: 1 addition & 1 deletion crates/synthizer/examples/sin_chords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ fn main() -> Result<()> {

let added = note1 + note2 + note3;
let ready = added * Chain::new(0.1f64);
let to_dev = ready.to_audio_device();
let to_dev = ready.to_audio_device(ChannelFormat::Mono);

let handle = {
let mut batch = synth.batch();
Expand Down
26 changes: 24 additions & 2 deletions crates/synthizer/src/chain.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(clippy::type_complexity)]
use crate::channel_format::ChannelFormat;
use crate::config;
use crate::core_traits::*;
use crate::signals as sigs;
Expand Down Expand Up @@ -79,16 +81,17 @@ impl<S: IntoSignal> Chain<S> {
/// Send this chain to the audio device.
pub fn to_audio_device(
self,
format: ChannelFormat,
) -> Chain<
impl IntoSignal<
Signal = impl for<'a> Signal<Input<'a> = IntoSignalInput<'a, S>, Output<'a> = ()>,
>,
>
where
S::Signal: for<'a> Signal<Output<'a> = f64>,
for<'a> IntoSignalOutput<'a, S>: AudioFrame<f64>,
{
Chain {
inner: sigs::AudioOutputSignalConfig::new(self.inner),
inner: sigs::AudioOutputSignalConfig::new(self.inner, format),
}
}

Expand Down Expand Up @@ -223,4 +226,23 @@ impl<S: IntoSignal> Chain<S> {
inner: sigs::BoxedSignalConfig::new(self.inner),
}
}

pub fn join<S2>(
self,
other: Chain<S2>,
) -> Chain<
impl IntoSignal<
Signal = impl for<'il, 'ol> Signal<
Input<'il> = (IntoSignalInput<'il, S>, IntoSignalInput<'il, S2>),
Output<'ol> = (IntoSignalOutput<'ol, S>, IntoSignalOutput<'ol, S2>),
>,
>,
>
where
S2: IntoSignal,
{
Chain {
inner: sigs::JoinSignalConfig::new(self.inner, other.inner),
}
}
}
2 changes: 1 addition & 1 deletion crates/synthizer/src/channel_format.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::num::NonZeroUsize;

/// A format for audio data.
#[derive(Clone, Debug, Eq, PartialEq, derive_more::IsVariant)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, derive_more::IsVariant)]
pub enum ChannelFormat {
/// This is single-channel mono audio.
Mono,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ impl SampleSource for SymphoniaWrapper {
let duration = self.format.tracks()[self.track_index].codec_params.n_frames;

Descriptor {
channel_format: self.synthizer_channel_format.clone(),
channel_format: self.synthizer_channel_format,
duration,
sample_rate: NonZeroU64::new(self.sample_rate).unwrap(),
seek_support: if duration.is_some() {
Expand Down
2 changes: 1 addition & 1 deletion crates/synthizer/src/sample_sources/vec_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl VecSourceBuilder {
impl SampleSource for VecSource {
fn get_descriptor(&self) -> Descriptor {
Descriptor {
channel_format: self.channel_format.clone(),
channel_format: self.channel_format,
duration: Some(self.frame_count as u64),
sample_rate: self.sample_rate,
seek_support: super::SeekSupport::SampleAccurate,
Expand Down
25 changes: 16 additions & 9 deletions crates/synthizer/src/signals/audio_io.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
use crate::channel_format::ChannelFormat;
use crate::context::*;
use crate::core_traits::*;

pub struct AudioOutputSignal<S>(S);
pub struct AudioOutputSignalConfig<S>(S);
pub struct AudioOutputSignalConfig<S> {
parent_cfg: S,
format: ChannelFormat,
}

pub struct AudioOutputState<T> {
offset: usize,
Expand All @@ -12,11 +16,11 @@ pub struct AudioOutputState<T> {

unsafe impl<S> Signal for AudioOutputSignal<S>
where
for<'a> S: Signal<Output<'a> = f64>,
for<'a> S: Signal,
for<'ol> SignalOutput<'ol, S>: AudioFrame<f64>,
{
type Output<'ol> = ();
type Input<'il> = S::Input<'il>;
// The parent state, with a usize tacked on for tick1's counter.
type State = AudioOutputState<S::State>;

fn on_block_start(ctx: &SignalExecutionContext<'_, '_>, state: &mut Self::State) {
Expand All @@ -41,7 +45,7 @@ where
let mut temp = [[0.0f64; 2]; N];
crate::channel_conversion::convert_channels(
&crate::audio_frames::DefaultingFrameWrapper::wrap_array(&mut block),
crate::ChannelFormat::Mono,
state.format,
&mut temp,
crate::ChannelFormat::Stereo,
);
Expand Down Expand Up @@ -70,25 +74,28 @@ where
impl<S> IntoSignal for AudioOutputSignalConfig<S>
where
S: IntoSignal,
for<'ol> S::Signal: Signal<Output<'ol> = f64>,
for<'ol> IntoSignalOutput<'ol, S>: AudioFrame<f64>,
{
type Signal = AudioOutputSignal<S::Signal>;

fn into_signal(self) -> IntoSignalResult<Self> {
let inner = self.0.into_signal()?;
let inner = self.parent_cfg.into_signal()?;
Ok(ReadySignal {
signal: AudioOutputSignal(inner.signal),
state: AudioOutputState {
offset: 0,
underlying_state: inner.state,
format: crate::ChannelFormat::Mono,
format: self.format,
},
})
}
}

impl<S> AudioOutputSignalConfig<S> {
pub(crate) fn new(signal: S) -> Self {
Self(signal)
pub(crate) fn new(signal: S, format: ChannelFormat) -> Self {
Self {
parent_cfg: signal,
format,
}
}
}
106 changes: 106 additions & 0 deletions crates/synthizer/src/signals/join.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use std::mem::MaybeUninit;

use crate::core_traits::*;

pub struct JoinSignalConfig<ParSigCfg1, ParSigCfg2>(ParSigCfg1, ParSigCfg2);
pub struct JoinSignal<ParSig1, ParSig2>(ParSig1, ParSig2);
pub struct JoinSignalState<ParSigState1, ParSigState2>(ParSigState1, ParSigState2);

unsafe impl<ParSig1, ParSig2> Signal for JoinSignal<ParSig1, ParSig2>
where
ParSig1: Signal,
ParSig2: Signal,
{
type Input<'il> = (SignalInput<'il, ParSig1>, SignalInput<'il, ParSig2>);
type Output<'ol> = (SignalOutput<'ol, ParSig1>, SignalOutput<'ol, ParSig2>);
type State = JoinSignalState<SignalState<ParSig1>, SignalState<ParSig2>>;

fn on_block_start(
ctx: &crate::context::SignalExecutionContext<'_, '_>,
state: &mut Self::State,
) {
ParSig1::on_block_start(ctx, &mut state.0);
ParSig2::on_block_start(ctx, &mut state.1);
}

fn tick<'il, 'ol, I, const N: usize>(
ctx: &'_ crate::context::SignalExecutionContext<'_, '_>,
input: I,
state: &mut Self::State,
) -> impl ValueProvider<Self::Output<'ol>>
where
Self::Input<'il>: 'ol,
'il: 'ol,
I: ValueProvider<Self::Input<'il>> + Sized,
{
let mut left_in: [MaybeUninit<SignalInput<'il, ParSig1>>; N] =
[const { MaybeUninit::uninit() }; N];
let mut right_in: [MaybeUninit<SignalInput<'il, ParSig2>>; N] =
[const { MaybeUninit::uninit() }; N];

let mut last_i = 0;
for (i, (l, r)) in unsafe { input.become_iterator() }.enumerate() {
left_in[i].write(l);
right_in[i].write(r);
last_i = i;
}

assert_eq!(last_i, N - 1);

let par_left_out = ParSig1::tick::<_, N>(
ctx,
ArrayProvider::new(left_in.map(|x| unsafe { x.assume_init() })),
&mut state.0,
);
let par_right_out = ParSig2::tick::<_, N>(
ctx,
ArrayProvider::new(right_in.map(|x| unsafe { x.assume_init() })),
&mut state.1,
);

let outgoing = crate::array_utils::collect_iter::<_, N>(
unsafe { par_left_out.become_iterator() }
.zip(unsafe { par_right_out.become_iterator() }),
);
ArrayProvider::new(outgoing)
}

fn trace_slots<
F: FnMut(
crate::unique_id::UniqueId,
std::sync::Arc<dyn std::any::Any + Send + Sync + 'static>,
),
>(
state: &Self::State,
mut inserter: &mut F,
) {
ParSig1::trace_slots(&state.0, &mut inserter);
ParSig2::trace_slots(&state.1, &mut inserter);
}
}

impl<ParSigCfg1, ParSigCfg2> IntoSignal for JoinSignalConfig<ParSigCfg1, ParSigCfg2>
where
JoinSignal<ParSigCfg1::Signal, ParSigCfg2::Signal>:
Signal<State = JoinSignalState<IntoSignalState<ParSigCfg1>, IntoSignalState<ParSigCfg2>>>,
ParSigCfg1: IntoSignal,
ParSigCfg2: IntoSignal,
{
type Signal = JoinSignal<ParSigCfg1::Signal, ParSigCfg2::Signal>;

fn into_signal(self) -> IntoSignalResult<Self> {
let par_l = self.0.into_signal()?;
let par_r = self.1.into_signal()?;

Ok(ReadySignal {
signal: JoinSignal(par_l.signal, par_r.signal),
state: JoinSignalState(par_l.state, par_r.state),
})
}
}

impl<ParSigCfg1, ParSigCfg2> JoinSignalConfig<ParSigCfg1, ParSigCfg2> {
pub(crate) fn new(par_sig1: ParSigCfg1, par_sig2: ParSigCfg2) -> Self {
Self(par_sig1, par_sig2)
}
}
2 changes: 2 additions & 0 deletions crates/synthizer/src/signals/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod audio_io;
mod boxed;
mod consume_input;
mod conversion;
mod join;
mod map;
mod null;
mod periodic_f64;
Expand All @@ -15,6 +16,7 @@ pub use audio_io::*;
pub use boxed::*;
pub(crate) use consume_input::*;
pub use conversion::*;
pub use join::*;
pub use map::*;
pub use null::*;
pub use periodic_f64::*;
Expand Down

0 comments on commit 8cc462b

Please sign in to comment.