Skip to content

Commit

Permalink
Bring back the ability to work with external media assets
Browse files Browse the repository at this point in the history
Minus some stuff one definitely needs for this-but that's coming.
  • Loading branch information
ahicks92 committed Jan 4, 2025
1 parent 2f5ed51 commit 40718c1
Show file tree
Hide file tree
Showing 36 changed files with 983 additions and 496 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ rand_xoshiro = "0.6.0"
rayon = "1.8.0"
reciprocal = "0.1.2"
rpds = "1.1.0"
rubato = "0.14.1"
rubato = "0.16.1"
seq-macro = "0.3.5"
smallvec = { version = "1.10.0", features = ["write"] }
spin = "0.9.8"
Expand Down
135 changes: 135 additions & 0 deletions crates/synthizer/examples/media_player.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use std::time::Duration;

use synthizer::sample_sources;
use synthizer::*;

fn main() -> Result<()> {
env_logger::init();

let mut synth = Synthesizer::new_default_output()?;

let args = std::env::args().collect::<Vec<_>>();
let file_path = args
.get(1)
.expect("Specify a file path as the first argument");
let file = std::fs::File::open(file_path).unwrap();
let source = sample_sources::create_encoded_source(file)?;

let mut media;

let handle = {
let mut batch = synth.batch();

media = batch.make_media(source)?;

batch.mount(
media
.start_chain::<2>(ChannelFormat::Stereo)
.to_audio_device(ChannelFormat::Stereo),
)?
};

println!(
"
Commands:
play
pause
quit
seek <secs>: seek to a position in the file.
loop (off | full | range) [<start> <end>]: configure looping.
"
);

loop {
use std::io::Write;

print!("Command> ");
std::io::stdout().flush().unwrap();
let mut line = String::new();
std::io::stdin().read_line(&mut line).unwrap();
// trim removes \r\n.
let mut line = line.trim().split(' ');

let cmd = line.next();
let Some(cmd) = cmd else {
continue;
};

match cmd {
"quit" => break,
"s" | "seek" => {
let Some(pos) = line.next() else {
println!("Missing parameter position");
continue;
};

let Ok(pos) = pos.parse::<f64>() else {
println!("Position not a number: {pos:?}");
continue;
};

let mut batch = synth.batch();
batch.media_seek(&handle, &media, (pos * SR as f64) as u64)?;
}
"pause" => {
let mut batch = synth.batch();
batch.media_pause(&handle, &media)?;
}
"play" => {
let mut batch = synth.batch();
batch.media_play(&handle, &media)?;
}
"loop" => {
let Some(ltype) = line.next() else {
println!("Missing loop subcommand");
continue;
};

let spec = match ltype {
"off" => LoopSpec::none(),
"full" => LoopSpec::all(),
"range" => {
let Some(start) = line.next() else {
println!("Missing start");
continue;
};

let Some(end) = line.next() else {
println!("Missing end");
continue;
};

let Ok(start) = start.parse::<f64>() else {
println!("Start not a number: {start:?}");
continue;
};

let Ok(end) = end.parse::<f64>() else {
println!("End not a number: {end:?}");
continue;
};

LoopSpec::timestamps(
Duration::from_secs_f64(start),
Some(Duration::from_secs_f64(end)),
)
}
_ => {
println!("Unrecognized loop type {ltype}");
continue;
}
};

let mut batch = synth.batch();
batch.media_config_looping(&handle, &media, spec)?;
}
_ => {
println!("Unrecognized command {cmd}");
continue;
}
}
}

Ok(())
}
8 changes: 8 additions & 0 deletions crates/synthizer/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ where
fn into_signal(self) -> IntoSignalResult<Self> {
self.inner.into_signal()
}

fn trace<F: FnMut(crate::unique_id::UniqueId, TracedResource)>(
&mut self,
inserter: &mut F,
) -> crate::Result<()> {
self.inner.trace(inserter)?;
Ok(())
}
}

/// Start a chain which reads from a slot.
Expand Down
21 changes: 10 additions & 11 deletions crates/synthizer/src/chain_mathops.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
//! Implements the mathematical operations between `IntoSignal`s.
use std::any::Any;

use std::ops::*;
use std::sync::Arc;

use crate::chain::Chain;
use crate::context::SignalExecutionContext;
use crate::core_traits::*;
use crate::error::Result;
use crate::unique_id::UniqueId;

macro_rules! impl_mathop {
Expand Down Expand Up @@ -80,14 +78,6 @@ macro_rules! impl_mathop {
);
ArrayProvider::new(arr)
}

fn trace_slots<F: FnMut(UniqueId, Arc<dyn Any + Send + Sync + 'static>)>(
state: &Self::State,
inserter: &mut F,
) {
S1::trace_slots(&state.0, inserter);
S2::trace_slots(&state.1, inserter);
}
}

impl<I1, I2, S1, S2> IntoSignal for $signal_config<S1, S2>
Expand All @@ -109,6 +99,15 @@ macro_rules! impl_mathop {
state: (l.state, r.state),
})
}

fn trace<F: FnMut(UniqueId, TracedResource)>(
&mut self,
inserter: &mut F,
) -> Result<()> {
self.0.trace(inserter)?;
self.1.trace(inserter)?;
Ok(())
}
}
};
}
Expand Down
5 changes: 5 additions & 0 deletions crates/synthizer/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::num::NonZeroUsize;

