use bevy::prelude::*;
use bevy_seedling::{pool::sample_effects::SampleEffects, prelude::*};
use firewheel::{
channel_config::{ChannelConfig, NonZeroChannelCount},
diff::{Diff, Patch},
event::NodeEventList,
node::{
AudioNode, AudioNodeInfo, AudioNodeProcessor, ConstructProcessorContext, ProcBuffers,
ProcInfo, ProcessStatus,
},
};
fn main() {
App::new()
.add_plugins((
MinimalPlugins,
bevy::log::LogPlugin::default(),
AssetPlugin::default(),
SeedlingPlugin::default(),
))
.register_node::<CustomVolumeNode>()
.add_systems(Startup, startup)
.add_systems(Update, update)
.run();
}
#[derive(Diff, Patch, Debug, Clone, Component)]
pub struct CustomVolumeNode {
pub volume: Volume,
}
#[derive(Debug, Component, Clone)]
pub struct VolumeConfig {
pub channels: NonZeroChannelCount,
}
impl Default for VolumeConfig {
fn default() -> Self {
Self {
channels: NonZeroChannelCount::STEREO,
}
}
}
impl AudioNode for CustomVolumeNode {
type Configuration = VolumeConfig;
fn info(&self, config: &Self::Configuration) -> AudioNodeInfo {
AudioNodeInfo::new()
.debug_name("custom volume")
.channel_config(ChannelConfig {
num_inputs: config.channels.get(),
num_outputs: config.channels.get(),
})
.uses_events(true)
}
fn construct_processor(
&self,
_config: &Self::Configuration,
_cx: ConstructProcessorContext,
) -> impl AudioNodeProcessor {
VolumeProcessor {
params: self.clone(),
}
}
}
struct VolumeProcessor {
params: CustomVolumeNode,
}
impl AudioNodeProcessor for VolumeProcessor {
fn process(
&mut self,
ProcBuffers {
inputs, outputs, ..
}: ProcBuffers,
proc_info: &ProcInfo,
mut events: NodeEventList,
) -> ProcessStatus {
events.for_each_patch::<CustomVolumeNode>(|patch| self.params.apply(patch));
if proc_info.in_silence_mask.all_channels_silent(inputs.len()) {
return ProcessStatus::ClearAllOutputs;
}
let gain = self.params.volume.amp();
for (input, output) in inputs.iter().zip(outputs.iter_mut()) {
for (input_sample, output_sample) in input.iter().zip(output.iter_mut()) {
*output_sample = *input_sample * gain;
}
}
ProcessStatus::outputs_not_silent()
}
}
fn startup(server: Res<AssetServer>, mut commands: Commands) {
commands.spawn((
SamplePlayer::new(server.load("selfless_courage.ogg")).looping(),
sample_effects![CustomVolumeNode {
volume: Volume::Linear(1.0),
}],
));
}
fn update(
player: Single<&SampleEffects, With<SamplePlayer>>,
mut custom_node: Query<&mut CustomVolumeNode>,
time: Res<Time>,
mut angle: Local<f32>,
) -> Result {
let mut custom_node = custom_node.get_effect_mut(&player)?;
custom_node.volume = Volume::Linear(angle.cos() * 0.25 + 0.5);
let period = 5.0;
*angle += time.delta().as_secs_f32() * core::f32::consts::TAU / period;
Ok(())
}