Simplifying audio (a bit) and optimizing for web

main
Elijah Voigt 4 hours ago
parent 61084e2251
commit 2665ffb3c5

@ -15,7 +15,6 @@ impl Plugin for GameActionsPlugin {
.bind(ZoomAction::Zoom, [Binding::Scroll])
.bind(ZoomAction::ZoomIn, [Binding::Key(KeyCode::Equal)])
.bind(ZoomAction::ZoomOut, [Binding::Key(KeyCode::Minus)]),
ActionsPlugin::<Action>::new().bind(Action::Quit, [Binding::Key(KeyCode::Escape)]),
));
}
}
@ -101,14 +100,6 @@ pub(crate) enum ZoomAction {
None,
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, GameAction, Default)]
pub(crate) enum Action {
#[action_handler(Action::quit)]
Quit,
#[default]
None,
}
impl ZoomAction {
pub(crate) fn zoom_by(factor: f32, mut projection: Query<&mut Projection>) {
projection.iter_mut().for_each(|mut p| {
@ -134,12 +125,6 @@ impl ZoomAction {
}
}
impl Action {
fn quit(mut writer: MessageWriter<AppExit>) {
writer.write(AppExit::Success);
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, GameAction, Default)]
pub(crate) enum AudioAction {
#[action_handler(AudioAction::toggle)]

@ -1,8 +1,4 @@
// TODO:
// * Performance: have a limit on per-note+octave sounds
// * Use a finite set of N audio entities and scale volume instead of
// spawning a bunch of audio entities which is causing issues at >100
// cells
// * Volume slider
// * Invert mute/unmute icons
// * Only move camera when cells move out of view
@ -28,10 +24,16 @@ const SIM_FRAME_DURATION: f32 = 0.5;
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(AssetPlugin {
.add_plugins(
DefaultPlugins
.set(AssetPlugin {
file_path: "assets".into(),
..default()
}))
})
.set(TaskPoolPlugin {
task_pool_options: TaskPoolOptions::with_num_threads(1),
}),
)
.add_plugins(FeathersPlugins)
.add_plugins(GameActionsPlugin)
.init_resource::<NextCoordinates>()
@ -109,7 +111,7 @@ fn main() {
.add_systems(
Update,
(
control_audio_players.in_set(GameSet::React),
control_audio_players.run_if(any_component_changed::<Cell>.or_else(state_changed::<SimulationPlayback>).or_else(state_changed::<AudioPlayback>)).in_set(GameSet::React),
update_pitch_map_resource_ui.run_if(resource_changed::<PitchMap>),
),
)
@ -193,7 +195,6 @@ enum AudioPlayback {
#[derive(Debug, Component, PartialEq, Default, Clone, FromTemplate)]
#[require(Visibility, Transform)]
enum UiButton {
Power,
Pause,
Step,
Play,
@ -210,14 +211,13 @@ enum UiButton {
impl UiButton {
fn icon(&self) -> &'static str {
match self {
Self::Power => "power.png",
Self::Pause => "pause.png",
Self::Step => "next.png",
Self::Play => "forward.png",
Self::ZoomOut => "zoomOut.png",
Self::ZoomIn => "zoomIn.png",
Self::PlayAudio => "audioOn.png",
Self::MuteAudio => "audioOff.png",
Self::PlayAudio => "audioOff.png",
Self::MuteAudio => "audioOn.png",
Self::Reset => "return.png",
Self::Wipe => "trashcan.png",
Self::None => todo!(),
@ -483,7 +483,6 @@ fn primary_ui() -> impl Scene {
Text("Welcome to a little experiment I made: adding sound to Conways Game of Life") ThemedText,
Text("---") ThemedText,
Text("The buttons above control (in order)") ThemedText,
Text("Quit the game") ThemedText,
Text("Start/Pause the simulation") ThemedText,
Text("Step the simulation") ThemedText,
Text("Reset the world state") ThemedText,
@ -530,8 +529,6 @@ fn primary_ui() -> impl Scene {
justify_content: JustifyContent::Start,
}
Children [
ui_button(UiButton::Power)
ActionSource<Action>(Action::Quit),
ui_toggle_button((UiButton::Pause, bsn! { SimulationPlayback::Pause }), (UiButton::Play, bsn! { SimulationPlayback::Run }))
ActionSource<SimAction>(SimAction::Toggle),
ui_button(UiButton::Step)
@ -544,8 +541,10 @@ fn primary_ui() -> impl Scene {
ActionSource<ZoomAction>(ZoomAction::ZoomOut),
ui_button(UiButton::ZoomIn)
ActionSource<ZoomAction>(ZoomAction::ZoomIn),
ui_toggle_button((UiButton::MuteAudio, bsn! { AudioPlayback::Mute }), (UiButton::PlayAudio, bsn! { AudioPlayback::Play }))
ActionSource<AudioAction>(AudioAction::Toggle),
ui_toggle_button(
(UiButton::MuteAudio, bsn! { AudioPlayback::Mute }),
(UiButton::PlayAudio, bsn! { AudioPlayback::Play })
) ActionSource<AudioAction>(AudioAction::Toggle),
]
]
}
@ -893,26 +892,11 @@ enum Lifecycle {
fn new_cell(
Coordinates { x, y }: Coordinates,
cell_assets: &Res<CellAssets>,
sinks: &Query<Entity, With<AudioSink>>,
sim_state: &Res<State<SimulationPlayback>>,
audio_state: &Res<State<AudioPlayback>>,
pitch_map: &Res<PitchMap>,
) -> impl Scene {
let c = Coordinates { x, y };
let volume = {
if *sim_state.get() == SimulationPlayback::Run && *audio_state.get() == AudioPlayback::Play
{
let ratio = 1.0 / (sinks.count() + 1) as f32;
Volume::Linear(ratio)
} else {
Volume::SILENT
}
};
let playback_settings = PlaybackSettings::ONCE.with_volume(volume);
let cell_pitch = c.to_cell_pitch(&(*pitch_map));
let translation = c.as_translation() + Vec3::new(0.0, 0.0, 1.0);
debug!("cell pitch: {:#?}", cell_pitch);
debug!("materials: {:#?}", cell_assets.materials);
@ -921,8 +905,7 @@ fn new_cell(
Cell
Coordinates { x, y }
template_value(cell_pitch.clone())
template_value(playback_settings)
template_value(Transform::from_translation(c.as_translation() + Vec3::new(0.0, 0.0, 1.0)))
template_value(Transform::from_translation(translation))
template_value(Mesh2dTemplate(cell_assets.mesh.clone().into()))
// MeshMaterial2d added by update_material
template_value(MeshMaterial2dTemplate(cell_assets.materials.get(&cell_pitch).unwrap().clone().into()))
@ -933,23 +916,13 @@ fn cell_lifecycle(
mut reader: MessageReader<Lifecycle>,
mut commands: Commands,
cell_assets: Res<CellAssets>,
query: Query<Entity, With<AudioSink>>,
sim_state: Res<State<SimulationPlayback>>,
audio_state: Res<State<AudioPlayback>>,
pitch_map: Res<PitchMap>,
) {
reader.read().for_each(|msg| match msg {
Lifecycle::Alive(c) => {
debug!("creating {:?}", c);
// TODO: Safeguard: ensure no other cell there?
commands.spawn_scene(new_cell(
c.clone(),
&cell_assets,
&query,
&sim_state,
&audio_state,
&pitch_map,
));
commands.spawn_scene(new_cell(c.clone(), &cell_assets, &pitch_map));
}
Lifecycle::Dead(e) => {
commands.entity(*e).despawn();
@ -1414,7 +1387,7 @@ fn update_camera_position(mut query: Query<(&Coordinates, &mut Transform), With<
fn spawn_audio_players(mut commands: Commands, cell_assets: Res<CellAssets>) {
CellPitch::iter_all().for_each(|cp| {
let h = cell_assets.pitches.get(&cp).unwrap().clone();
commands.spawn((AudioPlayer(h), cp, PlaybackSettings::default().with_volume(Volume::Linear(0.0))));
commands.spawn((AudioPlayer(h), cp, PlaybackSettings::default().muted()));
});
}
@ -1424,55 +1397,71 @@ fn control_audio_players(
simulation_state: Res<State<SimulationPlayback>>,
mut players: Query<(&mut AudioSink, &CellPitch)>,
cells: Query<&CellPitch, With<Cell>>,
mut active_cells: Local<HashMap<CellPitch, usize>>,
) {
fn turn_off(sink: &mut AudioSink) {
if !sink.is_paused() {
sink.pause();
}
if !sink.is_muted() {
sink.mute();
}
}
fn turn_on(sink: &mut AudioSink, vol: f32) {
// Set the volume of the sink to the proportional volume
sink.set_volume(Volume::Linear(vol));
// Play a paused sink
if sink.is_paused() {
sink.play();
}
// Unmute a muted sink
if sink.is_muted() {
sink.unmute();
}
}
match audio_state.get() {
AudioPlayback::Play => {
match simulation_state.get() {
SimulationPlayback::Run => {
let mut s: HashMap<CellPitch, usize> = HashMap::new();
// Count number of each type of cell for audio
cells.iter().for_each(|cp| {
let this = s.entry(cp.clone()).or_insert(0);
let this = active_cells.entry(cp.clone()).or_insert(0);
*this += 1;
});
let total: usize = s.values().sum();
// Get the total to compute per-cell ratio
let total: usize = active_cells.values().sum();
players.iter_mut().for_each(|(mut sink, cp)| {
match s.get(cp) {
// Update each type of cell with the correct volume
players
.iter_mut()
.for_each(|(mut sink, cp)| match active_cells.get(cp) {
Some(v) => {
let new_volume: f32 = (*v as f64 / total as f64) as f32;
debug!("Setting volume for {:?} to {:?}", cp, new_volume);
if sink.is_paused() {
sink.play();
turn_on(sink.as_mut(), new_volume);
}
sink.set_volume(Volume::Linear(new_volume));
},
None => {
sink.pause();
sink.set_volume(Volume::Linear(0.0));
}
turn_off(sink.as_mut());
}
});
// create distribution of cell pitches
// then go back and adjust volume based on % with this CellPitch
// if volume is 0, pause
// if volume > 0, play
},
active_cells.clear();
}
// If the sim is paused or stepping, mute an dpause audios
_ => {
players.iter_mut().for_each(|(mut sink, _)| {
sink.pause();
sink.set_volume(Volume::Linear(0.0));
turn_off(sink.as_mut());
});
}
}
}
AudioPlayback::Mute => {
players.iter_mut().for_each(|(mut sink, _)| {
sink.pause();
sink.set_volume(Volume::Linear(0.0));
turn_off(sink.as_mut());
});
},
}
}
}

Loading…
Cancel
Save