diff --git a/.vscode/settings.json b/.vscode/settings.json index c13b83b..57736a0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -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" } \ No newline at end of file diff --git a/crates/synthizer/examples/binaural_beats.rs b/crates/synthizer/examples/binaural_beats.rs new file mode 100644 index 0000000..2d5e110 --- /dev/null +++ b/crates/synthizer/examples/binaural_beats.rs @@ -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(()) +} diff --git a/crates/synthizer/examples/sin_chords.rs b/crates/synthizer/examples/sin_chords.rs index 7591eac..fad843b 100644 --- a/crates/synthizer/examples/sin_chords.rs +++ b/crates/synthizer/examples/sin_chords.rs @@ -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(); diff --git a/crates/synthizer/src/chain.rs b/crates/synthizer/src/chain.rs index 1ebe2e6..73a69da 100644 --- a/crates/synthizer/src/chain.rs +++ b/crates/synthizer/src/chain.rs @@ -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; @@ -79,16 +81,17 @@ impl Chain { /// Send this chain to the audio device. pub fn to_audio_device( self, + format: ChannelFormat, ) -> Chain< impl IntoSignal< Signal = impl for<'a> Signal = IntoSignalInput<'a, S>, Output<'a> = ()>, >, > where - S::Signal: for<'a> Signal = f64>, + for<'a> IntoSignalOutput<'a, S>: AudioFrame, { Chain { - inner: sigs::AudioOutputSignalConfig::new(self.inner), + inner: sigs::AudioOutputSignalConfig::new(self.inner, format), } } @@ -223,4 +226,23 @@ impl Chain { inner: sigs::BoxedSignalConfig::new(self.inner), } } + + pub fn join( + self, + other: Chain, + ) -> 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), + } + } } diff --git a/crates/synthizer/src/channel_format.rs b/crates/synthizer/src/channel_format.rs index 124c366..0c8dade 100644 --- a/crates/synthizer/src/channel_format.rs +++ b/crates/synthizer/src/channel_format.rs @@ -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, diff --git a/crates/synthizer/src/sample_sources/encoded/symphonia_impl.rs b/crates/synthizer/src/sample_sources/encoded/symphonia_impl.rs index b67ea06..9a61310 100644 --- a/crates/synthizer/src/sample_sources/encoded/symphonia_impl.rs +++ b/crates/synthizer/src/sample_sources/encoded/symphonia_impl.rs @@ -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() { diff --git a/crates/synthizer/src/sample_sources/vec_source.rs b/crates/synthizer/src/sample_sources/vec_source.rs index ec9f95e..3d30286 100644 --- a/crates/synthizer/src/sample_sources/vec_source.rs +++ b/crates/synthizer/src/sample_sources/vec_source.rs @@ -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, diff --git a/crates/synthizer/src/signals/audio_io.rs b/crates/synthizer/src/signals/audio_io.rs index 67325cc..3941cf3 100644 --- a/crates/synthizer/src/signals/audio_io.rs +++ b/crates/synthizer/src/signals/audio_io.rs @@ -1,8 +1,12 @@ +use crate::channel_format::ChannelFormat; use crate::context::*; use crate::core_traits::*; pub struct AudioOutputSignal(S); -pub struct AudioOutputSignalConfig(S); +pub struct AudioOutputSignalConfig { + parent_cfg: S, + format: ChannelFormat, +} pub struct AudioOutputState { offset: usize, @@ -12,11 +16,11 @@ pub struct AudioOutputState { unsafe impl Signal for AudioOutputSignal where - for<'a> S: Signal = f64>, + for<'a> S: Signal, + for<'ol> SignalOutput<'ol, S>: AudioFrame, { type Output<'ol> = (); type Input<'il> = S::Input<'il>; - // The parent state, with a usize tacked on for tick1's counter. type State = AudioOutputState; fn on_block_start(ctx: &SignalExecutionContext<'_, '_>, state: &mut Self::State) { @@ -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, ); @@ -70,25 +74,28 @@ where impl IntoSignal for AudioOutputSignalConfig where S: IntoSignal, - for<'ol> S::Signal: Signal = f64>, + for<'ol> IntoSignalOutput<'ol, S>: AudioFrame, { type Signal = AudioOutputSignal; fn into_signal(self) -> IntoSignalResult { - 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 AudioOutputSignalConfig { - pub(crate) fn new(signal: S) -> Self { - Self(signal) + pub(crate) fn new(signal: S, format: ChannelFormat) -> Self { + Self { + parent_cfg: signal, + format, + } } } diff --git a/crates/synthizer/src/signals/join.rs b/crates/synthizer/src/signals/join.rs new file mode 100644 index 0000000..c8baacb --- /dev/null +++ b/crates/synthizer/src/signals/join.rs @@ -0,0 +1,106 @@ +use std::mem::MaybeUninit; + +use crate::core_traits::*; + +pub struct JoinSignalConfig(ParSigCfg1, ParSigCfg2); +pub struct JoinSignal(ParSig1, ParSig2); +pub struct JoinSignalState(ParSigState1, ParSigState2); + +unsafe impl Signal for JoinSignal +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>; + + 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> + where + Self::Input<'il>: 'ol, + 'il: 'ol, + I: ValueProvider> + Sized, + { + let mut left_in: [MaybeUninit>; N] = + [const { MaybeUninit::uninit() }; N]; + let mut right_in: [MaybeUninit>; 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, + ), + >( + state: &Self::State, + mut inserter: &mut F, + ) { + ParSig1::trace_slots(&state.0, &mut inserter); + ParSig2::trace_slots(&state.1, &mut inserter); + } +} + +impl IntoSignal for JoinSignalConfig +where + JoinSignal: + Signal, IntoSignalState>>, + ParSigCfg1: IntoSignal, + ParSigCfg2: IntoSignal, +{ + type Signal = JoinSignal; + + fn into_signal(self) -> IntoSignalResult { + 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 JoinSignalConfig { + pub(crate) fn new(par_sig1: ParSigCfg1, par_sig2: ParSigCfg2) -> Self { + Self(par_sig1, par_sig2) + } +} diff --git a/crates/synthizer/src/signals/mod.rs b/crates/synthizer/src/signals/mod.rs index 1600b40..c94e4f8 100644 --- a/crates/synthizer/src/signals/mod.rs +++ b/crates/synthizer/src/signals/mod.rs @@ -3,6 +3,7 @@ mod audio_io; mod boxed; mod consume_input; mod conversion; +mod join; mod map; mod null; mod periodic_f64; @@ -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::*;