Skip to content

Commit 1a1a2fd

Browse files
committed
v0.1
1 parent 5e06a4c commit 1a1a2fd

2 files changed

Lines changed: 79 additions & 58 deletions

File tree

src/audio.rs

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
use rodio::{Sink, Source};
1+
use rodio::Source;
22
use std::f32::consts::PI;
3-
use std::sync::{Arc, Mutex};
43
use std::time::Duration;
54

65
pub struct AudioManager {
7-
pub sink: Option<Arc<Mutex<Sink>>>,
6+
// No need to store sink anymore since we create fresh ones for each playback
87
}
98

109
impl AudioManager {
@@ -18,31 +17,71 @@ impl AudioManager {
1817
self.play_audio(&tones);
1918
}
2019

21-
pub fn play_break_complete_sound(&self) {
22-
let tones = [
20+
21+
pub fn play_break_complete_music(&self) {
22+
// Play notification + longer melody as one continuous sequence
23+
let complete_sequence = [
24+
// Initial notification tones
2325
(220.0, Duration::from_millis(150)),
2426
(440.0, Duration::from_millis(150)),
2527
(880.0, Duration::from_millis(150)),
2628
(1760.0, Duration::from_millis(300)),
29+
(0.0, Duration::from_millis(300)), // Pause between notification and melody
30+
31+
// Phrase 1 - Gentle wake-up call
32+
(523.25, Duration::from_millis(300)), // C5
33+
(587.33, Duration::from_millis(300)), // D5
34+
(659.25, Duration::from_millis(300)), // E5
35+
(698.46, Duration::from_millis(400)), // F5
36+
(0.0, Duration::from_millis(100)), // Rest
37+
38+
// Phrase 2 - Building energy
39+
(783.99, Duration::from_millis(300)), // G5
40+
(880.00, Duration::from_millis(300)), // A5
41+
(987.77, Duration::from_millis(300)), // B5
42+
(1046.50, Duration::from_millis(500)), // C6
43+
(0.0, Duration::from_millis(200)), // Rest
44+
45+
// Phrase 3 - Descending comfort
46+
(1046.50, Duration::from_millis(250)), // C6
47+
(987.77, Duration::from_millis(250)), // B5
48+
(880.00, Duration::from_millis(250)), // A5
49+
(783.99, Duration::from_millis(250)), // G5
50+
(698.46, Duration::from_millis(300)), // F5
51+
(659.25, Duration::from_millis(400)), // E5
52+
(0.0, Duration::from_millis(150)), // Rest
53+
54+
// Phrase 4 - Motivational ending
55+
(523.25, Duration::from_millis(200)), // C5
56+
(659.25, Duration::from_millis(200)), // E5
57+
(783.99, Duration::from_millis(200)), // G5
58+
(1046.50, Duration::from_millis(300)), // C6
59+
(1174.66, Duration::from_millis(200)), // D6
60+
(1318.51, Duration::from_millis(600)), // E6 - Final note
2761
];
28-
self.play_audio(&tones);
62+
self.play_audio(&complete_sequence);
2963
}
3064

3165
fn play_audio(&self, tones: &[(f32, Duration)]) {
32-
if let Some(ref sink) = self.sink {
33-
let sink = sink.lock().unwrap();
34-
let sample_rate = 44100;
35-
36-
for (freq, dur) in tones {
37-
if *freq == 0.0 {
38-
let silence = rodio::source::Zero::<f32>::new(1, sample_rate)
39-
.take_duration(*dur)
40-
.buffered();
41-
sink.append(silence);
42-
} else {
43-
let source = SquareWaveWithDecay::new(*freq, *dur, sample_rate);
44-
sink.append(source);
66+
// Create a new stream and sink for each audio playback
67+
if let Ok((_stream, stream_handle)) = rodio::OutputStream::try_default() {
68+
if let Ok(sink) = rodio::Sink::try_new(&stream_handle) {
69+
let sample_rate = 44100;
70+
71+
for (freq, dur) in tones {
72+
if *freq == 0.0 {
73+
let silence = rodio::source::Zero::<f32>::new(1, sample_rate)
74+
.take_duration(*dur)
75+
.buffered();
76+
sink.append(silence);
77+
} else {
78+
let source = SquareWaveWithDecay::new(*freq, *dur, sample_rate);
79+
sink.append(source);
80+
}
4581
}
82+
83+
// Wait for the audio to finish playing
84+
sink.sleep_until_end();
4685
}
4786
}
4887
}

src/main.rs

Lines changed: 21 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use std::{
22
io,
3-
sync::{Arc, Mutex},
43
time::{Duration, Instant},
54
};
65

@@ -17,7 +16,6 @@ use ratatui::{
1716
text::{Line, Span},
1817
widgets::{Block, Borders, Gauge, Paragraph},
1918
};
20-
use rodio::{OutputStream, Sink};
2119

2220
mod ascii_digits;
2321
mod audio;
@@ -66,22 +64,6 @@ struct PomodoroTimer {
6664

6765
impl PomodoroTimer {
6866
fn new() -> Result<Self, Box<dyn std::error::Error>> {
69-
// Initialize audio system for notifications
70-
let (_stream_handle, sink) = match OutputStream::try_default() {
71-
Ok((stream, stream_handle)) => match Sink::try_new(&stream_handle) {
72-
Ok(sink) => (Some(Box::new(stream) as Box<dyn std::any::Any>), Some(Arc::new(Mutex::new(sink)))),
73-
Err(e) => {
74-
eprintln!("Warning: Could not create audio sink: {e}");
75-
eprintln!("The application will continue but notification sounds may not work.");
76-
(None, None)
77-
}
78-
},
79-
Err(e) => {
80-
eprintln!("Warning: Could not initialize audio output: {e}");
81-
eprintln!("The application will continue but notification sounds may not work.");
82-
(None, None)
83-
}
84-
};
8567

8668
let current_session = PomodoroSession {
8769
timer_type: TimerType::Work,
@@ -100,7 +82,7 @@ impl PomodoroTimer {
10082
custom_input: String::new(),
10183
show_mario_animation: false,
10284
mario_animation: MarioAnimation::new(),
103-
audio_manager: AudioManager { sink },
85+
audio_manager: AudioManager {},
10486
custom_work_duration: Duration::from_secs(25 * 60),
10587
custom_break_duration: Duration::from_secs(5 * 60),
10688
})
@@ -279,7 +261,10 @@ impl PomodoroTimer {
279261
fn play_notification(&self) {
280262
match self.current_session.timer_type {
281263
TimerType::Work => self.audio_manager.play_work_complete_sound(),
282-
TimerType::Break => self.audio_manager.play_break_complete_sound(),
264+
TimerType::Break => {
265+
// Play the combined notification + music sequence for break completion
266+
self.audio_manager.play_break_complete_music();
267+
}
283268
}
284269
}
285270

@@ -443,40 +428,40 @@ fn ui(f: &mut Frame, timer: &PomodoroTimer) {
443428

444429
// Custom input dialog
445430
if timer.show_custom_input {
446-
let popup_area = centered_rect(50, 30, f.area());
431+
let popup_area = centered_rect(70, 50, f.area());
447432
f.render_widget(ratatui::widgets::Clear, popup_area);
448433

449434
let input_popup = Paragraph::new(vec![
450-
Line::from(""),
451-
Line::from(vec![Span::styled(
452-
"CUSTOM TIMER",
453-
Style::default().fg(PRIMARY_COLOR).add_modifier(Modifier::BOLD),
454-
)])
455-
.alignment(Alignment::Center),
435+
// Line::from(""),
436+
// Line::from(vec![Span::styled(
437+
// "CUSTOM TIMER",
438+
// Style::default().fg(PRIMARY_COLOR).add_modifier(Modifier::BOLD),
439+
// )])
440+
// .alignment(Alignment::Center),
456441
Line::from(""),
457442
Line::from(vec![
458-
Span::raw("Format: "),
443+
Span::raw(" Format: "),
459444
Span::styled("work,break", Style::default().fg(HIGHLIGHT_COLOR)),
460445
Span::raw(" or "),
461446
Span::styled("work", Style::default().fg(HIGHLIGHT_COLOR)),
462447
]),
463448
Line::from(vec![
464-
Span::raw("Examples: "),
449+
Span::raw(" Examples: "),
465450
Span::styled("30,10", Style::default().fg(HIGHLIGHT_COLOR)),
466451
Span::raw(" or "),
467452
Span::styled("20", Style::default().fg(HIGHLIGHT_COLOR)),
468453
]),
469454
Line::from(""),
470455
Line::from(vec![
471-
Span::raw("Input: "),
456+
Span::raw(" Input: "),
472457
Span::styled(&timer.custom_input, Style::default().fg(Color::White).add_modifier(Modifier::BOLD)),
473458
Span::styled("█", Style::default().fg(PRIMARY_COLOR)), // Cursor
474459
]),
475460
Line::from(""),
476461
Line::from(vec![
477-
Span::styled("Enter", Style::default().fg(PRIMARY_COLOR).add_modifier(Modifier::BOLD)),
462+
Span::styled("", Style::default().fg(PRIMARY_COLOR).add_modifier(Modifier::BOLD)),
478463
Span::raw(" - Confirm | "),
479-
Span::styled("Esc", Style::default().fg(PRIMARY_COLOR).add_modifier(Modifier::BOLD)),
464+
Span::styled("X", Style::default().fg(PRIMARY_COLOR).add_modifier(Modifier::BOLD)),
480465
Span::raw(" - Cancel"),
481466
]),
482467
])
@@ -485,7 +470,8 @@ fn ui(f: &mut Frame, timer: &PomodoroTimer) {
485470
Block::default()
486471
.borders(Borders::ALL)
487472
.title("Custom Timer")
488-
.border_style(Style::default().fg(PRIMARY_COLOR)),
473+
.border_style(Style::default().fg(PRIMARY_COLOR))
474+
.title_alignment(Alignment::Center),
489475
);
490476
f.render_widget(input_popup, popup_area);
491477
}
@@ -549,11 +535,7 @@ fn run_timer() -> Result<(), Box<dyn std::error::Error>> {
549535

550536
let result = main_loop(&mut terminal, &mut timer);
551537

552-
// Clean shutdown of audio to prevent warning messages
553-
if let Some(ref sink) = timer.audio_manager.sink {
554-
let sink = sink.lock().unwrap();
555-
sink.stop();
556-
}
538+
// Audio cleanup is now handled automatically by each individual playback
557539

558540
disable_raw_mode()?;
559541
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
@@ -587,7 +569,7 @@ fn main_loop(terminal: &mut Terminal<CrosstermBackend<io::Stdout>>, timer: &mut
587569
if timer.show_custom_input {
588570
match key {
589571
KeyEvent {
590-
code: KeyCode::Esc,
572+
code: KeyCode::Char('x'),
591573
modifiers: KeyModifiers::NONE,
592574
..
593575
} => {

0 commit comments

Comments
 (0)