diff --git a/src/audio/midi_player.rs b/src/audio/midi_player.rs index 49fee20..60e3718 100644 --- a/src/audio/midi_player.rs +++ b/src/audio/midi_player.rs @@ -32,6 +32,7 @@ impl AudioPlayer { pub fn new( song: Rc, song_tempo: i32, + tempo_percentage: usize, sound_font_file: Option, beat_sender: Arc>, ) -> Self { @@ -39,7 +40,11 @@ impl AudioPlayer { let solo_track_id = None; // player params - let player_params = Arc::new(Mutex::new(MidiPlayerParams::new(song_tempo, solo_track_id))); + let player_params = Arc::new(Mutex::new(MidiPlayerParams::new( + song_tempo, + tempo_percentage, + solo_track_id, + ))); // midi sequencer initialization let builder = MidiBuilder::new(); @@ -102,6 +107,11 @@ impl AudioPlayer { } } + pub fn set_tempo_percentage(&mut self, new_tempo_percentage: usize) { + let mut params_guard = self.player_params.lock().unwrap(); + params_guard.set_tempo_percentage(new_tempo_percentage) + } + pub fn stop(&mut self) { // Pause stream if let Some(stream) = &self.stream { @@ -233,7 +243,7 @@ fn new_output_stream( move |output: &mut [f32], _: &cpal::OutputCallbackInfo| { let mut player_params_guard = player_params.lock().unwrap(); let mut sequencer_guard = sequencer.lock().unwrap(); - sequencer_guard.advance(player_params_guard.tempo()); + sequencer_guard.advance(player_params_guard.adjusted_tempo()); let mut synthesizer_guard = synthesizer.lock().unwrap(); // process midi events for current tick if let Some(events) = sequencer_guard.get_next_events() { diff --git a/src/audio/midi_player_params.rs b/src/audio/midi_player_params.rs index b55dfea..56d4970 100644 --- a/src/audio/midi_player_params.rs +++ b/src/audio/midi_player_params.rs @@ -1,13 +1,15 @@ /// Hold values changed during playback of a MIDI events. pub struct MidiPlayerParams { tempo: i32, + tempo_percentage: usize, solo_track_id: Option, } impl MidiPlayerParams { - pub fn new(tempo: i32, solo_track_id: Option) -> Self { + pub fn new(tempo: i32, tempo_percentage: usize, solo_track_id: Option) -> Self { Self { tempo, + tempo_percentage, solo_track_id, } } @@ -20,11 +22,15 @@ impl MidiPlayerParams { self.solo_track_id = solo_track_id; } - pub fn tempo(&self) -> i32 { - self.tempo + pub fn adjusted_tempo(&self) -> i32 { + (self.tempo as f32 * self.tempo_percentage as f32 / 100.0) as i32 } pub fn set_tempo(&mut self, tempo: i32) { self.tempo = tempo; } + + pub fn set_tempo_percentage(&mut self, tempo_percentage: usize) { + self.tempo_percentage = tempo_percentage; + } } diff --git a/src/ui/application.rs b/src/ui/application.rs index 0eb8cab..bc236d4 100644 --- a/src/ui/application.rs +++ b/src/ui/application.rs @@ -28,6 +28,7 @@ pub struct RuxApplication { all_tracks: Vec, // all possible tracks tablature: Option, // loaded tablature tablature_id: container::Id, // tablature container id + tempo_selection: TempoSelection, // tempo percentage for playback audio_player: Option, // audio player tab_file_is_loading: bool, // file loading flag in progress sound_font_file: Option, // sound font file @@ -54,6 +55,43 @@ impl SongDisplayInfo { } } +#[derive(Debug, Clone, PartialEq)] +pub struct TempoSelection { + percentage: usize, +} + +impl Default for TempoSelection { + fn default() -> Self { + TempoSelection::new(100) + } +} + +impl TempoSelection { + fn new(percentage: usize) -> Self { + Self { percentage } + } + + fn values() -> Vec { + vec![ + TempoSelection::new(25), + TempoSelection::new(50), + TempoSelection::new(60), + TempoSelection::new(70), + TempoSelection::new(80), + TempoSelection::new(90), + TempoSelection::new(100), + TempoSelection::new(150), + TempoSelection::new(200), + ] + } +} + +impl Display for TempoSelection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}%", self.percentage) + } +} + #[derive(Debug, Default, Clone, PartialEq)] pub struct TrackSelection { index: usize, @@ -77,13 +115,14 @@ pub enum Message { OpenFile, // open file dialog FileOpened(Result<(Vec, String), PickerError>), // file content & file name TrackSelected(TrackSelection), // track selection - FocusMeasure(usize), // used when clicking on measure in tablature - FocusTick(usize), // focus on a specific tick in the tablature - PlayPause, // toggle play/pause - StopPlayer, // stop playback - ToggleSolo, // toggle solo mode - WindowResized, // window resized - TablatureResized(Size), // tablature resized + FocusMeasure(usize), // used when clicking on measure in tablature + FocusTick(usize), // focus on a specific tick in the tablature + PlayPause, // toggle play/pause + StopPlayer, // stop playback + ToggleSolo, // toggle solo mode + WindowResized, // window resized + TablatureResized(Size), // tablature resized + TempoSelected(TempoSelection), // tempo selection } impl RuxApplication { @@ -95,6 +134,7 @@ impl RuxApplication { all_tracks: vec![], tablature: None, tablature_id: container::Id::new("tablature-outer-container"), + tempo_selection: TempoSelection::default(), audio_player: None, tab_file_is_loading: false, sound_font_file, @@ -186,6 +226,7 @@ impl RuxApplication { let audio_player = AudioPlayer::new( song_rc.clone(), song_rc.tempo.value, + self.tempo_selection.percentage, self.sound_font_file.clone(), self.beat_sender.clone(), ); @@ -270,6 +311,13 @@ impl RuxApplication { } Task::none() } + Message::TempoSelected(tempos_selection) => { + if let Some(audio_player) = &mut self.audio_player { + audio_player.set_tempo_percentage(tempos_selection.percentage) + } + self.tempo_selection = tempos_selection; + Task::none() + } } } @@ -298,6 +346,15 @@ impl RuxApplication { let track_control = if self.all_tracks.is_empty() { row![horizontal_space()] } else { + let tempo_label = text("Tempo").size(14); + let tempo_percentage = pick_list( + TempoSelection::values(), + Some(&self.tempo_selection), + Message::TempoSelected, + ) + .text_size(14) + .padding([5, 10]); + let solo_mode = action_toggle( solo_icon(), "Solo", @@ -315,7 +372,7 @@ impl RuxApplication { .text_size(14) .padding([5, 10]); - row![solo_mode, track_pick_list,] + row![tempo_label, tempo_percentage, solo_mode, track_pick_list,] .spacing(10) .align_y(Alignment::Center) };