Skip to content

Commit

Permalink
Add automatic metronome
Browse files Browse the repository at this point in the history
  • Loading branch information
treiher committed Sep 9, 2023
1 parent ab3e487 commit d101851
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Interval button for showing all values
- Display of interval bounds
- Beeps when timer expires
- Automatic metronome
- Notifications when going to next section of training session (not supported by Chrome Android)
- Compact overview of recorded training sets
- Adjustable beep volume
Expand Down
12 changes: 10 additions & 2 deletions frontend/src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ const STORAGE_KEY_ONGOING_TRAINING_SESSION: &str = "ongoing training session";

#[allow(clippy::needless_pass_by_value)]
pub fn init(url: Url, _orders: &mut impl Orders<Msg>) -> Model {
let settings = gloo_storage::LocalStorage::get(STORAGE_KEY_SETTINGS)
.unwrap_or(Settings { beep_volume: 80 });
let settings = gloo_storage::LocalStorage::get(STORAGE_KEY_SETTINGS).unwrap_or(Settings {
beep_volume: 80,
automatic_metronome: true,
});
let ongoing_training_session =
gloo_storage::LocalStorage::get(STORAGE_KEY_ONGOING_TRAINING_SESSION).unwrap_or(None);
Model {
Expand Down Expand Up @@ -259,6 +261,7 @@ pub enum TrainingSessionElement {
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Settings {
pub beep_volume: u8,
pub automatic_metronome: bool,
}

#[derive(serde::Serialize, serde::Deserialize, Clone)]
Expand Down Expand Up @@ -847,6 +850,7 @@ pub enum Msg {
TrainingSessionDeleted(Result<u32, String>),

SetBeepVolume(u8),
SetAutomaticMetronome(bool),

StartTrainingSession(u32),
UpdateTrainingSession(usize, TimerState),
Expand Down Expand Up @@ -1672,6 +1676,10 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
local_storage_set(STORAGE_KEY_SETTINGS, &model.settings, &mut model.errors);
orders.notify(Event::BeepVolumeChanged);
}
Msg::SetAutomaticMetronome(value) => {
model.settings.automatic_metronome = value;
local_storage_set(STORAGE_KEY_SETTINGS, &model.settings, &mut model.errors);
}

Msg::StartTrainingSession(training_session_id) => {
model.ongoing_training_session = Some(OngoingTrainingSession::new(training_session_id));
Expand Down
25 changes: 25 additions & 0 deletions frontend/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ enum Msg {
ShowSettingsDialog,
CloseSettingsDialog,
BeepVolumeChanged(String),
ToggleAutomaticMetronome,
EnableNotifications,
GoUp,
LogOut,
Expand Down Expand Up @@ -311,6 +312,12 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
orders.send_msg(Msg::Data(data::Msg::SetBeepVolume(value)));
}
}
Msg::ToggleAutomaticMetronome => {
orders.send_msg(Msg::Data(data::Msg::SetAutomaticMetronome(not(model
.data
.settings
.automatic_metronome))));
}
Msg::EnableNotifications => {
orders.skip().perform_cmd(async {
if let Ok(promise) = web_sys::Notification::request_permission() {
Expand Down Expand Up @@ -664,6 +671,24 @@ fn view_settings_dialog(data_model: &data::Model) -> Node<Msg> {
input_ev(Ev::Input, Msg::BeepVolumeChanged),
]
],
p![
C!["mb-5"],
h1![C!["subtitle"], "Metronome"],
button![
C!["button"],
if data_model.settings.automatic_metronome {
C!["is-primary"]
} else {
C!["is-danger"]
},
ev(Ev::Click, |_| Msg::ToggleAutomaticMetronome),
if data_model.settings.automatic_metronome {
"Automatic"
} else {
"Manual"
},
],
],
{
let permission = web_sys::Notification::permission();
p![
Expand Down
70 changes: 63 additions & 7 deletions frontend/src/page/training_session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,15 +457,23 @@ impl Metronome {
self.is_active
}

fn start(&mut self, audio_context: &Option<web_sys::AudioContext>) {
self.is_active = true;
if let Some(audio_context) = audio_context {
self.beat_number = 0;
self.next_beat_time = audio_context.current_time() + 0.5;
}
}

fn pause(&mut self) {
self.is_active = false;
}

fn start_pause(&mut self, audio_context: &Option<web_sys::AudioContext>) {
if self.is_active() {
self.is_active = false;
self.pause();
} else {
self.is_active = true;
if let Some(audio_context) = audio_context {
self.beat_number = 0;
self.next_beat_time = audio_context.current_time() + 0.5;
}
self.start(audio_context);
}
}

Expand Down Expand Up @@ -669,6 +677,8 @@ pub enum Msg {
ResetStopwatch,
ToggleStopwatch,

StartMetronome(u32),
PauseMetronome,
StartPauseMetronome,
MetronomeIntervalChanged(String),
MetronomeStressChanged(String),
Expand Down Expand Up @@ -875,6 +885,9 @@ pub fn update(
update_streams(model, orders);
orders.notify(data::Msg::StartTrainingSession(model.training_session_id));
if let Some(guide) = &model.guide {
if data_model.settings.automatic_metronome {
update_metronome(&model.form.sections[guide.section_idx], orders);
}
orders.notify(data::Msg::UpdateTrainingSession(
guide.section_idx,
guide.timer.to_timer_state(),
Expand All @@ -899,6 +912,13 @@ pub fn update(
.unwrap()
.timer
.restore(ongoing_training_session.timer_state);
if let Some(guide) = &model.guide {
if data_model.settings.automatic_metronome
&& guide.section_idx < model.form.sections.len()
{
update_metronome(&model.form.sections[guide.section_idx], orders);
}
}
update_streams(model, orders);
orders.force_render_now().send_msg(Msg::ScrollToSection);
Url::go_and_push(
Expand Down Expand Up @@ -959,6 +979,9 @@ pub fn update(
update_guide_timer(model);
update_streams(model, orders);
if let Some(guide) = &mut model.guide {
if data_model.settings.automatic_metronome {
update_metronome(&model.form.sections[guide.section_idx], orders);
}
if let Some(ongoing_training_session) = &data_model.ongoing_training_session {
orders.notify(data::Msg::UpdateTrainingSession(
ongoing_training_session.section_idx - 1,
Expand All @@ -973,10 +996,16 @@ pub fn update(
guide.section_idx += 1;
if guide.section_idx == model.form.sections.len() {
model.guide = None;
orders.notify(data::Msg::EndTrainingSession);
orders
.send_msg(Msg::PauseMetronome)
.notify(data::Msg::EndTrainingSession);
} else {
guide.section_start_time = Utc::now();

if data_model.settings.automatic_metronome {
update_metronome(&model.form.sections[guide.section_idx], orders);
}

let title;
let body;
match &model.form.sections[guide.section_idx] {
Expand Down Expand Up @@ -1012,6 +1041,7 @@ pub fn update(
};
}
}

model.show_notification(&title, body);
}
}
Expand Down Expand Up @@ -1146,6 +1176,16 @@ pub fn update(
update_streams(model, orders);
}

Msg::StartMetronome(interval) => {
model.timer_dialog.metronome.interval = interval;
model.timer_dialog.metronome.stressed_beat = 1;
model.timer_dialog.metronome.start(&model.audio_context);
update_streams(model, orders);
}
Msg::PauseMetronome => {
model.timer_dialog.metronome.pause();
update_streams(model, orders);
}
Msg::StartPauseMetronome => {
model
.timer_dialog
Expand Down Expand Up @@ -1229,6 +1269,22 @@ fn update_guide_timer(model: &mut Model) {
}
}

fn update_metronome(form_section: &FormSection, orders: &mut impl Orders<Msg>) {
match form_section {
FormSection::Set { exercises } => {
let exercise = &exercises[0];
if exercise.target_reps.is_some() {
if let Some(target_time) = exercise.target_time {
orders.send_msg(Msg::StartMetronome(target_time));
}
}
}
FormSection::Rest { .. } => {
orders.send_msg(Msg::PauseMetronome);
}
}
}

// ------ ------
// View
// ------ ------
Expand Down

0 comments on commit d101851

Please sign in to comment.