|
|
|
|
@ -14,56 +14,46 @@ fn main() {
|
|
|
|
|
.init_resource::<NextCoordinates>()
|
|
|
|
|
.init_resource::<CellAssets>()
|
|
|
|
|
.init_resource::<Panning>()
|
|
|
|
|
.init_resource::<PitchMap>()
|
|
|
|
|
.insert_resource(ClearColor(WHITE.into()))
|
|
|
|
|
.insert_resource(UiTheme(create_light_theme()))
|
|
|
|
|
.init_state::<SimulationPlayback>()
|
|
|
|
|
.init_state::<AudioPlayback>()
|
|
|
|
|
.add_message::<Lifecycle>()
|
|
|
|
|
.add_systems(Startup, the_camera.spawn())
|
|
|
|
|
.add_systems(Startup, the_ui.spawn())
|
|
|
|
|
.add_systems(Startup, load_audio_tones)
|
|
|
|
|
.add_systems(Startup, load_materials)
|
|
|
|
|
.add_systems(Startup, load_meshes)
|
|
|
|
|
.add_systems(
|
|
|
|
|
Update,
|
|
|
|
|
update_grid_position.run_if(on_message::<CursorMoved>),
|
|
|
|
|
Startup,
|
|
|
|
|
(
|
|
|
|
|
the_camera.spawn(),
|
|
|
|
|
the_ui.spawn(),
|
|
|
|
|
load_audio_tones,
|
|
|
|
|
load_materials,
|
|
|
|
|
load_meshes,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.add_systems(
|
|
|
|
|
Update,
|
|
|
|
|
(
|
|
|
|
|
update_grid_position.run_if(on_message::<CursorMoved>),
|
|
|
|
|
// Run if any component added/removed: AudioSink
|
|
|
|
|
// Run if state changed: AudioPlayback
|
|
|
|
|
control_volume,
|
|
|
|
|
)
|
|
|
|
|
.add_systems(Update, grid_gizmo)
|
|
|
|
|
.add_systems(
|
|
|
|
|
Update,
|
|
|
|
|
de_spawn_cells
|
|
|
|
|
.run_if(not(is_ui_hovered))
|
|
|
|
|
.run_if(not(is_panning))
|
|
|
|
|
.run_if(input_just_released(MouseButton::Left)),
|
|
|
|
|
)
|
|
|
|
|
.add_systems(
|
|
|
|
|
Update,
|
|
|
|
|
assert_cell_uniqueness.run_if(any_with_component::<Cell>),
|
|
|
|
|
)
|
|
|
|
|
.add_systems(
|
|
|
|
|
Update,
|
|
|
|
|
simulation_step.run_if(input_just_pressed(KeyCode::ArrowRight)),
|
|
|
|
|
)
|
|
|
|
|
.add_systems(
|
|
|
|
|
Update,
|
|
|
|
|
simulation_step
|
|
|
|
|
.run_if(in_state(SimulationPlayback::Run))
|
|
|
|
|
.run_if(cooldown_secs(SIM_FRAME_DURATION)),
|
|
|
|
|
)
|
|
|
|
|
.add_systems(
|
|
|
|
|
Update,
|
|
|
|
|
simulation_step.run_if(state_changed::<SimulationPlayback>),
|
|
|
|
|
)
|
|
|
|
|
grid_gizmo,
|
|
|
|
|
// TODO: Visualize audio play/mute buttons enable/disable
|
|
|
|
|
.add_systems(Update, toggle_interactive::<SimulationPlayback>)
|
|
|
|
|
.add_systems(Update, toggle_interactive::<AudioPlayback>)
|
|
|
|
|
toggle_interactive::<SimulationPlayback>,
|
|
|
|
|
// TODO Visualize simulation run/pause buttons enable/disable
|
|
|
|
|
toggle_interactive::<AudioPlayback>,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.add_systems(Update, cell_lifecycle.run_if(on_message::<Lifecycle>))
|
|
|
|
|
.add_systems(Update, mouse_pan.run_if(on_message::<MouseMotion>))
|
|
|
|
|
.add_systems(Update, set_pan_state)
|
|
|
|
|
@ -94,6 +84,7 @@ enum UiButton {
|
|
|
|
|
SubHighOctave,
|
|
|
|
|
AddLowOctave,
|
|
|
|
|
SubLowOctave,
|
|
|
|
|
Note,
|
|
|
|
|
#[default]
|
|
|
|
|
None,
|
|
|
|
|
}
|
|
|
|
|
@ -113,6 +104,7 @@ impl UiButton {
|
|
|
|
|
Self::SubHighOctave => "todo.png",
|
|
|
|
|
Self::AddLowOctave => "todo.png",
|
|
|
|
|
Self::SubLowOctave => "todo.png",
|
|
|
|
|
Self::Note => "todo.png",
|
|
|
|
|
Self::None => todo!(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -207,6 +199,66 @@ fn the_ui() -> impl Scene {
|
|
|
|
|
on(|_: On<Activate>, mut next: ResMut<NextState<AudioPlayback>>| {
|
|
|
|
|
next.set(AudioPlayback::Play);
|
|
|
|
|
}),
|
|
|
|
|
ui_button(UiButton::Note)
|
|
|
|
|
Note::A
|
|
|
|
|
on(|_: On<Activate>, pitch_map: ResMut<PitchMap>| {
|
|
|
|
|
warn!("TODO")
|
|
|
|
|
}),
|
|
|
|
|
ui_button(UiButton::Note)
|
|
|
|
|
Note::ASharp
|
|
|
|
|
on(|_: On<Activate>, pitch_map: ResMut<PitchMap>| {
|
|
|
|
|
warn!("TODO")
|
|
|
|
|
}),
|
|
|
|
|
ui_button(UiButton::Note)
|
|
|
|
|
Note::B
|
|
|
|
|
on(|_: On<Activate>, pitch_map: ResMut<PitchMap>| {
|
|
|
|
|
warn!("TODO")
|
|
|
|
|
}),
|
|
|
|
|
ui_button(UiButton::Note)
|
|
|
|
|
Note::C
|
|
|
|
|
on(|_: On<Activate>, pitch_map: ResMut<PitchMap>| {
|
|
|
|
|
warn!("TODO")
|
|
|
|
|
}),
|
|
|
|
|
ui_button(UiButton::Note)
|
|
|
|
|
Note::CSharp
|
|
|
|
|
on(|_: On<Activate>, pitch_map: ResMut<PitchMap>| {
|
|
|
|
|
warn!("TODO")
|
|
|
|
|
}),
|
|
|
|
|
ui_button(UiButton::Note)
|
|
|
|
|
Note::D
|
|
|
|
|
on(|_: On<Activate>, pitch_map: ResMut<PitchMap>| {
|
|
|
|
|
warn!("TODO")
|
|
|
|
|
}),
|
|
|
|
|
ui_button(UiButton::Note)
|
|
|
|
|
Note::DSharp
|
|
|
|
|
on(|_: On<Activate>, pitch_map: ResMut<PitchMap>| {
|
|
|
|
|
warn!("TODO")
|
|
|
|
|
}),
|
|
|
|
|
ui_button(UiButton::Note)
|
|
|
|
|
Note::E
|
|
|
|
|
on(|_: On<Activate>, pitch_map: ResMut<PitchMap>| {
|
|
|
|
|
warn!("TODO")
|
|
|
|
|
}),
|
|
|
|
|
ui_button(UiButton::Note)
|
|
|
|
|
Note::F
|
|
|
|
|
on(|_: On<Activate>, pitch_map: ResMut<PitchMap>| {
|
|
|
|
|
warn!("TODO")
|
|
|
|
|
}),
|
|
|
|
|
ui_button(UiButton::Note)
|
|
|
|
|
Note::FSharp
|
|
|
|
|
on(|_: On<Activate>, pitch_map: ResMut<PitchMap>| {
|
|
|
|
|
warn!("TODO")
|
|
|
|
|
}),
|
|
|
|
|
ui_button(UiButton::Note)
|
|
|
|
|
Note::G
|
|
|
|
|
on(|_: On<Activate>, pitch_map: ResMut<PitchMap>| {
|
|
|
|
|
warn!("TODO")
|
|
|
|
|
}),
|
|
|
|
|
ui_button(UiButton::Note)
|
|
|
|
|
Note::GSharp
|
|
|
|
|
on(|_: On<Activate>, pitch_map: ResMut<PitchMap>| {
|
|
|
|
|
warn!("TODO")
|
|
|
|
|
}),
|
|
|
|
|
ui_button(UiButton::AddLowOctave)
|
|
|
|
|
on(|_: On<Activate>| {
|
|
|
|
|
warn!("TODO")
|
|
|
|
|
@ -564,29 +616,40 @@ struct CellAssets {
|
|
|
|
|
mesh: Handle<Mesh>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Hash, PartialEq, Eq, Debug, Clone)]
|
|
|
|
|
// TODO: Impl Ord to sort Notes
|
|
|
|
|
#[derive(Hash, PartialEq, Eq, Debug, Clone, Component, Default, FromTemplate)]
|
|
|
|
|
enum Note {
|
|
|
|
|
#[default]
|
|
|
|
|
A,
|
|
|
|
|
ASharp,
|
|
|
|
|
B,
|
|
|
|
|
C,
|
|
|
|
|
CSharp,
|
|
|
|
|
D,
|
|
|
|
|
DSharp,
|
|
|
|
|
E,
|
|
|
|
|
F,
|
|
|
|
|
FSharp,
|
|
|
|
|
G,
|
|
|
|
|
GSharp,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Note {
|
|
|
|
|
/// Pitches:
|
|
|
|
|
/// A -440 Hz
|
|
|
|
|
/// B - 493.88 Hz
|
|
|
|
|
/// C# - 554.37 Hz
|
|
|
|
|
/// E - 659.25 Hz
|
|
|
|
|
/// F# - 698.46 Hz
|
|
|
|
|
/// Pitches: https://en.wikipedia.org/wiki/Piano_key_frequencies
|
|
|
|
|
fn as_frequency(&self) -> f32 {
|
|
|
|
|
match self {
|
|
|
|
|
Note::A => 440.0,
|
|
|
|
|
Note::B => 493.88,
|
|
|
|
|
Note::CSharp => 553.37,
|
|
|
|
|
Note::E => 659.25,
|
|
|
|
|
Note::FSharp => 698.46,
|
|
|
|
|
Self::A => 440.0,
|
|
|
|
|
Self::ASharp => todo!(),
|
|
|
|
|
Self::B => todo!(),
|
|
|
|
|
Self::C => todo!(),
|
|
|
|
|
Self::CSharp => todo!(),
|
|
|
|
|
Self::D => todo!(),
|
|
|
|
|
Self::DSharp => todo!(),
|
|
|
|
|
Self::E => todo!(),
|
|
|
|
|
Self::F => todo!(),
|
|
|
|
|
Self::FSharp => todo!(),
|
|
|
|
|
Self::G => todo!(),
|
|
|
|
|
Self::GSharp => todo!(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -741,8 +804,6 @@ fn is_panning(panning: Res<Panning>) -> bool {
|
|
|
|
|
panning.0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Do not spawn cell when clicking UI
|
|
|
|
|
|
|
|
|
|
// This may or may not be an eye sore
|
|
|
|
|
fn create_light_theme() -> ThemeProps {
|
|
|
|
|
ThemeProps {
|
|
|
|
|
@ -779,3 +840,20 @@ fn toggle_interactive<T: Component + States + PartialEq>(
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Resource)]
|
|
|
|
|
struct PitchMap {
|
|
|
|
|
notes: Vec<Note>,
|
|
|
|
|
octaves_up: usize,
|
|
|
|
|
octaves_down: usize,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for PitchMap {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
notes: vec![Note::A, Note::B, Note::CSharp, Note::E, Note::FSharp],
|
|
|
|
|
octaves_up: 3,
|
|
|
|
|
octaves_down: 3,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|