Skip to content

Commit

Permalink
Final WIP before a squash merge
Browse files Browse the repository at this point in the history
  • Loading branch information
ahicks92 committed Oct 14, 2023
1 parent be6bcde commit a327878
Show file tree
Hide file tree
Showing 10 changed files with 120 additions and 44 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ audio_synchronization = { path = "crates/audio_synchronization" }
cc = "1.0.79"
criterion = "0.4.0"
crossbeam = "0.8.2"
derivative = "2.2.0"
derive_more = "0.99.17"
enum_dispatch = "0.3.12"
env_logger = "0.10.0"
Expand Down
1 change: 1 addition & 0 deletions crates/synthizer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ arc-swap.workspace = true
arrayvec.workspace = true
atomic_refcell.workspace = true
audio_synchronization.workspace = true
derivative.workspace = true
derive_more.workspace = true
enum_dispatch.workspace = true
im.workspace = true
Expand Down
13 changes: 7 additions & 6 deletions crates/synthizer/examples/sine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ fn main() -> syz::Result<()> {
let server = syz::ServerHandle::new_default_device()?;
let audio_output = syz::nodes::AudioOutputNodeHandle::new(&server, syz::ChannelFormat::Stereo)?;

for freq in [300.0f64, 400.0, 500.0] {
let sin = syz::nodes::TrigWaveformNodeHandle::new_sin(&server, freq)?;
server.connect(&sin, 0, &audio_output, 0)?;
sleep(Duration::from_secs(3));
std::mem::drop(sin);
sleep(Duration::from_secs(1));
let sin = syz::nodes::TrigWaveformNodeHandle::new_sin(&server, 300.0)?;
server.connect(&sin, 0, &audio_output, 0)?;
sleep(Duration::from_millis(500));

for freq in [400.0, 500.0] {
sin.props().frequency().set_value(freq)?;
sleep(Duration::from_millis(500));
}

Ok(())
Expand Down
9 changes: 7 additions & 2 deletions crates/synthizer/src/background_drop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,13 @@ static WORK_QUEUE: StaticThingBuf<
crate::option_recycler::OptionRecycler,
> = StaticThingBuf::<Option<DropElement>, BACKLOG, crate::option_recycler::OptionRecycler>::with_recycle(crate::option_recycler::OptionRecycler);

struct ArcDrop(Arc<dyn Any + Send + Sync + 'static>);
struct BoxDrop(Box<dyn Any + Send + Sync + 'static>);
#[derive(derivative::Derivative)]
#[derivative(Debug)]
struct ArcDrop(#[derivative(Debug = "ignore")] Arc<dyn Any + Send + Sync + 'static>);

#[derive(derivative::Derivative)]
#[derivative(Debug)]
struct BoxDrop(#[derivative(Debug = "ignore")] Box<dyn Any + Send + Sync + 'static>);

macro_rules! decl_variant {
($($tys:ident),*)=> {
Expand Down
31 changes: 24 additions & 7 deletions crates/synthizer/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,19 @@ use crate::unique_id::UniqueId;

/// A port is returned on object creation and tells commands where they are going. This is what non-audio-thread
/// objects get and use to dispatch against a server.
#[derive(Copy, Clone, Eq, Ord, PartialEq, PartialOrd, Hash)]
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub(crate) struct Port {
pub(crate) kind: PortKind,
}

#[derive(Copy, Clone, Eq, Ord, PartialEq, PartialOrd, Hash, derive_more::IsVariant)]
#[derive(Copy, Clone, Eq, Ord, PartialEq, PartialOrd, Hash, derive_more::IsVariant, Debug)]
pub(crate) enum PortKind {
/// Servers cannot be objects, since they own objects. We special case that instead.
Server,

Node(UniqueId),
}

pub(crate) trait CommandPayload<E> {
fn to_command(self) -> E;
}

impl Port {
/// Return a port for a server.
pub(crate) fn for_server() -> Self {
Expand All @@ -40,6 +36,12 @@ impl Port {
}
}

impl AsRef<Port> for Port {
fn as_ref(&self) -> &Port {
self
}
}

/// We have to isolate imports because the macro is using magic to allow for `paste` usage.
mod cmdkind {
use crate::properties::PropertyCommand;
Expand All @@ -51,12 +53,17 @@ mod cmdkind {
}
use cmdkind::*;

#[derive(Debug)]
pub(crate) struct Command {
port: Port,
payload: CommandKind,
}

impl Command {
pub(crate) fn port(&self) -> &Port {
&self.port
}

pub(crate) fn new(port: &Port, what: impl Into<CommandKind>) -> Self {
Self {
port: *port,
Expand All @@ -72,6 +79,16 @@ impl Command {
.try_into()
.map_err(|payload| Self { payload, ..self })
}

pub(crate) fn take_call<T: CommandKindPayload>(
self,
closure: impl FnOnce(T),
) -> Result<(), Self> {
self.payload.take_call(closure).map_err(|payload| Command {
port: self.port,
payload,
})
}
}

/// Something that knows how to dispatch commands.
Expand All @@ -88,7 +105,7 @@ pub(crate) trait CommandSenderExt {
P: AsRef<Port>;
}

impl<T: CommandSender> CommandSenderExt for T {
impl<T: CommandSender + ?Sized> CommandSenderExt for T {
fn send<C, P>(&self, port: P, payload: C) -> Result<()>
where
CommandKind: From<C>,
Expand Down
14 changes: 14 additions & 0 deletions crates/synthizer/src/nodes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,17 @@ pub(crate) enum ConcreteNodeHandle {
TrigWaveform(ExclusiveSlabRef<trig::TrigWaveformNode>),
AudioOutput(ExclusiveSlabRef<audio_output::AudioOutputNode>),
}

impl std::fmt::Debug for ConcreteNodeHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ConcreteNodeHandle")
.field(
"pointing_at",
&match self {
Self::AudioOutput(_) => "AudioOutput",
Self::TrigWaveform(_) => "TrigWaveform",
},
)
.finish()
}
}
29 changes: 25 additions & 4 deletions crates/synthizer/src/nodes/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,22 +250,34 @@ pub(crate) trait NodeAt: HasNodeDescriptor {

/// Execute a command.
///
/// This function is called on *all* commands that reach this node, including built-in commands. It should return `Ok(())` if the command was handled, else `Err(unhandled_command)`. The default implementation does nothing, and delegates to built-in processing.
/// This function is called on *all* commands that reach this node, including built-in commands. It should return
/// `Ok(())` if the command was handled, else `Err(unhandled_command)`. The default implementation does nothing,
/// and delegates to built-in processing.
fn execute_command(&mut self, cmd: cmd::Command) -> Result<(), cmd::Command> {
Err(cmd)
}

/// This function handles built-in commands after giving the node a chance to execute them itself.
///
/// Any command we don't understand panics the process. Unfortunately, we don't have `Debug` on the command enum; we might like this, but that's infeasible at the current time since commands may contain e.g. audio buffers (consider this a todo).
/// Any command we don't understand panics the process. Unfortunately, we don't have `Debug` on the command enum; we
/// might like this, but that's infeasible at the current time since commands may contain e.g. audio buffers
/// (consider this a todo). Commands that aren't something to do with nodes should never make it to nodes.
fn command_received(&mut self, cmd: cmd::Command) {
let Err(_cmd) = self.execute_command(cmd) else {
let Err(cmd) = self.execute_command(cmd) else {
// That's fine; it was handled.
return;
};

// Now we must determine where it is going.
cmd.take_call(|prop: props::PropertyCommand| {
use props::PropertyCommandReceiver;

match prop {
props::PropertyCommand::Set { index, value } => {
self.get_property_struct().set_property(index, value);
}
}
})
.expect("Should have handled the command");
}
}

Expand All @@ -279,6 +291,7 @@ impl<T: HasNodeDescriptor + NodeAt> Node for T {}
pub(crate) trait ErasedNode {
fn describe_erased(&self) -> Cow<'static, NodeDescriptor>;
fn execute_erased(&mut self, context: &mut ErasedExecutionContext);
fn command_received_erased(&mut self, cmd: crate::command::Command);
}

impl<T: Node> ErasedNode for T {
Expand All @@ -289,6 +302,10 @@ impl<T: Node> ErasedNode for T {
fn execute_erased(&mut self, context: &mut ErasedExecutionContext) {
self.gather_and_execute(context)
}

fn command_received_erased(&mut self, cmd: crate::command::Command) {
self.command_received(cmd);
}
}

impl<T: Send + Sync + ErasedNode> ErasedNode for ExclusiveSlabRef<T> {
Expand All @@ -299,6 +316,10 @@ impl<T: Send + Sync + ErasedNode> ErasedNode for ExclusiveSlabRef<T> {
fn execute_erased(&mut self, context: &mut ErasedExecutionContext) {
self.deref_mut().execute_erased(context);
}

fn command_received_erased(&mut self, cmd: cmd::Command) {
self.deref_mut().command_received_erased(cmd);
}
}

mod sealed_node_handle {
Expand Down
13 changes: 10 additions & 3 deletions crates/synthizer/src/properties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ impl PropertyKind for F64X1 {
}

/// Command to perform an operation on a property.
#[derive(Debug)]
pub(crate) enum PropertyCommand {
Set { index: usize, value: PropertyValue },
}
Expand Down Expand Up @@ -115,6 +116,7 @@ impl PropertyCommandReceiver for () {

fn tick_first(&mut self) {}
}

/// A reference to a property.
///
/// To get one, call `.props().name()` on a node of your choice.
Expand Down Expand Up @@ -143,9 +145,14 @@ impl<'a, V: PropertyKind> Property<'a, V> {
/// as necessary on the audio thread. This may seem odd, but if one is also connecting to a property for automation
/// purposes then it is possible that the sum is in range even when individual sets aren't, and this can't be
/// checked except on the audio thread itself.
pub fn set(&self, _val: V) -> Result<()> {
// todo, I'm typing out all the other stuff first.
Ok(())
pub fn set_value(&self, val: V::ContainedType) -> Result<()> {
self.sender.send(
self.port,
PropertyCommand::Set {
index: self.index,
value: V::lift(val).into(),
},
)
}
}

Expand Down
21 changes: 15 additions & 6 deletions crates/synthizer/src/server/implementation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ struct NodeContainer {
executed_marker: UniqueId,
}

#[derive(Debug)]
pub(crate) enum ServerCommand {
RegisterNode {
id: UniqueId,
Expand Down Expand Up @@ -243,12 +244,20 @@ impl ServerImpl {
}

pub(crate) fn dispatch_command(&mut self, command: crate::command::Command) {
// Right now all we have are server commands.
self.run_server_command(
command
.extract_payload_as::<ServerCommand>()
.unwrap_or_else(|_| panic!("only server commands are supported")),
);
use crate::command::Port;

match command.port().kind {
PortKind::Server => command
.take_call(|c: ServerCommand| self.run_server_command(c))
.expect("Server ports should only receive ServerCommand"),
PortKind::Node(id) => {
let node = self
.nodes
.get_mut(&id)
.expect("Ports should only point at nodes that exist");
node.node.command_received_erased(command);
}
}
}
}

Expand Down
32 changes: 16 additions & 16 deletions crates/synthizer/src/variant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,33 @@
macro_rules! variant {
($v: vis $name: ident, $($structs :ident),*) => {
paste::paste! {

$v trait [<$name Payload>]<E>: 'static {
fn to_variant(self) -> E;
$v trait [<$name Payload>]: 'static {
fn to_variant(self) -> $name;
}

#[derive(Debug)]
$v enum $name {
$([<$structs V>]($structs)),*
}

$(
impl [<$name Payload>]<$name> for $structs {
impl [<$name Payload>] for $structs {
fn to_variant(self) -> $name {
$name::[<$structs V>](self)
}
}
)*

impl<T> From<T> for $name where
T: [<$name Payload>]<$name>,
T: [<$name Payload>],
{
fn from(val: T)->$name {
val.to_variant()
}
}

impl $name {
$v fn new(what: impl [<$name Payload>]<Self>) -> Self {
$v fn new(what: impl [<$name Payload>]) -> Self {
what.to_variant()
}

Expand Down Expand Up @@ -82,27 +82,25 @@ macro_rules! variant {
}

/// Call the provided closure on the payload, giving it ownership, if the payload is of the expected
/// type.
///
/// Otherwise, return `Some(self)`.
/// type. Return `Ok(r)` if te closure was called, else `Err(self)`.
///
/// This is useful for, e.g., command handling: `cmd.take_call::<T>(|x| ...)?;` will bail when the
/// handler takes the value (alternatively, use `.or_else`).
$v fn take_call<T: [<$name Payload>]<Self>>(mut self, closure: impl FnOnce(T)) -> Option<Self> {
$v fn take_call<R, T: [<$name Payload>]>(mut self, closure: impl FnOnce(T) -> R) -> Result<R, Self> {
// Careful: self must not move.
let (ptr, tid) = unsafe { self.contained_and_type_id() };

if tid == std::any::TypeId::of::<T>() {
unsafe{
let actual_ptr = ptr as *mut T;
closure(actual_ptr.read());
let r = closure(actual_ptr.read());
// The closure now owns the item. This was a move. Forget self to prevent double drops.
#[allow(clippy::forget_non_drop)]
std::mem::forget(self);
Ok(r)
}
None
} else {
Some(self)
Err(self)
}
}
}
Expand Down Expand Up @@ -130,7 +128,9 @@ macro_rules! variant {
mod tests {
use super::*;

#[derive(Debug)]
struct TestCmd1;
#[derive(Debug)]
struct TestCmd2;

variant!(TestVariant, TestCmd1, TestCmd2);
Expand All @@ -148,14 +148,14 @@ mod tests {
.take_call(|_x: TestCmd2| {
called = true;
})
.unwrap();
.unwrap_err();
assert!(!called);
// And the second one should.
assert!(c1
.take_call::<TestCmd1>(|_x| {
.take_call(|_: TestCmd1| {
called = true;
})
.is_none());
.is_ok());
assert!(called);

let c2 = TestVariant::new(TestCmd2);
Expand Down

0 comments on commit a327878

Please sign in to comment.