Skip to content

Commit

Permalink
chore: move away from hard-coded BEATS_PER_LOOP (cont4)
Browse files Browse the repository at this point in the history
  • Loading branch information
nathanleiby committed Nov 19, 2024
1 parent f00603c commit 04c999e
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 95 deletions.
8 changes: 2 additions & 6 deletions src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use crate::voices::Instrument;
pub const WINDOW_WIDTH: i32 = 1280;
pub const WINDOW_HEIGHT: i32 = 720;

pub const BEATS_PER_LOOP: f64 = 16.;
pub const DEFAULT_BEATS_PER_LOOP: usize = 16;

//
Expand All @@ -29,9 +28,6 @@ pub const ALL_INSTRUMENTS: [Instrument; 10] = [
Instrument::PedalHiHat,
];

pub const GRID_ROWS: usize = ALL_INSTRUMENTS.len();
pub const GRID_COLS: usize = DEFAULT_BEATS_PER_LOOP;

// Message passing (TODO: move to events?)

#[derive(Debug)]
Expand All @@ -55,7 +51,7 @@ impl UserHit {
}
}

pub fn beat(&self, beats_per_loop: f64) -> f64 {
self.clock_tick % beats_per_loop
pub fn beat(&self, beats_per_loop: usize) -> f64 {
self.clock_tick % (beats_per_loop as f64)
}
}
89 changes: 27 additions & 62 deletions src/egui_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use log::info;
use macroquad::color::{DARKBLUE, GREEN, LIGHTGRAY, ORANGE, PURPLE, RED};

