|
|
|
@ -2,7 +2,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
use std::time::Duration;
|
|
|
|
use std::time::Duration;
|
|
|
|
|
|
|
|
|
|
|
|
use bevy::{picking::hover::Hovered, platform::collections::HashMap};
|
|
|
|
use bevy::{audio::Volume, picking::hover::Hovered, platform::collections::HashMap};
|
|
|
|
use engine::*;
|
|
|
|
use engine::*;
|
|
|
|
|
|
|
|
|
|
|
|
const TILE: &str = "tileGrey_01.png";
|
|
|
|
const TILE: &str = "tileGrey_01.png";
|
|
|
|
@ -27,6 +27,7 @@ fn main() {
|
|
|
|
.init_resource::<Coordinates>()
|
|
|
|
.init_resource::<Coordinates>()
|
|
|
|
.init_resource::<CellAssets>()
|
|
|
|
.init_resource::<CellAssets>()
|
|
|
|
.init_state::<SimulationState>()
|
|
|
|
.init_state::<SimulationState>()
|
|
|
|
|
|
|
|
.insert_resource(GlobalVolume::new(Volume::Linear(1.0)))
|
|
|
|
.add_message::<Lifecycle>()
|
|
|
|
.add_message::<Lifecycle>()
|
|
|
|
.add_message::<SimulationState>()
|
|
|
|
.add_message::<SimulationState>()
|
|
|
|
.add_systems(Startup, setup)
|
|
|
|
.add_systems(Startup, setup)
|
|
|
|
@ -37,6 +38,10 @@ fn main() {
|
|
|
|
Update,
|
|
|
|
Update,
|
|
|
|
update_grid_position.run_if(on_message::<CursorMoved>),
|
|
|
|
update_grid_position.run_if(on_message::<CursorMoved>),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
.add_systems(
|
|
|
|
|
|
|
|
Update,
|
|
|
|
|
|
|
|
modulate_volume // TODO: .run_if(any_component_added::<AudioSink>)
|
|
|
|
|
|
|
|
)
|
|
|
|
.add_systems(Update, grid_gizmo)
|
|
|
|
.add_systems(Update, grid_gizmo)
|
|
|
|
.add_systems(
|
|
|
|
.add_systems(
|
|
|
|
Update,
|
|
|
|
Update,
|
|
|
|
@ -58,6 +63,10 @@ fn main() {
|
|
|
|
.run_if(in_state(SimulationState::Run))
|
|
|
|
.run_if(in_state(SimulationState::Run))
|
|
|
|
.run_if(cooldown_secs(FRAME_DURATION)),
|
|
|
|
.run_if(cooldown_secs(FRAME_DURATION)),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
.add_systems(
|
|
|
|
|
|
|
|
Update,
|
|
|
|
|
|
|
|
start_audio_synced.run_if(cooldown_secs(FRAME_DURATION))
|
|
|
|
|
|
|
|
)
|
|
|
|
.add_systems(
|
|
|
|
.add_systems(
|
|
|
|
Update,
|
|
|
|
Update,
|
|
|
|
simulation_step.run_if(on_message::<SimulationState>),
|
|
|
|
simulation_step.run_if(on_message::<SimulationState>),
|
|
|
|
@ -325,7 +334,17 @@ fn on_add_coordinates(
|
|
|
|
commands.entity(trigger.entity).insert(t);
|
|
|
|
commands.entity(trigger.entity).insert(t);
|
|
|
|
|
|
|
|
|
|
|
|
let p = cell_assets.pitches.get(&c.as_cell_pitch()).unwrap();
|
|
|
|
let p = cell_assets.pitches.get(&c.as_cell_pitch()).unwrap();
|
|
|
|
commands.entity(trigger.entity).insert((AudioPlayer(p.clone()), PlaybackSettings::LOOP));
|
|
|
|
commands.entity(trigger.entity).insert((AudioPlayer(p.clone()), PlaybackSettings::ONCE));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// All audio starts paused so we can keep them in sync
|
|
|
|
|
|
|
|
/// This starts audios when the system is triggered, which is on a timer
|
|
|
|
|
|
|
|
fn start_audio_synced(query: Query<&AudioSink, With<Cell>>) {
|
|
|
|
|
|
|
|
query.iter().for_each(|sink| {
|
|
|
|
|
|
|
|
if sink.is_paused() {
|
|
|
|
|
|
|
|
sink.play()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// When the cursor moves, update the Coordiantes
|
|
|
|
/// When the cursor moves, update the Coordiantes
|
|
|
|
@ -552,6 +571,12 @@ enum Note {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl Note {
|
|
|
|
impl Note {
|
|
|
|
|
|
|
|
/// Pitches:
|
|
|
|
|
|
|
|
/// A -440 Hz
|
|
|
|
|
|
|
|
/// B - 493.88 Hz
|
|
|
|
|
|
|
|
/// C# - 554.37 Hz
|
|
|
|
|
|
|
|
/// E - 659.25 Hz
|
|
|
|
|
|
|
|
/// F# - 698.46 Hz
|
|
|
|
fn as_frequency(&self) -> f32 {
|
|
|
|
fn as_frequency(&self) -> f32 {
|
|
|
|
match self {
|
|
|
|
match self {
|
|
|
|
Note::A => 440.0,
|
|
|
|
Note::A => 440.0,
|
|
|
|
@ -570,26 +595,13 @@ struct CellPitch {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl CellPitch {
|
|
|
|
impl CellPitch {
|
|
|
|
/// Pitches:
|
|
|
|
|
|
|
|
/// A -440 Hz
|
|
|
|
|
|
|
|
/// B - 493.88 Hz
|
|
|
|
|
|
|
|
/// C# - 554.37 Hz
|
|
|
|
|
|
|
|
/// E - 659.25 Hz
|
|
|
|
|
|
|
|
/// F# - 698.46 Hz
|
|
|
|
|
|
|
|
fn to_frequency(&self) -> f32 {
|
|
|
|
fn to_frequency(&self) -> f32 {
|
|
|
|
let base_note = self.note.as_frequency();
|
|
|
|
// Thanks Claude Opus 4.8
|
|
|
|
match self.octave {
|
|
|
|
self.note.as_frequency() * 2f32.powi(self.octave as i32)
|
|
|
|
// Negative octave values -> lower notes
|
|
|
|
|
|
|
|
..=-1 => base_note / (self.octave as f32),
|
|
|
|
|
|
|
|
0 => base_note,
|
|
|
|
|
|
|
|
// Positive octave values -> higher notes
|
|
|
|
|
|
|
|
1.. => base_note * (self.octave as f32),
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn load_audio_tones(mut pitches: ResMut<Assets<Pitch>>, mut cell_assets: ResMut<CellAssets>) {
|
|
|
|
fn load_audio_tones(mut pitches: ResMut<Assets<Pitch>>, mut cell_assets: ResMut<CellAssets>) {
|
|
|
|
let duration = Duration::from_secs_f32(FRAME_DURATION);
|
|
|
|
|
|
|
|
let notes = [Note::A, Note::B, Note::CSharp, Note::E, Note::FSharp];
|
|
|
|
let notes = [Note::A, Note::B, Note::CSharp, Note::E, Note::FSharp];
|
|
|
|
let octaves = [-2, -1, 0, 1, 2];
|
|
|
|
let octaves = [-2, -1, 0, 1, 2];
|
|
|
|
|
|
|
|
|
|
|
|
@ -600,6 +612,7 @@ fn load_audio_tones(mut pitches: ResMut<Assets<Pitch>>, mut cell_assets: ResMut<
|
|
|
|
octave: *octave,
|
|
|
|
octave: *octave,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
let frequency = cell_pitch.to_frequency();
|
|
|
|
let frequency = cell_pitch.to_frequency();
|
|
|
|
|
|
|
|
let duration = Duration::from_secs(100);
|
|
|
|
let handle = pitches.add(Pitch {
|
|
|
|
let handle = pitches.add(Pitch {
|
|
|
|
frequency,
|
|
|
|
frequency,
|
|
|
|
duration,
|
|
|
|
duration,
|
|
|
|
@ -610,6 +623,11 @@ fn load_audio_tones(mut pitches: ResMut<Assets<Pitch>>, mut cell_assets: ResMut<
|
|
|
|
debug_assert!(cell_assets.pitches.iter().len() == 25);
|
|
|
|
debug_assert!(cell_assets.pitches.iter().len() == 25);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn modulate_volume(mut gv: ResMut<GlobalVolume>, active: Query<Entity, With<AudioSink>>) {
|
|
|
|
|
|
|
|
let ratio = 1.0 / active.count() as f32;
|
|
|
|
|
|
|
|
gv.volume = Volume::Linear(ratio);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn load_materials(
|
|
|
|
fn load_materials(
|
|
|
|
server: ResMut<AssetServer>,
|
|
|
|
server: ResMut<AssetServer>,
|
|
|
|
mut cell_assets: ResMut<CellAssets>,
|
|
|
|
mut cell_assets: ResMut<CellAssets>,
|
|
|
|
@ -638,14 +656,3 @@ fn load_materials(
|
|
|
|
fn load_meshes(mut cell_assets: ResMut<CellAssets>, mut meshes: ResMut<Assets<Mesh>>) {
|
|
|
|
fn load_meshes(mut cell_assets: ResMut<CellAssets>, mut meshes: ResMut<Assets<Mesh>>) {
|
|
|
|
cell_assets.mesh = meshes.add(Rectangle::new(SCALE, SCALE));
|
|
|
|
cell_assets.mesh = meshes.add(Rectangle::new(SCALE, SCALE));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn play_audio_tone(cell_assets: Res<CellAssets>, mut commands: Commands) {
|
|
|
|
|
|
|
|
let handle = cell_assets
|
|
|
|
|
|
|
|
.pitches
|
|
|
|
|
|
|
|
.get(&CellPitch {
|
|
|
|
|
|
|
|
note: Note::A,
|
|
|
|
|
|
|
|
octave: 0,
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
commands.spawn((AudioPlayer(handle.clone()), PlaybackSettings::LOOP));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|