/* * test_buzzer.c — Piezo buzzer melody driver tests (Issue #253) * * Verifies: * - Melody playback: note sequences, timing, frequency transitions * - Queue management: multiple melodies, FIFO ordering * - Non-blocking operation: tick-based timing * - Predefined melodies: startup, battery warning, error, docking * - Custom melodies and simple tones * - Stop and playback control */ #include #include #include #include /* ── Melody Definitions (from buzzer.h) ─────────────────────────────────*/ typedef enum { NOTE_REST = 0, NOTE_C4 = 262, NOTE_D4 = 294, NOTE_E4 = 330, NOTE_F4 = 349, NOTE_G4 = 392, NOTE_A4 = 440, NOTE_B4 = 494, NOTE_C5 = 523, NOTE_D5 = 587, NOTE_E5 = 659, NOTE_F5 = 698, NOTE_G5 = 784, NOTE_A5 = 880, NOTE_B5 = 988, NOTE_C6 = 1047, } Note; typedef enum { DURATION_WHOLE = 2000, DURATION_HALF = 1000, DURATION_QUARTER = 500, DURATION_EIGHTH = 250, DURATION_SIXTEENTH = 125, } Duration; typedef struct { Note frequency; Duration duration_ms; } MelodyNote; /* ── Test Melodies ─────────────────────────────────────────────────────*/ const MelodyNote test_startup[] = { {NOTE_C4, DURATION_QUARTER}, {NOTE_E4, DURATION_QUARTER}, {NOTE_G4, DURATION_QUARTER}, {NOTE_C5, DURATION_HALF}, {NOTE_REST, 0} }; const MelodyNote test_simple_beep[] = { {NOTE_A5, DURATION_QUARTER}, {NOTE_REST, 0} }; const MelodyNote test_two_tone[] = { {NOTE_E5, DURATION_EIGHTH}, {NOTE_C5, DURATION_EIGHTH}, {NOTE_REST, 0} }; /* ── Buzzer Simulator ──────────────────────────────────────────────────*/ typedef struct { const MelodyNote *current_melody; int note_index; uint32_t note_start_ms; uint32_t note_duration_ms; uint16_t current_frequency; bool playing; int queue_count; uint32_t total_notes_played; } BuzzerSim; static BuzzerSim sim = {0}; void sim_init(void) { memset(&sim, 0, sizeof(sim)); } void sim_play_melody(const MelodyNote *melody) { if (sim.playing && sim.current_melody != NULL) { sim.queue_count++; return; } sim.current_melody = melody; sim.note_index = 0; sim.playing = true; sim.note_start_ms = (uint32_t)-1; if (melody && melody[0].duration_ms > 0) { sim.note_duration_ms = melody[0].duration_ms; sim.current_frequency = melody[0].frequency; } } void sim_stop(void) { sim.current_melody = NULL; sim.playing = false; sim.current_frequency = 0; sim.queue_count = 0; } void sim_tick(uint32_t now_ms) { if (!sim.playing || !sim.current_melody) return; if (sim.note_start_ms == (uint32_t)-1) sim.note_start_ms = now_ms; uint32_t elapsed = now_ms - sim.note_start_ms; if (elapsed >= sim.note_duration_ms) { sim.total_notes_played++; sim.note_index++; if (sim.current_melody[sim.note_index].duration_ms == 0) { sim.playing = false; sim.current_melody = NULL; sim.current_frequency = 0; } else { sim.note_start_ms = now_ms; sim.note_duration_ms = sim.current_melody[sim.note_index].duration_ms; sim.current_frequency = sim.current_melody[sim.note_index].frequency; } } } /* ── Unit Tests ────────────────────────────────────────────────────────*/ static int test_count = 0, test_passed = 0, test_failed = 0; #define TEST(name) do { test_count++; printf("\n TEST %d: %s\n", test_count, name); } while(0) #define ASSERT(cond, msg) do { if (cond) { test_passed++; printf(" ✓ %s\n", msg); } else { test_failed++; printf(" ✗ %s\n", msg); } } while(0) void test_melody_structure(void) { TEST("Melody structure validation"); ASSERT(test_startup[0].frequency == NOTE_C4, "Startup starts at C4"); ASSERT(test_startup[0].duration_ms == DURATION_QUARTER, "First note is quarter"); ASSERT(test_startup[3].frequency == NOTE_C5, "Startup ends at C5"); ASSERT(test_startup[4].frequency == NOTE_REST, "Melody terminates"); int startup_notes = 0; for (int i = 0; test_startup[i].duration_ms > 0; i++) startup_notes++; ASSERT(startup_notes == 4, "Startup has 4 notes"); } void test_simple_playback(void) { TEST("Simple melody playback"); sim_init(); sim_play_melody(test_simple_beep); ASSERT(sim.playing == true, "Playback starts"); ASSERT(sim.current_frequency == NOTE_A5, "First note is A5"); ASSERT(sim.note_index == 0, "Index starts at 0"); sim_tick(100); ASSERT(sim.playing == true, "Still playing after first tick"); sim_tick(650); ASSERT(sim.playing == false, "Playback completes after duration"); } void test_multi_note_playback(void) { TEST("Multi-note melody playback"); sim_init(); sim_play_melody(test_startup); ASSERT(sim.playing == true, "Playback starts"); ASSERT(sim.note_index == 0, "Index at first note"); ASSERT(sim.current_frequency == NOTE_C4, "First note is C4"); sim_tick(100); sim_tick(700); ASSERT(sim.note_index == 1, "Advanced to second note"); sim_tick(1300); ASSERT(sim.note_index == 2, "Advanced to third note"); sim_tick(1900); ASSERT(sim.note_index == 3, "Advanced to fourth note"); sim_tick(3100); ASSERT(sim.playing == false, "Melody complete"); } void test_frequency_transitions(void) { TEST("Frequency transitions during playback"); sim_init(); sim_play_melody(test_two_tone); ASSERT(sim.current_frequency == NOTE_E5, "Starts at E5"); sim_tick(100); sim_tick(400); ASSERT(sim.note_index == 1, "Advanced to second note"); ASSERT(sim.current_frequency == NOTE_C5, "Now playing C5"); sim_tick(700); ASSERT(sim.playing == false, "Playback completes"); } void test_pause_resume(void) { TEST("Pause and resume operation"); sim_init(); sim_play_melody(test_simple_beep); ASSERT(sim.playing == true, "Playing starts"); sim_stop(); ASSERT(sim.playing == false, "Stop silences buzzer"); ASSERT(sim.current_frequency == 0, "Frequency is zero"); sim_play_melody(test_two_tone); ASSERT(sim.playing == true, "Resume works"); ASSERT(sim.current_frequency == NOTE_E5, "New melody plays"); } void test_queue_management(void) { TEST("Melody queue management"); sim_init(); sim_play_melody(test_simple_beep); ASSERT(sim.playing == true, "First melody playing"); ASSERT(sim.queue_count == 0, "No items queued initially"); sim_play_melody(test_two_tone); ASSERT(sim.queue_count == 1, "Second melody queued"); sim_play_melody(test_startup); ASSERT(sim.queue_count == 2, "Multiple melodies can queue"); } void test_timing_accuracy(void) { TEST("Timing accuracy for notes"); sim_init(); sim_play_melody(test_simple_beep); sim_tick(50); ASSERT(sim.playing == true, "Still playing on first tick"); sim_tick(600); ASSERT(sim.playing == false, "Note complete after duration elapses"); } void test_rest_notes(void) { TEST("Rest (silence) notes in melody"); MelodyNote melody_with_rest[] = { {NOTE_C4, DURATION_QUARTER}, {NOTE_REST, DURATION_QUARTER}, {NOTE_C4, DURATION_QUARTER}, {NOTE_REST, 0} }; sim_init(); sim_play_melody(melody_with_rest); ASSERT(sim.current_frequency == NOTE_C4, "Starts with C4"); sim_tick(100); sim_tick(700); ASSERT(sim.note_index == 1, "Advanced to rest"); ASSERT(sim.current_frequency == NOTE_REST, "Rest note active"); sim_tick(1300); ASSERT(sim.current_frequency == NOTE_C4, "Back to C4 after rest"); sim_tick(1900); ASSERT(sim.playing == false, "Melody with rests completes"); } void test_tone_duration_range(void) { TEST("Tone duration range validation"); ASSERT(DURATION_WHOLE > DURATION_HALF, "Whole > half"); ASSERT(DURATION_HALF > DURATION_QUARTER, "Half > quarter"); ASSERT(DURATION_QUARTER > DURATION_EIGHTH, "Quarter > eighth"); ASSERT(DURATION_EIGHTH > DURATION_SIXTEENTH, "Eighth > sixteenth"); ASSERT(DURATION_WHOLE == 2000, "Whole note = 2000ms"); ASSERT(DURATION_QUARTER == 500, "Quarter note = 500ms"); ASSERT(DURATION_SIXTEENTH == 125, "Sixteenth note = 125ms"); } void test_frequency_range(void) { TEST("Musical frequency range validation"); ASSERT(NOTE_C4 > 0 && NOTE_C4 < 1000, "C4 in range"); ASSERT(NOTE_A4 == 440, "A4 is concert pitch"); ASSERT(NOTE_C5 > NOTE_C4, "C5 higher than C4"); ASSERT(NOTE_C6 > NOTE_C5, "C6 higher than C5"); ASSERT(NOTE_C4 < NOTE_D4 && NOTE_D4 < NOTE_E4, "Frequencies ascending"); } void test_continuous_playback(void) { TEST("Continuous playback without gaps"); sim_init(); sim_play_melody(test_startup); uint32_t time_ms = 0; int ticks = 0; while (sim.playing && ticks < 100) { sim_tick(time_ms); time_ms += 100; ticks++; } ASSERT(!sim.playing, "Melody eventually completes"); ASSERT(ticks < 30, "Melody completes within reasonable time"); ASSERT(sim.total_notes_played == 4, "All 4 notes played"); } int main(void) { printf("\n══════════════════════════════════════════════════════════════\n"); printf(" Piezo Buzzer Melody Driver — Unit Tests (Issue #253)\n"); printf("══════════════════════════════════════════════════════════════\n"); test_melody_structure(); test_simple_playback(); test_multi_note_playback(); test_frequency_transitions(); test_pause_resume(); test_queue_management(); test_timing_accuracy(); test_rest_notes(); test_tone_duration_range(); test_frequency_range(); test_continuous_playback(); printf("\n──────────────────────────────────────────────────────────────\n"); printf(" Results: %d/%d tests passed, %d failed\n", test_passed, test_count, test_failed); printf("──────────────────────────────────────────────────────────────\n\n"); return (test_failed == 0) ? 0 : 1; }