|
|
|
@ -1,17 +1,13 @@
|
|
|
|
// TODO:
|
|
|
|
// TODO:
|
|
|
|
// * Lock down system scheduling/run_if conditions
|
|
|
|
// * Fix randomly audio not working
|
|
|
|
// * Performance: have a limit on per-note+octave sounds
|
|
|
|
// * Lock down system scheduling/run_if conditions
|
|
|
|
// * Use a finite set of N audio entities and scale volume instead of
|
|
|
|
// * Fix tiling repeating
|
|
|
|
// spawning a bunch of audio entities which is causing issues at >100
|
|
|
|
// * Negative coordinates should not mirror
|
|
|
|
// cells
|
|
|
|
// * UI widget explaining rules/hotkeys
|
|
|
|
// * Stop creating cells when I didn't mean to (clicking UI, click + dragging)
|
|
|
|
|
|
|
|
// * Keep some controls open when main ui panel is closed
|
|
|
|
|
|
|
|
// * Exclude power button from web build
|
|
|
|
|
|
|
|
// * Show shadow of piece (same tile, just with alpha transparency)
|
|
|
|
// * Show shadow of piece (same tile, just with alpha transparency)
|
|
|
|
// * Dark mode
|
|
|
|
// * Start with an interesting pattern
|
|
|
|
|
|
|
|
// * UI minimize widgets
|
|
|
|
// * Make click and drag feel better
|
|
|
|
// * Make click and drag feel better
|
|
|
|
// * Add arrow keys to move around
|
|
|
|
|
|
|
|
// * "Follow" enhancement: Zoom in/out
|
|
|
|
|
|
|
|
#![allow(clippy::complexity)]
|
|
|
|
#![allow(clippy::complexity)]
|
|
|
|
// Only because of FromTemplat macros unfortunately...
|
|
|
|
// Only because of FromTemplat macros unfortunately...
|
|
|
|
#![allow(dead_code)]
|
|
|
|
#![allow(dead_code)]
|
|
|
|
@ -41,8 +37,6 @@ fn main() {
|
|
|
|
.insert_resource(UiTheme(create_light_theme()))
|
|
|
|
.insert_resource(UiTheme(create_light_theme()))
|
|
|
|
.init_state::<SimulationPlayback>()
|
|
|
|
.init_state::<SimulationPlayback>()
|
|
|
|
.init_state::<AudioPlayback>()
|
|
|
|
.init_state::<AudioPlayback>()
|
|
|
|
.init_state::<DebugUi>()
|
|
|
|
|
|
|
|
.init_state::<MainUi>()
|
|
|
|
|
|
|
|
.add_message::<Lifecycle>()
|
|
|
|
.add_message::<Lifecycle>()
|
|
|
|
.add_systems(
|
|
|
|
.add_systems(
|
|
|
|
Startup,
|
|
|
|
Startup,
|
|
|
|
@ -54,52 +48,22 @@ fn main() {
|
|
|
|
load_meshes,
|
|
|
|
load_meshes,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.configure_sets(
|
|
|
|
|
|
|
|
Update,
|
|
|
|
|
|
|
|
(GameSet::Plan, GameSet::Apply, GameSet::React).chain(),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
// Cell management/Simulation
|
|
|
|
// Cell management/Simulation
|
|
|
|
.add_systems(
|
|
|
|
.add_systems(
|
|
|
|
Update,
|
|
|
|
Update,
|
|
|
|
(
|
|
|
|
(
|
|
|
|
// Produce lifecycle messages
|
|
|
|
cell_lifecycle.run_if(on_message::<Lifecycle>),
|
|
|
|
(
|
|
|
|
update_grid_position.run_if(on_message::<CursorMoved>),
|
|
|
|
default_board.run_if(run_once),
|
|
|
|
de_spawn_cells
|
|
|
|
update_grid_position.run_if(on_message::<CursorMoved>),
|
|
|
|
.run_if(not(is_ui_hovered))
|
|
|
|
de_spawn_cells
|
|
|
|
.run_if(not(is_panning))
|
|
|
|
.run_if(not(is_ui_hovered))
|
|
|
|
.run_if(input_just_released(MouseButton::Left)),
|
|
|
|
.run_if(not(is_panning))
|
|
|
|
simulation_step
|
|
|
|
.run_if(input_just_released(MouseButton::Left)),
|
|
|
|
.run_if(in_state(SimulationPlayback::Run))
|
|
|
|
simulation_step.run_if(
|
|
|
|
.run_if(cooldown_secs(SIM_FRAME_DURATION)),
|
|
|
|
in_state(SimulationPlayback::Run)
|
|
|
|
simulation_step.run_if(state_changed::<SimulationPlayback>),
|
|
|
|
.and_then(cooldown_secs(SIM_FRAME_DURATION))
|
|
|
|
pause_simulation.run_if(in_state(SimulationPlayback::Step)),
|
|
|
|
.or_else(state_changed::<SimulationPlayback>),
|
|
|
|
update_material,
|
|
|
|
),
|
|
|
|
|
|
|
|
pause_simulation.run_if(in_state(SimulationPlayback::Step)),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
.in_set(GameSet::Plan),
|
|
|
|
|
|
|
|
// Apply lifecycle message
|
|
|
|
|
|
|
|
cell_lifecycle
|
|
|
|
|
|
|
|
.run_if(on_message::<Lifecycle>)
|
|
|
|
|
|
|
|
.in_set(GameSet::Apply),
|
|
|
|
|
|
|
|
// React to board changes
|
|
|
|
|
|
|
|
(
|
|
|
|
|
|
|
|
reassign_cell_pitch.run_if(resource_changed::<PitchMap>),
|
|
|
|
|
|
|
|
update_material.run_if(
|
|
|
|
|
|
|
|
any_component_added::<CellPitch>
|
|
|
|
|
|
|
|
.or_else(any_component_changed::<CellPitch>),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
camera_follow
|
|
|
|
|
|
|
|
.run_if(in_state(SimulationPlayback::Run))
|
|
|
|
|
|
|
|
.run_if(any_component_changed::<Coordinates>),
|
|
|
|
|
|
|
|
update_camera_position
|
|
|
|
|
|
|
|
.run_if(in_state(SimulationPlayback::Run))
|
|
|
|
|
|
|
|
.run_if(not(query_condition_empty::<(
|
|
|
|
|
|
|
|
With<Camera>,
|
|
|
|
|
|
|
|
Changed<Coordinates>,
|
|
|
|
|
|
|
|
)>)),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
.in_set(GameSet::React),
|
|
|
|
|
|
|
|
),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
// Audio Systems
|
|
|
|
// Audio Systems
|
|
|
|
@ -108,41 +72,28 @@ fn main() {
|
|
|
|
(
|
|
|
|
(
|
|
|
|
control_volume,
|
|
|
|
control_volume,
|
|
|
|
update_pitch_map_resource_ui.run_if(resource_changed::<PitchMap>),
|
|
|
|
update_pitch_map_resource_ui.run_if(resource_changed::<PitchMap>),
|
|
|
|
update_audio
|
|
|
|
reassign_cell_pitch.run_if(resource_changed::<PitchMap>),
|
|
|
|
.run_if(
|
|
|
|
update_audio,
|
|
|
|
any_component_added::<CellPitch>
|
|
|
|
|
|
|
|
.or_else(any_component_changed::<CellPitch>),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
.in_set(GameSet::React),
|
|
|
|
|
|
|
|
),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
// Debugging
|
|
|
|
// Debugging
|
|
|
|
.add_systems(
|
|
|
|
.add_systems(
|
|
|
|
Update,
|
|
|
|
Update,
|
|
|
|
(
|
|
|
|
(
|
|
|
|
|
|
|
|
assert_cell_uniqueness.run_if(any_with_component::<Cell>),
|
|
|
|
grid_gizmo,
|
|
|
|
grid_gizmo,
|
|
|
|
|
|
|
|
update_debug_info_cell_count,
|
|
|
|
update_debug_info_coordinates,
|
|
|
|
update_debug_info_coordinates,
|
|
|
|
(
|
|
|
|
update_debug_info_active,
|
|
|
|
assert_cell_uniqueness
|
|
|
|
|
|
|
|
.run_if(any_with_component::<Cell>)
|
|
|
|
|
|
|
|
.after(update_material)
|
|
|
|
|
|
|
|
.after(update_audio),
|
|
|
|
|
|
|
|
update_debug_info_cell_count,
|
|
|
|
|
|
|
|
update_debug_info_active,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
.in_set(GameSet::React),
|
|
|
|
|
|
|
|
),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
// Ui Controllers
|
|
|
|
// Ui Controllers
|
|
|
|
.add_systems(
|
|
|
|
.add_systems(
|
|
|
|
Update,
|
|
|
|
Update,
|
|
|
|
(
|
|
|
|
(
|
|
|
|
toggle_state_visible::<SimulationPlayback>
|
|
|
|
toggle_state_visible::<SimulationPlayback>.run_if(state_changed::<SimulationPlayback>),
|
|
|
|
.run_if(state_changed::<SimulationPlayback>),
|
|
|
|
|
|
|
|
manage_interactive::<SimulationPlayback>,
|
|
|
|
manage_interactive::<SimulationPlayback>,
|
|
|
|
toggle_state_visible::<AudioPlayback>.run_if(state_changed::<AudioPlayback>),
|
|
|
|
toggle_state_visible::<AudioPlayback>.run_if(state_changed::<AudioPlayback>),
|
|
|
|
toggle_state_visible::<DebugUi>.run_if(state_changed::<DebugUi>),
|
|
|
|
|
|
|
|
toggle_state_visible::<MainUi>.run_if(state_changed::<MainUi>),
|
|
|
|
|
|
|
|
manage_interactive::<AudioPlayback>,
|
|
|
|
manage_interactive::<AudioPlayback>,
|
|
|
|
update_note_interactivity,
|
|
|
|
update_note_interactivity,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
@ -157,13 +108,6 @@ fn main() {
|
|
|
|
|
|
|
|
|
|
|
|
const SCALE: f32 = 100.0;
|
|
|
|
const SCALE: f32 = 100.0;
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
|
|
|
|
|
|
|
|
enum GameSet {
|
|
|
|
|
|
|
|
Plan,
|
|
|
|
|
|
|
|
Apply,
|
|
|
|
|
|
|
|
React,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Component, PartialEq, Clone, Default)]
|
|
|
|
#[derive(Component, PartialEq, Clone, Default)]
|
|
|
|
enum DebugInfo {
|
|
|
|
enum DebugInfo {
|
|
|
|
CellCount,
|
|
|
|
CellCount,
|
|
|
|
@ -173,20 +117,6 @@ enum DebugInfo {
|
|
|
|
Empty,
|
|
|
|
Empty,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(States, Debug, Default, Hash, PartialEq, Eq, Clone, Component, FromTemplate)]
|
|
|
|
|
|
|
|
enum DebugUi {
|
|
|
|
|
|
|
|
#[default]
|
|
|
|
|
|
|
|
Visible,
|
|
|
|
|
|
|
|
Hidden,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(States, Debug, Default, Hash, PartialEq, Eq, Clone, Component, FromTemplate)]
|
|
|
|
|
|
|
|
enum MainUi {
|
|
|
|
|
|
|
|
#[default]
|
|
|
|
|
|
|
|
Visible,
|
|
|
|
|
|
|
|
Hidden,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(States, Debug, Default, Hash, PartialEq, Eq, Clone, Component, FromTemplate)]
|
|
|
|
#[derive(States, Debug, Default, Hash, PartialEq, Eq, Clone, Component, FromTemplate)]
|
|
|
|
enum AudioPlayback {
|
|
|
|
enum AudioPlayback {
|
|
|
|
#[default]
|
|
|
|
#[default]
|
|
|
|
@ -205,6 +135,11 @@ enum UiButton {
|
|
|
|
ZoomIn,
|
|
|
|
ZoomIn,
|
|
|
|
PlayAudio,
|
|
|
|
PlayAudio,
|
|
|
|
MuteAudio,
|
|
|
|
MuteAudio,
|
|
|
|
|
|
|
|
AddHighOctave,
|
|
|
|
|
|
|
|
SubHighOctave,
|
|
|
|
|
|
|
|
AddLowOctave,
|
|
|
|
|
|
|
|
SubLowOctave,
|
|
|
|
|
|
|
|
Note,
|
|
|
|
Reset,
|
|
|
|
Reset,
|
|
|
|
Wipe,
|
|
|
|
Wipe,
|
|
|
|
#[default]
|
|
|
|
#[default]
|
|
|
|
@ -222,6 +157,11 @@ impl UiButton {
|
|
|
|
Self::ZoomIn => "zoomIn.png",
|
|
|
|
Self::ZoomIn => "zoomIn.png",
|
|
|
|
Self::PlayAudio => "audioOn.png",
|
|
|
|
Self::PlayAudio => "audioOn.png",
|
|
|
|
Self::MuteAudio => "audioOff.png",
|
|
|
|
Self::MuteAudio => "audioOff.png",
|
|
|
|
|
|
|
|
Self::AddHighOctave => "todo.png",
|
|
|
|
|
|
|
|
Self::SubHighOctave => "todo.png",
|
|
|
|
|
|
|
|
Self::AddLowOctave => "todo.png",
|
|
|
|
|
|
|
|
Self::SubLowOctave => "todo.png",
|
|
|
|
|
|
|
|
Self::Note => "todo.png",
|
|
|
|
Self::Reset => "return.png",
|
|
|
|
Self::Reset => "return.png",
|
|
|
|
Self::Wipe => "trashcan.png",
|
|
|
|
Self::Wipe => "trashcan.png",
|
|
|
|
Self::None => todo!(),
|
|
|
|
Self::None => todo!(),
|
|
|
|
@ -259,10 +199,7 @@ fn ui_button(button: UiButton) -> impl Scene {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn ui_toggle_button<A: Scene, B: Scene>(
|
|
|
|
fn ui_toggle_button<A: Scene, B: Scene>((button_a, state_a): (UiButton, A), (button_b, state_b): (UiButton, B)) -> impl Scene {
|
|
|
|
(button_a, state_a): (UiButton, A),
|
|
|
|
|
|
|
|
(button_b, state_b): (UiButton, B),
|
|
|
|
|
|
|
|
) -> impl Scene {
|
|
|
|
|
|
|
|
bsn! {
|
|
|
|
bsn! {
|
|
|
|
@FeathersButton
|
|
|
|
@FeathersButton
|
|
|
|
Hovered
|
|
|
|
Hovered
|
|
|
|
@ -302,7 +239,6 @@ fn ui_toggle_button<A: Scene, B: Scene>(
|
|
|
|
fn the_camera() -> impl Scene {
|
|
|
|
fn the_camera() -> impl Scene {
|
|
|
|
bsn! {
|
|
|
|
bsn! {
|
|
|
|
Camera2d
|
|
|
|
Camera2d
|
|
|
|
Coordinates
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -352,33 +288,21 @@ fn the_ui() -> impl Scene {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn debug_ui() -> impl Scene {
|
|
|
|
fn debug_ui() -> impl Scene {
|
|
|
|
fn toggle_debug_ui(
|
|
|
|
|
|
|
|
_: On<Pointer<Click>>,
|
|
|
|
|
|
|
|
mut next: ResMut<NextState<DebugUi>>,
|
|
|
|
|
|
|
|
curr: Res<State<DebugUi>>,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
next.set(match curr.get() {
|
|
|
|
|
|
|
|
DebugUi::Hidden => DebugUi::Visible,
|
|
|
|
|
|
|
|
DebugUi::Visible => DebugUi::Hidden,
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
bsn! {
|
|
|
|
bsn! {
|
|
|
|
Node {
|
|
|
|
Node {
|
|
|
|
justify_self: JustifySelf::End,
|
|
|
|
justify_self: JustifySelf::End,
|
|
|
|
display: Display::Flex,
|
|
|
|
display: Display::Flex,
|
|
|
|
flex_direction: FlexDirection::Column,
|
|
|
|
flex_direction: FlexDirection::Column,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Toggle visibility when debugging disabled
|
|
|
|
pane()
|
|
|
|
pane()
|
|
|
|
Transform
|
|
|
|
Transform
|
|
|
|
Children [
|
|
|
|
Children [
|
|
|
|
pane_header()
|
|
|
|
pane_header()
|
|
|
|
on(toggle_debug_ui)
|
|
|
|
|
|
|
|
Children [
|
|
|
|
Children [
|
|
|
|
Text("Debug Info") ThemedText,
|
|
|
|
Text("Debug Info") ThemedText,
|
|
|
|
],
|
|
|
|
],
|
|
|
|
pane_body()
|
|
|
|
pane_body()
|
|
|
|
DebugUi::Visible
|
|
|
|
|
|
|
|
ToggleStateVisible
|
|
|
|
|
|
|
|
Children [
|
|
|
|
Children [
|
|
|
|
subpane()
|
|
|
|
subpane()
|
|
|
|
Node {
|
|
|
|
Node {
|
|
|
|
@ -462,182 +386,6 @@ fn primary_ui() -> impl Scene {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn toggle_main_ui(
|
|
|
|
|
|
|
|
_: On<Pointer<Click>>,
|
|
|
|
|
|
|
|
mut next: ResMut<NextState<MainUi>>,
|
|
|
|
|
|
|
|
curr: Res<State<MainUi>>,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
next.set(match curr.get() {
|
|
|
|
|
|
|
|
MainUi::Hidden => MainUi::Visible,
|
|
|
|
|
|
|
|
MainUi::Visible => MainUi::Hidden,
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn description_text() -> impl Scene {
|
|
|
|
|
|
|
|
bsn! {
|
|
|
|
|
|
|
|
subpane()
|
|
|
|
|
|
|
|
Node {
|
|
|
|
|
|
|
|
display: Display::Flex,
|
|
|
|
|
|
|
|
flex_direction: FlexDirection::Column,
|
|
|
|
|
|
|
|
align_items: AlignItems::Stretch,
|
|
|
|
|
|
|
|
justify_content: JustifyContent::Start,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
text_setup()
|
|
|
|
|
|
|
|
Children [
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
|
|
|
Text("Wipe the world state") ThemedText,
|
|
|
|
|
|
|
|
Text("Zoom in/out") ThemedText,
|
|
|
|
|
|
|
|
Text("Toggle sound on/off") ThemedText,
|
|
|
|
|
|
|
|
Text("---") ThemedText,
|
|
|
|
|
|
|
|
Text("Below that are the notes you can toggle on/off") ThemedText,
|
|
|
|
|
|
|
|
Text("And below that are the number of octaves for each note") ThemedText,
|
|
|
|
|
|
|
|
Text("Notes are color coded horizontally") ThemedText,
|
|
|
|
|
|
|
|
Text("Octaves are color coded vertically") ThemedText,
|
|
|
|
|
|
|
|
Text("---") ThemedText,
|
|
|
|
|
|
|
|
Text("The camera automatically follows the action") ThemedText,
|
|
|
|
|
|
|
|
Text("---") ThemedText,
|
|
|
|
|
|
|
|
Text("Hotkyes") ThemedText,
|
|
|
|
|
|
|
|
Text("* Space: Play/Pause Simulation") ThemedText,
|
|
|
|
|
|
|
|
Text("* m: Mute/Play Audio") ThemedText,
|
|
|
|
|
|
|
|
Text("* r: Reset Simulation") ThemedText,
|
|
|
|
|
|
|
|
Text("* n: Next Simulation Step") ThemedText,
|
|
|
|
|
|
|
|
Text("* -/=: Zoom In/Out") ThemedText,
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn controls() -> impl Scene {
|
|
|
|
|
|
|
|
bsn! {
|
|
|
|
|
|
|
|
subpane()
|
|
|
|
|
|
|
|
Node {
|
|
|
|
|
|
|
|
display: Display::Flex,
|
|
|
|
|
|
|
|
flex_direction: FlexDirection::Row,
|
|
|
|
|
|
|
|
align_items: AlignItems::Stretch,
|
|
|
|
|
|
|
|
justify_content: JustifyContent::Start,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
Children [
|
|
|
|
|
|
|
|
subpane_header() Children [
|
|
|
|
|
|
|
|
Text("Nav") ThemedText
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
subpane_body()
|
|
|
|
|
|
|
|
Node {
|
|
|
|
|
|
|
|
display: Display::Flex,
|
|
|
|
|
|
|
|
flex_direction: FlexDirection::Row,
|
|
|
|
|
|
|
|
align_items: AlignItems::Stretch,
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
ActionSource<SimAction>(SimAction::Step),
|
|
|
|
|
|
|
|
ui_button(UiButton::Reset)
|
|
|
|
|
|
|
|
ActionSource<SimAction>(SimAction::Reset),
|
|
|
|
|
|
|
|
ui_button(UiButton::Wipe)
|
|
|
|
|
|
|
|
ActionSource<SimAction>(SimAction::Wipe),
|
|
|
|
|
|
|
|
ui_button(UiButton::ZoomOut)
|
|
|
|
|
|
|
|
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),
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn notes() -> impl Scene {
|
|
|
|
|
|
|
|
bsn! {
|
|
|
|
|
|
|
|
subpane()
|
|
|
|
|
|
|
|
Children [
|
|
|
|
|
|
|
|
subpane_header() Children [
|
|
|
|
|
|
|
|
Text("Notes") ThemedText
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
subpane_body()
|
|
|
|
|
|
|
|
Node {
|
|
|
|
|
|
|
|
display: Display::Flex,
|
|
|
|
|
|
|
|
flex_direction: FlexDirection::Row,
|
|
|
|
|
|
|
|
align_items: AlignItems::Stretch,
|
|
|
|
|
|
|
|
justify_content: JustifyContent::Start,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
Children [
|
|
|
|
|
|
|
|
ui_checkbox("A")
|
|
|
|
|
|
|
|
template_value(Note::A)
|
|
|
|
|
|
|
|
on(toggle_note),
|
|
|
|
|
|
|
|
ui_checkbox("A#")
|
|
|
|
|
|
|
|
template_value(Note::ASharp)
|
|
|
|
|
|
|
|
on(toggle_note),
|
|
|
|
|
|
|
|
ui_checkbox("B")
|
|
|
|
|
|
|
|
template_value(Note::B)
|
|
|
|
|
|
|
|
on(toggle_note),
|
|
|
|
|
|
|
|
ui_checkbox("C")
|
|
|
|
|
|
|
|
template_value(Note::C)
|
|
|
|
|
|
|
|
on(toggle_note),
|
|
|
|
|
|
|
|
ui_checkbox("C#")
|
|
|
|
|
|
|
|
template_value(Note::CSharp)
|
|
|
|
|
|
|
|
on(toggle_note),
|
|
|
|
|
|
|
|
ui_checkbox("D")
|
|
|
|
|
|
|
|
template_value(Note::D)
|
|
|
|
|
|
|
|
on(toggle_note),
|
|
|
|
|
|
|
|
ui_checkbox("D#")
|
|
|
|
|
|
|
|
template_value(Note::DSharp)
|
|
|
|
|
|
|
|
on(toggle_note),
|
|
|
|
|
|
|
|
ui_checkbox("E")
|
|
|
|
|
|
|
|
template_value(Note::E)
|
|
|
|
|
|
|
|
on(toggle_note),
|
|
|
|
|
|
|
|
ui_checkbox("F")
|
|
|
|
|
|
|
|
template_value(Note::F)
|
|
|
|
|
|
|
|
on(toggle_note),
|
|
|
|
|
|
|
|
ui_checkbox("F#")
|
|
|
|
|
|
|
|
template_value(Note::FSharp)
|
|
|
|
|
|
|
|
on(toggle_note),
|
|
|
|
|
|
|
|
ui_checkbox("G")
|
|
|
|
|
|
|
|
template_value(Note::G)
|
|
|
|
|
|
|
|
on(toggle_note),
|
|
|
|
|
|
|
|
ui_checkbox("G#")
|
|
|
|
|
|
|
|
template_value(Note::GSharp)
|
|
|
|
|
|
|
|
on(toggle_note),
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn octaves() -> impl Scene {
|
|
|
|
|
|
|
|
bsn! {
|
|
|
|
|
|
|
|
subpane()
|
|
|
|
|
|
|
|
subpane_header() Children [
|
|
|
|
|
|
|
|
Text("Octaves") ThemedText
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
subpane_body()
|
|
|
|
|
|
|
|
Node {
|
|
|
|
|
|
|
|
display: Display::Flex,
|
|
|
|
|
|
|
|
flex_direction: FlexDirection::Row,
|
|
|
|
|
|
|
|
align_items: AlignItems::Stretch,
|
|
|
|
|
|
|
|
justify_content: JustifyContent::Start,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
Children [
|
|
|
|
|
|
|
|
@FeathersSlider { @max: 11., @value: 5., @min: 1. }
|
|
|
|
|
|
|
|
SliderStep(2.)
|
|
|
|
|
|
|
|
SliderPrecision(0)
|
|
|
|
|
|
|
|
on(|value_change: On<ValueChange<f32>>, mut pitch_map: ResMut<PitchMap>| {
|
|
|
|
|
|
|
|
if value_change.is_final {
|
|
|
|
|
|
|
|
pitch_map.octaves = value_change.value as usize;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
on(slider_self_update),
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bsn! {
|
|
|
|
bsn! {
|
|
|
|
Node {
|
|
|
|
Node {
|
|
|
|
display: Display::Flex,
|
|
|
|
display: Display::Flex,
|
|
|
|
@ -648,19 +396,133 @@ fn primary_ui() -> impl Scene {
|
|
|
|
pane()
|
|
|
|
pane()
|
|
|
|
Children [
|
|
|
|
Children [
|
|
|
|
pane_header()
|
|
|
|
pane_header()
|
|
|
|
on(toggle_main_ui)
|
|
|
|
|
|
|
|
// todo: toggle main ui
|
|
|
|
|
|
|
|
Children [
|
|
|
|
Children [
|
|
|
|
Text("Conways Game of Life With Sound (click here to open control panel)") ThemedText,
|
|
|
|
Text("Conways Game of Life With Sound") ThemedText,
|
|
|
|
],
|
|
|
|
],
|
|
|
|
pane_body()
|
|
|
|
pane_body()
|
|
|
|
MainUi::Visible
|
|
|
|
|
|
|
|
ToggleStateVisible
|
|
|
|
|
|
|
|
Children [
|
|
|
|
Children [
|
|
|
|
controls(),
|
|
|
|
subpane()
|
|
|
|
notes(),
|
|
|
|
Node {
|
|
|
|
octaves(),
|
|
|
|
display: Display::Flex,
|
|
|
|
description_text(),
|
|
|
|
flex_direction: FlexDirection::Row,
|
|
|
|
|
|
|
|
align_items: AlignItems::Stretch,
|
|
|
|
|
|
|
|
justify_content: JustifyContent::Start,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
Children [
|
|
|
|
|
|
|
|
subpane_header() Children [
|
|
|
|
|
|
|
|
Text("Nav") ThemedText
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
subpane_body()
|
|
|
|
|
|
|
|
Node {
|
|
|
|
|
|
|
|
display: Display::Flex,
|
|
|
|
|
|
|
|
flex_direction: FlexDirection::Row,
|
|
|
|
|
|
|
|
align_items: AlignItems::Stretch,
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
ActionSource<SimAction>(SimAction::Step),
|
|
|
|
|
|
|
|
ui_button(UiButton::Reset)
|
|
|
|
|
|
|
|
ActionSource<SimAction>(SimAction::Reset),
|
|
|
|
|
|
|
|
ui_button(UiButton::Wipe)
|
|
|
|
|
|
|
|
ActionSource<SimAction>(SimAction::Wipe),
|
|
|
|
|
|
|
|
ui_button(UiButton::ZoomOut)
|
|
|
|
|
|
|
|
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),
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
subpane()
|
|
|
|
|
|
|
|
Children [
|
|
|
|
|
|
|
|
subpane_header() Children [
|
|
|
|
|
|
|
|
Text("Notes") ThemedText
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
subpane_body()
|
|
|
|
|
|
|
|
Node {
|
|
|
|
|
|
|
|
display: Display::Flex,
|
|
|
|
|
|
|
|
flex_direction: FlexDirection::Row,
|
|
|
|
|
|
|
|
align_items: AlignItems::Stretch,
|
|
|
|
|
|
|
|
justify_content: JustifyContent::Start,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
Children [
|
|
|
|
|
|
|
|
ui_checkbox("A")
|
|
|
|
|
|
|
|
template_value(Note::A)
|
|
|
|
|
|
|
|
on(toggle_note),
|
|
|
|
|
|
|
|
ui_checkbox("A#")
|
|
|
|
|
|
|
|
template_value(Note::ASharp)
|
|
|
|
|
|
|
|
on(toggle_note),
|
|
|
|
|
|
|
|
ui_checkbox("B")
|
|
|
|
|
|
|
|
template_value(Note::B)
|
|
|
|
|
|
|
|
on(toggle_note),
|
|
|
|
|
|
|
|
ui_checkbox("C")
|
|
|
|
|
|
|
|
template_value(Note::C)
|
|
|
|
|
|
|
|
on(toggle_note),
|
|
|
|
|
|
|
|
ui_checkbox("C#")
|
|
|
|
|
|
|
|
template_value(Note::CSharp)
|
|
|
|
|
|
|
|
on(toggle_note),
|
|
|
|
|
|
|
|
ui_checkbox("D")
|
|
|
|
|
|
|
|
template_value(Note::D)
|
|
|
|
|
|
|
|
on(toggle_note),
|
|
|
|
|
|
|
|
ui_checkbox("D#")
|
|
|
|
|
|
|
|
template_value(Note::DSharp)
|
|
|
|
|
|
|
|
on(toggle_note),
|
|
|
|
|
|
|
|
ui_checkbox("E")
|
|
|
|
|
|
|
|
template_value(Note::E)
|
|
|
|
|
|
|
|
on(toggle_note),
|
|
|
|
|
|
|
|
ui_checkbox("F")
|
|
|
|
|
|
|
|
template_value(Note::F)
|
|
|
|
|
|
|
|
on(toggle_note),
|
|
|
|
|
|
|
|
ui_checkbox("F#")
|
|
|
|
|
|
|
|
template_value(Note::FSharp)
|
|
|
|
|
|
|
|
on(toggle_note),
|
|
|
|
|
|
|
|
ui_checkbox("G")
|
|
|
|
|
|
|
|
template_value(Note::G)
|
|
|
|
|
|
|
|
on(toggle_note),
|
|
|
|
|
|
|
|
ui_checkbox("G#")
|
|
|
|
|
|
|
|
template_value(Note::GSharp)
|
|
|
|
|
|
|
|
on(toggle_note),
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
subpane()
|
|
|
|
|
|
|
|
subpane_header() Children [
|
|
|
|
|
|
|
|
Text("Octaves") ThemedText
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
subpane_body()
|
|
|
|
|
|
|
|
Node {
|
|
|
|
|
|
|
|
display: Display::Flex,
|
|
|
|
|
|
|
|
flex_direction: FlexDirection::Row,
|
|
|
|
|
|
|
|
align_items: AlignItems::Stretch,
|
|
|
|
|
|
|
|
justify_content: JustifyContent::Start,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
Children [
|
|
|
|
|
|
|
|
@FeathersSlider { @max: 5., @value: 3., @min: 1. }
|
|
|
|
|
|
|
|
SliderStep(1.)
|
|
|
|
|
|
|
|
SliderPrecision(0)
|
|
|
|
|
|
|
|
on(|value_change: On<ValueChange<f32>>, mut pitch_map: ResMut<PitchMap>| {
|
|
|
|
|
|
|
|
if value_change.is_final {
|
|
|
|
|
|
|
|
pitch_map.octaves_up = value_change.value as usize;
|
|
|
|
|
|
|
|
debug!("(octave up) Updated pitch map: {:#?}", pitch_map);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
on(slider_self_update),
|
|
|
|
|
|
|
|
@FeathersSlider { @max: 5., @value: 3., @min: 1. }
|
|
|
|
|
|
|
|
SliderStep(1.)
|
|
|
|
|
|
|
|
SliderPrecision(0)
|
|
|
|
|
|
|
|
on(|value_change: On<ValueChange<f32>>, mut pitch_map: ResMut<PitchMap>| {
|
|
|
|
|
|
|
|
if value_change.is_final {
|
|
|
|
|
|
|
|
pitch_map.octaves_down = value_change.value as usize;
|
|
|
|
|
|
|
|
debug!("(octave down) Updated pitch map: {:#?}", pitch_map);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
on(slider_self_update),
|
|
|
|
|
|
|
|
],
|
|
|
|
]
|
|
|
|
]
|
|
|
|
]
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -725,19 +587,39 @@ impl Coordinates {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// For the purposes of creating assests, what does the given coordinate translate to CellPitch wise
|
|
|
|
/// For the purposes of creating assests, what does the given coordinate translate to CellPitch wise
|
|
|
|
fn to_cell_pitch(&self, pm: &PitchMap) -> CellPitch {
|
|
|
|
fn to_cell_pitch(&self) -> CellPitch {
|
|
|
|
let rows = pm.notes.len();
|
|
|
|
let Coordinates { x, y } = self.normalized();
|
|
|
|
let cols = pm.octaves;
|
|
|
|
let note = match x {
|
|
|
|
|
|
|
|
0 => Note::A,
|
|
|
|
let this_x = self.x.rem_euclid(rows as isize);
|
|
|
|
1 => Note::ASharp,
|
|
|
|
let this_y = self.y.rem_euclid(cols as isize) - (pm.octaves as isize / 2);
|
|
|
|
2 => Note::B,
|
|
|
|
|
|
|
|
3 => Note::C,
|
|
|
|
debug_assert!(this_x >= 0);
|
|
|
|
4 => Note::CSharp,
|
|
|
|
|
|
|
|
5 => Note::D,
|
|
|
|
|
|
|
|
6 => Note::DSharp,
|
|
|
|
|
|
|
|
7 => Note::E,
|
|
|
|
|
|
|
|
8 => Note::F,
|
|
|
|
|
|
|
|
9 => Note::FSharp,
|
|
|
|
|
|
|
|
10 => Note::G,
|
|
|
|
|
|
|
|
11 => Note::GSharp,
|
|
|
|
|
|
|
|
_ => panic!("This shouldn't happen!"),
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
let octave = y;
|
|
|
|
|
|
|
|
CellPitch { note, octave }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let note = pm.notes.get(this_x as usize).unwrap().clone();
|
|
|
|
// Turn an arbitrary coordinate to a "normalized" coordinate centered on 0,0
|
|
|
|
let octave = this_y;
|
|
|
|
fn normalized(&self) -> Self {
|
|
|
|
|
|
|
|
println!("normalizing {:?}", (self.x, self.y));
|
|
|
|
|
|
|
|
let x = self.x.rem_euclid(12);
|
|
|
|
|
|
|
|
let y = self.y.rem_euclid(11);
|
|
|
|
|
|
|
|
println!("normalized {:?}", (x,y));
|
|
|
|
|
|
|
|
debug_assert!(x >= 0);
|
|
|
|
|
|
|
|
debug_assert!(x <= 12);
|
|
|
|
|
|
|
|
debug_assert!(y >= 0);
|
|
|
|
|
|
|
|
debug_assert!(y <= 11);
|
|
|
|
|
|
|
|
|
|
|
|
CellPitch { note, octave }
|
|
|
|
Coordinates { x, y }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -916,21 +798,24 @@ fn new_cell(
|
|
|
|
|
|
|
|
|
|
|
|
let playback_settings = PlaybackSettings::ONCE.with_volume(volume);
|
|
|
|
let playback_settings = PlaybackSettings::ONCE.with_volume(volume);
|
|
|
|
|
|
|
|
|
|
|
|
let cell_pitch = c.to_cell_pitch(&(*pitch_map));
|
|
|
|
let cell_pitch = pitch_map.coordinates_cell_pitch(&c);
|
|
|
|
|
|
|
|
|
|
|
|
debug!("cell pitch: {:#?}", cell_pitch);
|
|
|
|
#[derive(Clone, Default, Component)]
|
|
|
|
debug!("materials: {:#?}", cell_assets.materials);
|
|
|
|
enum Foo {
|
|
|
|
|
|
|
|
#[default]
|
|
|
|
|
|
|
|
A,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bsn! {
|
|
|
|
bsn! {
|
|
|
|
Cell
|
|
|
|
Cell
|
|
|
|
Coordinates { x, y }
|
|
|
|
Coordinates { x, y }
|
|
|
|
template_value(cell_pitch.clone())
|
|
|
|
template_value(cell_pitch)
|
|
|
|
template_value(playback_settings)
|
|
|
|
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(c.as_translation() + Vec3::new(0.0, 0.0, 1.0)))
|
|
|
|
template_value(Mesh2dTemplate(cell_assets.mesh.clone().into()))
|
|
|
|
template_value(Mesh2dTemplate(cell_assets.mesh.clone().into()))
|
|
|
|
// AudioPlayer added by update_audio
|
|
|
|
// AudioPlayer added by update_audio
|
|
|
|
// MeshMaterial2d added by update_material
|
|
|
|
// MeshMaterial2d added by update_material
|
|
|
|
template_value(MeshMaterial2dTemplate(cell_assets.materials.get(&cell_pitch).unwrap().clone().into()))
|
|
|
|
template_value(MeshMaterial2dTemplate(cell_assets.materials.get(&pitch_map.coordinates_cell_pitch(&c)).unwrap().clone().into()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -1096,7 +981,7 @@ impl CellPitch {
|
|
|
|
]
|
|
|
|
]
|
|
|
|
.into_iter()
|
|
|
|
.into_iter()
|
|
|
|
.flat_map(|note| {
|
|
|
|
.flat_map(|note| {
|
|
|
|
(-5..=5).map(move |octave| CellPitch {
|
|
|
|
(0..11).map(move |octave| CellPitch {
|
|
|
|
note: note.clone(),
|
|
|
|
note: note.clone(),
|
|
|
|
octave,
|
|
|
|
octave,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
@ -1175,15 +1060,6 @@ fn load_meshes(mut cell_assets: ResMut<CellAssets>, mut meshes: ResMut<Assets<Me
|
|
|
|
cell_assets.mesh = meshes.add(Rectangle::new(SCALE, SCALE));
|
|
|
|
cell_assets.mesh = meshes.add(Rectangle::new(SCALE, SCALE));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn default_board(mut writer: MessageWriter<Lifecycle>) {
|
|
|
|
|
|
|
|
// https://playgameoflife.com/lexicon/R-pentomino
|
|
|
|
|
|
|
|
[(0, 0), (0, -1), (-1, 0), (0, 1), (1, 1)]
|
|
|
|
|
|
|
|
.into_iter()
|
|
|
|
|
|
|
|
.for_each(|(x, y)| {
|
|
|
|
|
|
|
|
writer.write(Lifecycle::Alive(Coordinates { x, y }));
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: Do not place piece if panning
|
|
|
|
// TODO: Do not place piece if panning
|
|
|
|
// TODO: Tie cursor to real world space, not pixels
|
|
|
|
// TODO: Tie cursor to real world space, not pixels
|
|
|
|
fn mouse_pan(
|
|
|
|
fn mouse_pan(
|
|
|
|
@ -1267,14 +1143,16 @@ fn manage_interactive<T: Component + States + PartialEq>(
|
|
|
|
#[derive(Resource, Debug)]
|
|
|
|
#[derive(Resource, Debug)]
|
|
|
|
struct PitchMap {
|
|
|
|
struct PitchMap {
|
|
|
|
notes: Vec<Note>,
|
|
|
|
notes: Vec<Note>,
|
|
|
|
octaves: usize,
|
|
|
|
octaves_up: usize,
|
|
|
|
|
|
|
|
octaves_down: usize,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl Default for PitchMap {
|
|
|
|
impl Default for PitchMap {
|
|
|
|
fn default() -> Self {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
Self {
|
|
|
|
notes: vec![Note::A, Note::B, Note::CSharp, Note::E, Note::FSharp],
|
|
|
|
notes: vec![Note::A, Note::B, Note::CSharp, Note::E, Note::FSharp],
|
|
|
|
octaves: 5,
|
|
|
|
octaves_up: 3,
|
|
|
|
|
|
|
|
octaves_down: 3,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -1289,33 +1167,72 @@ impl PitchMap {
|
|
|
|
// always ensure the notes list is sorted
|
|
|
|
// always ensure the notes list is sorted
|
|
|
|
self.notes.sort();
|
|
|
|
self.notes.sort();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Given the currently selected notes and octave range selected in the PitchMap convert the given coordinates to a CellPitch
|
|
|
|
|
|
|
|
fn coordinates_cell_pitch(&self, c: &Coordinates) -> CellPitch {
|
|
|
|
|
|
|
|
let Coordinates { x, y } = c.normalized();
|
|
|
|
|
|
|
|
let note: Note = {
|
|
|
|
|
|
|
|
let num_notes = self.notes.len();
|
|
|
|
|
|
|
|
self.notes.get((x % num_notes as isize) as usize).unwrap().clone()
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
let octave: isize = {
|
|
|
|
|
|
|
|
let num_octaves = self.octaves_up + self.octaves_down + 1;
|
|
|
|
|
|
|
|
println!("num_octaves: {:?}", num_octaves);
|
|
|
|
|
|
|
|
(y % num_octaves as isize) as isize
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
let cp = CellPitch { note, octave };
|
|
|
|
|
|
|
|
println!("Cell Pitch: {:?}", cp);
|
|
|
|
|
|
|
|
cp
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[test]
|
|
|
|
fn test_cell_pitch() {
|
|
|
|
fn test_cell_pitch() {
|
|
|
|
let pm = PitchMap {
|
|
|
|
let pm = PitchMap {
|
|
|
|
notes: vec![Note::A, Note::B, Note::C],
|
|
|
|
notes: vec![Note::A, Note::B, Note::C],
|
|
|
|
octaves: 3,
|
|
|
|
octaves_up: 1,
|
|
|
|
|
|
|
|
octaves_down: 1,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
let xs = -6..=5;
|
|
|
|
{
|
|
|
|
let ys = -6..=4;
|
|
|
|
let xs = [-6, -3, 0, 3, 6];
|
|
|
|
xs.enumerate().for_each(|(i, x)| {
|
|
|
|
let ys = [-5, -2, 0, 3, 6];
|
|
|
|
ys.clone().enumerate().for_each(|(j, y)| {
|
|
|
|
xs.iter().for_each(|x| {
|
|
|
|
let c = Coordinates { x, y };
|
|
|
|
ys.iter().for_each(|y| {
|
|
|
|
let n = pm.notes.get(i % 3).unwrap();
|
|
|
|
let CellPitch { note, octave } = pm.coordinates_cell_pitch(&Coordinates { x: *x, y: *y });
|
|
|
|
let o = (j as isize % 3) - 1;
|
|
|
|
debug_assert_eq!(note, Note::A);
|
|
|
|
let cp = c.to_cell_pitch(&pm);
|
|
|
|
debug_assert_eq!(octave, 0);
|
|
|
|
debug_assert_eq!(cp.note, *n);
|
|
|
|
});
|
|
|
|
debug_assert_eq!(cp.octave, o);
|
|
|
|
});
|
|
|
|
})
|
|
|
|
}
|
|
|
|
});
|
|
|
|
{
|
|
|
|
|
|
|
|
let xs = [-5, -2, 1, 4, 7];
|
|
|
|
|
|
|
|
let ys = [-4, -1, 1, 4, 7];
|
|
|
|
|
|
|
|
xs.iter().for_each(|x| {
|
|
|
|
|
|
|
|
ys.iter().for_each(|y| {
|
|
|
|
|
|
|
|
let CellPitch { note, octave } = pm.coordinates_cell_pitch(&Coordinates { x: *x, y: *y });
|
|
|
|
|
|
|
|
debug_assert_eq!(note, Note::B);
|
|
|
|
|
|
|
|
debug_assert_eq!(octave, 1);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
let xs = [-4, -1, 2, 5, 8];
|
|
|
|
|
|
|
|
let ys = [-3, 0, 2, 5, 8];
|
|
|
|
|
|
|
|
xs.iter().for_each(|x| {
|
|
|
|
|
|
|
|
ys.iter().for_each(|y| {
|
|
|
|
|
|
|
|
let CellPitch { note, octave } = pm.coordinates_cell_pitch(&Coordinates { x: *x, y: *y });
|
|
|
|
|
|
|
|
debug_assert_eq!(note, Note::C);
|
|
|
|
|
|
|
|
debug_assert_eq!(octave, 2);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// When the pitch map changes, update all cells to use the correct pitch (note + octave)
|
|
|
|
/// When the pitch map changes, update all cells to use the correct pitch (note + octave)
|
|
|
|
fn reassign_cell_pitch(mut query: Query<(&mut CellPitch, &Coordinates)>, pitch_map: Res<PitchMap>) {
|
|
|
|
fn reassign_cell_pitch(mut query: Query<(&mut CellPitch, &Coordinates)>, pitch_map: Res<PitchMap>) {
|
|
|
|
debug!("query size: {:?}", query.iter().len());
|
|
|
|
debug!("query size: {:?}", query.iter().len());
|
|
|
|
query.iter_mut().for_each(|(mut cell_pitch, c)| {
|
|
|
|
query.iter_mut().for_each(|(mut cell_pitch, c)| {
|
|
|
|
*cell_pitch = c.to_cell_pitch(&(*pitch_map));
|
|
|
|
*cell_pitch = pitch_map.coordinates_cell_pitch(c);
|
|
|
|
debug!("Updating cell pitch for {:?}", c);
|
|
|
|
debug!("Updating cell pitch for {:?}", c);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -1404,7 +1321,7 @@ struct ToggleStateVisible;
|
|
|
|
|
|
|
|
|
|
|
|
fn toggle_state_visible<S: States + Component>(
|
|
|
|
fn toggle_state_visible<S: States + Component>(
|
|
|
|
mut q: Query<(&S, &mut Node), With<ToggleStateVisible>>,
|
|
|
|
mut q: Query<(&S, &mut Node), With<ToggleStateVisible>>,
|
|
|
|
c: Res<State<S>>,
|
|
|
|
c: Res<State<S>>
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
q.iter_mut().for_each(|(s, mut node)| {
|
|
|
|
q.iter_mut().for_each(|(s, mut node)| {
|
|
|
|
node.display = if s == c.get() {
|
|
|
|
node.display = if s == c.get() {
|
|
|
|
@ -1414,41 +1331,3 @@ fn toggle_state_visible<S: States + Component>(
|
|
|
|
};
|
|
|
|
};
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn camera_follow(
|
|
|
|
|
|
|
|
mut camera: Query<&mut Coordinates, With<Camera>>,
|
|
|
|
|
|
|
|
cells: Query<&Coordinates, (With<Cell>, Without<Camera>)>,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
camera.iter_mut().for_each(|mut c| {
|
|
|
|
|
|
|
|
let left = cells
|
|
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
|
|
.map(|Coordinates { x, .. }| *x)
|
|
|
|
|
|
|
|
.min()
|
|
|
|
|
|
|
|
.unwrap_or(0);
|
|
|
|
|
|
|
|
let right = cells
|
|
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
|
|
.map(|Coordinates { x, .. }| *x)
|
|
|
|
|
|
|
|
.max()
|
|
|
|
|
|
|
|
.unwrap_or(0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let top = cells
|
|
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
|
|
.map(|Coordinates { y, .. }| *y)
|
|
|
|
|
|
|
|
.max()
|
|
|
|
|
|
|
|
.unwrap_or(0);
|
|
|
|
|
|
|
|
let bottom = cells
|
|
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
|
|
.map(|Coordinates { y, .. }| *y)
|
|
|
|
|
|
|
|
.min()
|
|
|
|
|
|
|
|
.unwrap_or(0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
c.x = (right + left) / 2;
|
|
|
|
|
|
|
|
c.y = (top + bottom) / 2;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn update_camera_position(mut query: Query<(&Coordinates, &mut Transform), With<Camera>>) {
|
|
|
|
|
|
|
|
query.iter_mut().for_each(|(c, mut t)| {
|
|
|
|
|
|
|
|
t.translation = c.as_translation();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|