use crate::{
consts::{UserHit, ALL_INSTRUMENTS, GRID_COLS, GRID_ROWS},
consts::{UserHit, ALL_INSTRUMENTS},
events::Events,
score::{
compute_accuracy_of_single_hit, compute_last_loop_summary,
Expand All @@ -21,8 +21,6 @@ use crate::{
voices::{Instrument, Voices},
};

pub type EnabledBeats = [[bool; GRID_COLS]; GRID_ROWS];

// This resource holds information about the game:
pub struct UIState {
selector_vec: Vec<String>,
Expand All @@ -41,8 +39,6 @@ pub struct UIState {
current_beat: f32,
beats_per_loop: usize,

enabled_beats: EnabledBeats,

latency_offset_s: f32,

user_hits: Vec<UserHit>,
Expand Down Expand Up @@ -86,8 +82,6 @@ impl Default for UIState {

latency_offset_s: 0.,

enabled_beats: [[false; GRID_COLS]; GRID_ROWS],

user_hits: vec![],
desired_hits: Voices::new(),

Expand Down Expand Up @@ -129,10 +123,6 @@ impl UIState {
self.current_loop = val;
}

pub fn set_enabled_beats(&mut self, voices: &Voices, beats_per_loop: usize) {
self.enabled_beats = voices.to_enabled_beats(beats_per_loop);
}

pub fn set_bpm(&mut self, bpm: f32) {
self.bpm = bpm;
}
Expand Down Expand Up @@ -454,31 +444,6 @@ fn draw_central_panel(ctx: &egui::Context, ui_state: &UIState, events: &mut Vec<
const VIRTUAL_WIDTH: f32 = 800.;
const VIRTUAL_HEIGHT: f32 = 1000.;

fn is_beat_enabled(
visible_row: usize,
col: usize,
enabled_beats: EnabledBeats,
visible_instruments: &[&Instrument],
) -> bool {
// determine instrument
let res = visible_instruments
.iter()
.enumerate()
.find(|x| x.0 == visible_row);
let ins = match res {
Some(x) => *x.1,
None => panic!("invalid instrument idx"),
};

// see if it's enabled
let res2 = ALL_INSTRUMENTS.iter().enumerate().find(|x| x.1 == ins);
let row = match res2 {
Some(x) => x.0,
None => panic!("invalid instrument idx"),
};

enabled_beats[row][col]
}
fn draw_beat_grid(ui_state: &UIState, ui: &mut egui::Ui, events: &mut Vec<Events>) {
let (response, painter) = ui.allocate_painter(
egui::Vec2::new(ui.available_width(), ui.available_height()),
Expand All @@ -504,15 +469,15 @@ fn draw_beat_grid(ui_state: &UIState, ui: &mut egui::Ui, events: &mut Vec<Events
ALL_INSTRUMENTS
.iter()
.enumerate()
.filter(|(idx, _)| ui_state.enabled_beats[*idx].iter().any(|b| *b))
.filter(|(_, ins)| !ui_state.desired_hits.get_instrument_beats(ins).is_empty())
.map(|(_, ins)| ins)
.collect()
} else {
ALL_INSTRUMENTS.iter().filter(|_| true).collect()
};

let visible_rows = visible_instruments.len();
let visible_cols = GRID_COLS; // future, this depends on length of loop
let visible_cols = ui_state.beats_per_loop;

let width_scale: f32 = VIRTUAL_WIDTH / visible_cols as f32;
let height_scale: f32 = VIRTUAL_HEIGHT / visible_rows as f32;
Expand Down Expand Up @@ -580,7 +545,6 @@ fn draw_beat_grid(ui_state: &UIState, ui: &mut egui::Ui, events: &mut Vec<Events
continue;
}

// draw a horizontal line through the middle
let base_pos = pos2(0., (visible_row as f32) * height_scale);
let start_pt = to_screen.transform_pos(base_pos);
let end_pt =
Expand All @@ -606,6 +570,7 @@ fn draw_beat_grid(ui_state: &UIState, ui: &mut egui::Ui, events: &mut Vec<Events
shapes.push(shape);
}

// Draw horizontal lines
for visible_row in 0..visible_rows {
// draw a horizontal line through the middle
let base_pos = pos2(0., (visible_row as f32 + 0.5) * height_scale);
Expand All @@ -616,29 +581,25 @@ fn draw_beat_grid(ui_state: &UIState, ui: &mut egui::Ui, events: &mut Vec<Events
egui::Stroke::new(1., Color32::DARK_GRAY),
);
shapes.push(shape);
}

// TODO: Instead of looking at each potential beat, draw the beats where it says they exist!
for col in 0..visible_cols {
let t_rect = rect_for_col_row(col, visible_row, to_screen, width_scale, height_scale);

// if this beat is enabled (row is instrument, col is beat)..
if is_beat_enabled(
visible_row,
col,
ui_state.enabled_beats,
&visible_instruments,
) {
// TODO: These are being drawn over by `draw_note_successes`. how to handle?
let shape =
egui::Shape::rect_filled(t_rect, egui::Rounding::default(), beat_fill_color);
shapes.push(shape)
}
for (visible_row, ins) in visible_instruments.iter().enumerate().take(visible_rows) {
let desired_hits = ui_state.desired_hits.get_instrument_beats(ins);
for d in desired_hits {
let t_rect = rect_for_col_row(*d, visible_row, to_screen, width_scale, height_scale);
let shape =
egui::Shape::rect_filled(t_rect, egui::Rounding::default(), beat_fill_color);
shapes.push(shape)
}
}

// Draw Note Successes
let loop_last_completed_beat = ui_state.current_beat - MISS_MARGIN as f32;
let current_loop_hits = get_hits_from_nth_loop(&ui_state.user_hits, ui_state.current_loop);
let current_loop_hits = get_hits_from_nth_loop(
&ui_state.user_hits,
ui_state.current_loop,
ui_state.beats_per_loop,
);
draw_note_successes(
&current_loop_hits,
&ui_state.desired_hits,
Expand Down Expand Up @@ -686,14 +647,14 @@ fn draw_beat_grid(ui_state: &UIState, ui: &mut egui::Ui, events: &mut Vec<Events
Instrument::Tom3 => "Tom3 (Low)",
Instrument::PedalHiHat => "Pedal Hi-hat",
};
let t_rect = rect_for_col_row(0, row, to_screen, width_scale, height_scale);
let t_rect = rect_for_col_row(0., row, to_screen, width_scale, height_scale);
let label = egui::Label::new(name);
ui.put(t_rect, label);
}
}

fn rect_for_col_row(
col: usize,
col: f64,
row: usize,
to_screen: RectTransform,
width_scale: f32,
Expand Down Expand Up @@ -739,7 +700,11 @@ fn draw_user_hits(
visible_instruments: &[&Instrument],
) {
for (instrument_idx, instrument) in visible_instruments.iter().enumerate() {
let user_notes = get_user_hit_timings_by_instrument(&ui_state.user_hits, **instrument);
let user_notes = get_user_hit_timings_by_instrument(
&ui_state.user_hits,
**instrument,
ui_state.beats_per_loop,
);
let desired_notes = ui_state.desired_hits.get_instrument_beats(instrument);
for note in user_notes.iter() {
draw_user_hit(
Expand Down Expand Up @@ -820,7 +785,7 @@ fn draw_note_successes(
beats_per_loop: usize,
) {
for (instrument_idx, instrument) in visible_instruments.iter().enumerate() {
let actual = get_user_hit_timings_by_instrument(user_hits, **instrument);
let actual = get_user_hit_timings_by_instrument(user_hits, **instrument, beats_per_loop);
// add audio_latency to each note
let actual_w_latency = actual
.iter()
Expand Down Expand Up @@ -857,8 +822,7 @@ fn note_success_shape(
width_scale: f32,
height_scale: f32,
) -> Shape {
let col = beats_offset as usize; // TODO: truncate, for now
let rect = rect_for_col_row(col, row, to_screen, width_scale, height_scale);
let rect = rect_for_col_row(beats_offset, row, to_screen, width_scale, height_scale);

let bar_color = match acc {
Accuracy::Early => ORANGE,
Expand Down Expand Up @@ -887,6 +851,7 @@ fn gold_mode(ui: &mut egui::Ui, ui_state: &UIState) {
let nth_loop_hits = get_hits_from_nth_loop(
&ui_state.user_hits,
(ui_state.current_loop as i32 - i) as usize, // TODO: check for overflow
ui_state.beats_per_loop,
);
let summary_data = compute_last_loop_summary(
&nth_loop_hits,
Expand Down
8 changes: 5 additions & 3 deletions src/game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ pub fn compute_ui_state(gs: &GameState, audio: &Audio, midi_device_name: &str) -
ui_state.set_selected_idx(gs.selected_loop_idx);
ui_state.set_current_beat(audio.current_beat());
ui_state.set_current_loop(audio.current_loop() as usize);
ui_state.set_enabled_beats(&gs.voices, gs.beats_per_loop);
ui_state.set_is_playing(!audio.is_paused());
ui_state.set_bpm(audio.get_bpm() as f32);
ui_state.set_audio_latency_s(audio.get_configured_audio_latency_seconds() as f32);
Expand Down Expand Up @@ -148,8 +147,11 @@ pub fn process_system_events(
match msg {
TxMsg::AudioNew => (),
TxMsg::StartingLoop(loop_num) => {
let last_loop_hits =
get_hits_from_nth_loop(&audio.user_hits, (audio.current_loop() - 1) as usize);
let last_loop_hits = get_hits_from_nth_loop(
&audio.user_hits,
(audio.current_loop() - 1) as usize,
beats_per_loop,
);
let summary_data =
compute_last_loop_summary(&last_loop_hits, voices, beats_per_loop);
info!("last loop summary = {:?}", summary_data);
Expand Down
17 changes: 12 additions & 5 deletions src/score.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::{collections::HashMap, vec};

use crate::{
consts::UserHit,
consts::{ALL_INSTRUMENTS, BEATS_PER_LOOP},
consts::ALL_INSTRUMENTS,
voices::{Instrument, Voices},
};

Expand Down Expand Up @@ -176,11 +176,12 @@ impl LastLoopSummary {
pub fn get_user_hit_timings_by_instrument(
user_hits: &[UserHit],
instrument: Instrument,
beats_per_loop: usize,
) -> Vec<f64> {
user_hits
.iter()
.filter(|hit| hit.instrument == instrument)
.map(|hit| hit.beat(BEATS_PER_LOOP))
.map(|hit| hit.beat(beats_per_loop))
.collect::<Vec<f64>>()
}

Expand Down Expand Up @@ -231,7 +232,8 @@ pub fn compute_last_loop_summary(

for instrument in ALL_INSTRUMENTS.iter() {
// get accuracy of hihat
let user_timings = get_user_hit_timings_by_instrument(user_hits, *instrument);
let user_timings =
get_user_hit_timings_by_instrument(user_hits, *instrument, beats_per_loop);
let desired_timings = desired_hits.get_instrument_beats(instrument);

let accuracies = compute_loop_performance_for_voice(
Expand All @@ -247,12 +249,17 @@ pub fn compute_last_loop_summary(
out
}

pub fn get_hits_from_nth_loop(user_hits: &[UserHit], desired_loop_idx: usize) -> Vec<UserHit> {
pub fn get_hits_from_nth_loop(
user_hits: &[UserHit],
desired_loop_idx: usize,
beats_per_loop: usize,
) -> Vec<UserHit> {
let last_loop_hits: Vec<UserHit> = user_hits
.iter()
.filter(|hit| {
// include hits from just before start of loop (back to 0 - MISS), since those could be early or on-time hits
let loop_num_for_hit = ((hit.clock_tick + MISS_MARGIN) / BEATS_PER_LOOP) as usize;
let loop_num_for_hit =
((hit.clock_tick + MISS_MARGIN) / beats_per_loop as f64) as usize;
loop_num_for_hit == desired_loop_idx
})
.cloned()
Expand Down
20 changes: 1 addition & 19 deletions src/voices.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::error::Error;

use serde::{Deserialize, Serialize};

use crate::consts::{ALL_INSTRUMENTS, GRID_COLS, GRID_ROWS};
use crate::consts::ALL_INSTRUMENTS;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Instrument {
Expand Down Expand Up @@ -147,24 +147,6 @@ impl Voices {
// Instrument::Metronome => "assets/sounds/click.wav",
}
}

// TODO: how to handle a dynamic number of cols.. vec?
// TODO: Probably better to represent this without arrays
pub fn to_enabled_beats(&self, beats_per_loop: usize) -> [[bool; GRID_COLS]; GRID_ROWS] {
let mut out = [[false; GRID_COLS]; GRID_ROWS];

for (instrument_idx, instrument) in ALL_INSTRUMENTS.iter().enumerate() {
for beat in self.get_instrument_beats(instrument) {
for val in 0..beats_per_loop {
if *beat == (val as f64) {
out[instrument_idx][val] = true;
}
}
}
}

out
}
}

/// Loop is the full information required to play a loop. It can be read/written to a file.
Expand Down

0 comments on commit 04c999e

Please sign in to comment.