|
|
|
|
@ -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 {
|
|
|
|
|
file_path: "assets".into(),
|
|
|
|
|
..default()
|
|
|
|
|
}))
|
|
|
|
|
.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();
|
|
|
|
|
}
|
|
|
|
|
sink.set_volume(Volume::Linear(new_volume));
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
turn_on(sink.as_mut(), 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());
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|