/// The fixed sample rate of the library.
///
/// For efficiency and simplicity, the internals use this sample rate and only this sample rate, converting as needed at
Expand All @@ -22,3 +24,6 @@ pub(crate) const BLOCK_SIZE: usize = 128;
///
/// We have unsafe code which depends on this always being a float array.
pub(crate) type BlockArray = [f64; BLOCK_SIZE];

/// Number of worker threads for e.g. media decoding per synthesizer.
pub(crate) const WORKER_POOL_THREADS: NonZeroUsize = NonZeroUsize::new(4).unwrap();
3 changes: 2 additions & 1 deletion crates/synthizer/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use atomic_refcell::AtomicRefCell;

use crate::channel_format::*;
use crate::config;
use crate::signals::SlotUpdateContext;
use crate::signals::{MediaExecutorMap, SlotUpdateContext};

pub struct SignalExecutionContext<'a, 'shared> {
pub(crate) fixed: &'a FixedSignalExecutionContext<'shared>,
Expand All @@ -14,4 +14,5 @@ pub(crate) struct FixedSignalExecutionContext<'a> {
pub(crate) audio_destinationh: AtomicRefCell<&'a mut [[f64; 2]; config::BLOCK_SIZE]>,
pub(crate) audio_destination_format: &'a ChannelFormat,
pub(crate) slots: &'a SlotUpdateContext<'a>,
pub(crate) media: &'a MediaExecutorMap,
}
58 changes: 25 additions & 33 deletions crates/synthizer/src/core_traits.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#![allow(private_interfaces)]
use std::any::Any;
use std::sync::Arc;

use crate::context::*;
use crate::error::Result;
use crate::sample_sources::execution::Executor as MediaExecutor;
use crate::unique_id::UniqueId;

// These are "core" but it's a lot of code so we pull it out.
Expand All @@ -11,6 +13,12 @@ pub(crate) use crate::value_provider::*;
pub(crate) mod sealed {
use super::*;

/// Traced resources.
pub enum TracedResource {
Slot(Arc<dyn Any + Send + Sync + 'static>),
Media(Arc<MediaExecutor>),
}

/// This internal trait is the actual magic.
///
/// # Safety
Expand Down Expand Up @@ -65,29 +73,6 @@ pub(crate) mod sealed {
///
/// No default impl is provided. All signals need to consider what they want to do so we force the issue.
fn on_block_start(ctx: &SignalExecutionContext<'_, '_>, state: &mut Self::State);

/// Trace slots.
///
/// This is "private" to the slot machinery, but must be implemented on combinators. Combinators should forward
/// to their parents. Everything else should leave the implementation empty.
///
/// This is called when mounting, in the thread that mounts. It calls the callback with ids and states for new
/// slots. The only implementor which does anything but pass to other signals is `SlotSignal`.
///
/// If the user tries to use a slot which is not traced they get an error. If the algorithm tries to use a slot
/// which is not traced, we panic. The latter is an internal bug. It is on us to always know what slots the
/// user made.
///
/// The callback gets called with an Arc to the *value* of the slot. The rest is wrapped up by the generic
/// machinery.
fn trace_slots<F: FnMut(UniqueId, Arc<dyn Any + Send + Sync + 'static>)>(
state: &Self::State,
inserter: &mut F,
);
}

pub trait SignalDestination<Input: Sized, const N: usize> {
fn send(self, values: [Input; N]);
}

/// A frame of audio data, which can be stored on the stack.
Expand All @@ -114,6 +99,23 @@ pub(crate) mod sealed {
type Signal: Signal;

fn into_signal(self) -> Result<ReadySignal<Self::Signal, IntoSignalState<Self>>>;

/// Trace a signal's resource usage, and allocate objects.
///
/// The synthesizer must use erased `Any` to store objects in the maps. That means that it is necessary to let
/// signals allocate such objects, then hand them off. It's also required that we trace signals to figure out
/// other things, for example which things might be used before others.
///
/// Implementations should:
///
/// - If a combinator or other signal with "parents": call this on the parents, in the order that the signal
/// would call `tick` on those parents.
/// - If a "leaf" which uses resources (e.g. slots) call the callback.
/// - If a "leaf" which doesn't need resources, add an empty impl.
/// - If a combinator which uses resources, call the tracer either before calling the parents (if the resource
/// is used before ticking them) or after (if the resource is used after). Using resources "in the middle"
/// should be avoided.
fn trace<F: FnMut(UniqueId, TracedResource)>(&mut self, inserter: &mut F) -> Result<()>;
}

pub(crate) type IntoSignalResult<S> =
Expand All @@ -122,16 +124,6 @@ pub(crate) mod sealed {

pub(crate) use sealed::*;

impl<F, Input, const N: usize> SignalDestination<Input, N> for F
where
Input: Sized,
F: FnOnce([Input; N]),
{
fn send(self, value: [Input; N]) {
self(value)
}
}

// Workarounds for https://github.com/rust-lang/rust/issues/38078: rustc is not always able to determine when a type
// isn't ambiguous, or at the very least it doesn't tell us what the options are, so we use this instead.
pub(crate) type IntoSignalOutput<'a, S> = <<S as IntoSignal>::Signal as Signal>::Output<'a>;
Expand Down
Loading

0 comments on commit 40718c1

Please sign in to comment.