mirror of
https://github.com/eried/portapack-mayhem.git
synced 2025-11-09 15:25:06 -05:00
Add sound effects (#2849)
* Add sound effects Adding sound effects and volume adjuster in start menu. Sounds are a little clicky but I tinkered a long time and couldn't solve that. Maybe someone else wants to tinker a bit with me. * Format code
This commit is contained in:
parent
454b8776b6
commit
113cbc42c9
3 changed files with 312 additions and 23 deletions
24
firmware/application/external/breakout/main.cpp
vendored
24
firmware/application/external/breakout/main.cpp
vendored
|
|
@ -20,13 +20,13 @@ void initialize_app(ui::NavigationView& nav) {
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
__attribute__((section(".external_app.app_breakout.application_information"), used)) application_information_t _application_information_breakout = {
|
__attribute__((section(".external_app.app_breakout.application_information"), used)) application_information_t _application_information_breakout = {
|
||||||
(uint8_t*)0x00000000,
|
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||||
ui::external_app::breakout::initialize_app,
|
/*.externalAppEntry = */ ui::external_app::breakout::initialize_app,
|
||||||
CURRENT_HEADER_VERSION,
|
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||||
VERSION_MD5,
|
/*.app_version = */ VERSION_MD5,
|
||||||
|
|
||||||
"Breakout",
|
/*.app_name = */ "Breakout",
|
||||||
{
|
/*.bitmap_data = */ {
|
||||||
0x00,
|
0x00,
|
||||||
0x00,
|
0x00,
|
||||||
0x7F,
|
0x7F,
|
||||||
|
|
@ -60,11 +60,11 @@ __attribute__((section(".external_app.app_breakout.application_information"), us
|
||||||
0xF7,
|
0xF7,
|
||||||
0xF7,
|
0xF7,
|
||||||
},
|
},
|
||||||
ui::Color::green().v,
|
/*.icon_color = */ ui::Color::green().v,
|
||||||
app_location_t::GAMES,
|
/*.menu_location = */ app_location_t::GAMES,
|
||||||
-1,
|
/*.desired_menu_position = */ -1,
|
||||||
|
|
||||||
{0, 0, 0, 0},
|
/*.m4_app_tag = */ {'P', 'A', 'B', 'P'}, // This is the audio beep baseband tag!
|
||||||
0x00000000,
|
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||||
};
|
};
|
||||||
} // namespace ui::external_app::breakout
|
}
|
||||||
|
|
@ -7,6 +7,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ui_breakout.hpp"
|
#include "ui_breakout.hpp"
|
||||||
|
#include "baseband_api.hpp"
|
||||||
|
#include "audio.hpp"
|
||||||
|
#include "portapack.hpp"
|
||||||
|
#include "portapack_persistent_memory.hpp"
|
||||||
|
|
||||||
|
using namespace portapack;
|
||||||
|
|
||||||
namespace ui::external_app::breakout {
|
namespace ui::external_app::breakout {
|
||||||
|
|
||||||
|
|
@ -92,6 +98,37 @@ void BreakoutView::init_game() {
|
||||||
game_state = STATE_MENU;
|
game_state = STATE_MENU;
|
||||||
menu_blink_state = true;
|
menu_blink_state = true;
|
||||||
menu_blink_counter = 0;
|
menu_blink_counter = 0;
|
||||||
|
|
||||||
|
// Play startup sound
|
||||||
|
if (sound_enabled) {
|
||||||
|
baseband::request_audio_beep(262, 24000, 150); // C4 -
|
||||||
|
chThdSleepMilliseconds(200);
|
||||||
|
baseband::request_audio_beep(262, 24000, 150); // C4
|
||||||
|
chThdSleepMilliseconds(200);
|
||||||
|
baseband::request_audio_beep(392, 24000, 150); // G4
|
||||||
|
chThdSleepMilliseconds(200);
|
||||||
|
baseband::request_audio_beep(392, 24000, 150); // G4
|
||||||
|
chThdSleepMilliseconds(200);
|
||||||
|
baseband::request_audio_beep(440, 24000, 150); // A4
|
||||||
|
chThdSleepMilliseconds(200);
|
||||||
|
baseband::request_audio_beep(440, 24000, 150); // A4
|
||||||
|
chThdSleepMilliseconds(200);
|
||||||
|
baseband::request_audio_beep(392, 24000, 300); // G4
|
||||||
|
chThdSleepMilliseconds(350);
|
||||||
|
baseband::request_audio_beep(349, 24000, 150); // F4
|
||||||
|
chThdSleepMilliseconds(200);
|
||||||
|
baseband::request_audio_beep(349, 24000, 150); // F4
|
||||||
|
chThdSleepMilliseconds(200);
|
||||||
|
baseband::request_audio_beep(330, 24000, 150); // E4
|
||||||
|
chThdSleepMilliseconds(200);
|
||||||
|
baseband::request_audio_beep(330, 24000, 150); // E4
|
||||||
|
chThdSleepMilliseconds(200);
|
||||||
|
baseband::request_audio_beep(294, 24000, 150); // D4
|
||||||
|
chThdSleepMilliseconds(200);
|
||||||
|
baseband::request_audio_beep(294, 24000, 150); // D4
|
||||||
|
chThdSleepMilliseconds(200);
|
||||||
|
baseband::request_audio_beep(262, 24000, 400); // C4
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BreakoutView::init_level() {
|
void BreakoutView::init_level() {
|
||||||
|
|
@ -126,6 +163,34 @@ void BreakoutView::draw_screen() {
|
||||||
draw_level();
|
draw_level();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BreakoutView::draw_menu_volume_bar() {
|
||||||
|
// Clear the entire volume area first to handle bar shrinking
|
||||||
|
painter.fill_rectangle({40, 259, 220, 40}, Color::black());
|
||||||
|
|
||||||
|
// Draw volume label
|
||||||
|
auto style = *ui::Theme::getInstance()->fg_light;
|
||||||
|
painter.draw_string({40, 260}, style, "Volume:");
|
||||||
|
|
||||||
|
// Draw volume bar background
|
||||||
|
painter.draw_rectangle({100, 259, 102, 10}, Color::grey());
|
||||||
|
|
||||||
|
// Draw volume level
|
||||||
|
int bar_width = (volume_level * 100) / 99;
|
||||||
|
if (bar_width > 0) {
|
||||||
|
painter.fill_rectangle({101, 260, bar_width, 8}, Color::green());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw percentage
|
||||||
|
painter.draw_string({210, 260}, style, std::to_string(volume_level) + "%");
|
||||||
|
|
||||||
|
// Draw sound status
|
||||||
|
if (sound_enabled) {
|
||||||
|
painter.draw_string({UI_POS_X_CENTER(11), 280}, *ui::Theme::getInstance()->fg_green, "Sound: ON");
|
||||||
|
} else {
|
||||||
|
painter.draw_string({UI_POS_X_CENTER(11), 280}, *ui::Theme::getInstance()->fg_red, "Sound: OFF");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void BreakoutView::draw_borders() {
|
void BreakoutView::draw_borders() {
|
||||||
rect(0, GAME_AREA_TOP - 1, SCREEN_WIDTH, GAME_AREA_TOP, COLOR_BORDER);
|
rect(0, GAME_AREA_TOP - 1, SCREEN_WIDTH, GAME_AREA_TOP, COLOR_BORDER);
|
||||||
}
|
}
|
||||||
|
|
@ -166,6 +231,22 @@ void BreakoutView::draw_level() {
|
||||||
painter.draw_string({80, 30}, style, "Level: " + std::to_string(level));
|
painter.draw_string({80, 30}, style, "Level: " + std::to_string(level));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BreakoutView::adjust_volume(int delta) {
|
||||||
|
volume_level += delta;
|
||||||
|
if (volume_level < 0) volume_level = 0;
|
||||||
|
if (volume_level > 99) volume_level = 99;
|
||||||
|
|
||||||
|
// Update the actual audio volume using decibel conversion
|
||||||
|
// Map 0-99 to roughly -60dB to 0dB range
|
||||||
|
int db = (volume_level - 99) * 60 / 99; // Maps 0->-60dB, 99->0dB
|
||||||
|
audio::headphone::set_volume(volume_t::decibel(db));
|
||||||
|
|
||||||
|
// If we're in the menu, redraw the volume bar
|
||||||
|
if (game_state == STATE_MENU) {
|
||||||
|
draw_menu_volume_bar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void BreakoutView::move_paddle_left() {
|
void BreakoutView::move_paddle_left() {
|
||||||
if (paddle_x > 0) {
|
if (paddle_x > 0) {
|
||||||
fillrect(paddle_x, PADDLE_Y, paddle_x + PADDLE_WIDTH, PADDLE_Y + PADDLE_HEIGHT, COLOR_BACKGROUND);
|
fillrect(paddle_x, PADDLE_Y, paddle_x + PADDLE_WIDTH, PADDLE_Y + PADDLE_HEIGHT, COLOR_BACKGROUND);
|
||||||
|
|
@ -231,19 +312,23 @@ void BreakoutView::update_game() {
|
||||||
if (ball_x < 0) {
|
if (ball_x < 0) {
|
||||||
ball_x = 0;
|
ball_x = 0;
|
||||||
ball_dx = -ball_dx;
|
ball_dx = -ball_dx;
|
||||||
|
play_wall_hit();
|
||||||
} else if (ball_x > SCREEN_WIDTH - BALL_SIZE) {
|
} else if (ball_x > SCREEN_WIDTH - BALL_SIZE) {
|
||||||
ball_x = SCREEN_WIDTH - BALL_SIZE;
|
ball_x = SCREEN_WIDTH - BALL_SIZE;
|
||||||
ball_dx = -ball_dx;
|
ball_dx = -ball_dx;
|
||||||
|
play_wall_hit();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ball_y < GAME_AREA_TOP) {
|
if (ball_y < GAME_AREA_TOP) {
|
||||||
ball_y = GAME_AREA_TOP;
|
ball_y = GAME_AREA_TOP;
|
||||||
ball_dy = -ball_dy;
|
ball_dy = -ball_dy;
|
||||||
|
play_wall_hit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bottom edge - lose life
|
// Bottom edge - lose life
|
||||||
if (ball_y > GAME_AREA_BOTTOM) {
|
if (ball_y > GAME_AREA_BOTTOM) {
|
||||||
lives--;
|
lives--;
|
||||||
|
play_death_sound(); // Play death sound
|
||||||
draw_lives();
|
draw_lives();
|
||||||
if (lives <= 0) {
|
if (lives <= 0) {
|
||||||
handle_game_over();
|
handle_game_over();
|
||||||
|
|
@ -262,6 +347,8 @@ void BreakoutView::update_game() {
|
||||||
ball_y = PADDLE_Y - BALL_SIZE;
|
ball_y = PADDLE_Y - BALL_SIZE;
|
||||||
ball_dy = -ball_dy;
|
ball_dy = -ball_dy;
|
||||||
|
|
||||||
|
play_paddle_hit();
|
||||||
|
|
||||||
// Add some angle based on hit position
|
// Add some angle based on hit position
|
||||||
float hit_position = (ball_x + (BALL_SIZE / 2)) - paddle_x;
|
float hit_position = (ball_x + (BALL_SIZE / 2)) - paddle_x;
|
||||||
float angle = (hit_position / PADDLE_WIDTH) - 0.5f;
|
float angle = (hit_position / PADDLE_WIDTH) - 0.5f;
|
||||||
|
|
@ -301,6 +388,8 @@ bool BreakoutView::check_brick_collision(int row, int col) {
|
||||||
score += (5 - row) * 10;
|
score += (5 - row) * 10;
|
||||||
draw_score();
|
draw_score();
|
||||||
|
|
||||||
|
play_brick_hit();
|
||||||
|
|
||||||
ball_dy = -ball_dy;
|
ball_dy = -ball_dy;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -314,6 +403,8 @@ bool BreakoutView::check_level_complete() {
|
||||||
|
|
||||||
void BreakoutView::next_level() {
|
void BreakoutView::next_level() {
|
||||||
level++;
|
level++;
|
||||||
|
play_level_complete();
|
||||||
|
chThdSleepMilliseconds(500); // Give time for sound to play
|
||||||
init_level();
|
init_level();
|
||||||
draw_screen();
|
draw_screen();
|
||||||
}
|
}
|
||||||
|
|
@ -322,9 +413,11 @@ void BreakoutView::handle_game_over() {
|
||||||
game_state = STATE_GAME_OVER;
|
game_state = STATE_GAME_OVER;
|
||||||
gameover_blink_state = true;
|
gameover_blink_state = true;
|
||||||
gameover_blink_counter = 0;
|
gameover_blink_counter = 0;
|
||||||
|
play_game_over();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BreakoutView::show_menu() {
|
void BreakoutView::show_menu() {
|
||||||
|
// Only draw the static parts once
|
||||||
if (menu_blink_counter == 0) {
|
if (menu_blink_counter == 0) {
|
||||||
cls();
|
cls();
|
||||||
background(COLOR_BACKGROUND);
|
background(COLOR_BACKGROUND);
|
||||||
|
|
@ -338,23 +431,30 @@ void BreakoutView::show_menu() {
|
||||||
painter.draw_string({UI_POS_X_CENTER(20), 130}, style_cyan, "ROTARY: MOVE PADDLE");
|
painter.draw_string({UI_POS_X_CENTER(20), 130}, style_cyan, "ROTARY: MOVE PADDLE");
|
||||||
painter.draw_string({UI_POS_X_CENTER(21), 160}, style_cyan, "SELECT: START/LAUNCH");
|
painter.draw_string({UI_POS_X_CENTER(21), 160}, style_cyan, "SELECT: START/LAUNCH");
|
||||||
painter.draw_string({UI_POS_X_CENTER(25), 190}, style_blue, "========================");
|
painter.draw_string({UI_POS_X_CENTER(25), 190}, style_blue, "========================");
|
||||||
|
|
||||||
|
// Volume adjustment instructions
|
||||||
|
painter.draw_string({UI_POS_X_CENTER(24), 220}, style_cyan, "Press UP/DOWN for Volume");
|
||||||
|
|
||||||
|
// Draw volume control at bottom
|
||||||
|
draw_menu_volume_bar();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto style_red = *ui::Theme::getInstance()->fg_red;
|
// Only flash the "PRESS SELECT" text
|
||||||
|
|
||||||
if (++menu_blink_counter >= 30) {
|
if (++menu_blink_counter >= 30) {
|
||||||
menu_blink_counter = 0;
|
menu_blink_counter = 1; // Keep it at 1 so we don't redraw everything
|
||||||
menu_blink_state = !menu_blink_state;
|
menu_blink_state = !menu_blink_state;
|
||||||
|
|
||||||
painter.fill_rectangle({UI_POS_X_CENTER(17), 228, 128, 20}, Color::black());
|
auto style_red = *ui::Theme::getInstance()->fg_red;
|
||||||
|
painter.fill_rectangle({UI_POS_X_CENTER(17), 238, 128, 20}, Color::black());
|
||||||
|
|
||||||
if (menu_blink_state) {
|
if (menu_blink_state) {
|
||||||
painter.draw_string({UI_POS_X_CENTER(17), 230}, style_red, "* PRESS SELECT *");
|
painter.draw_string({UI_POS_X_CENTER(17), 240}, style_red, "* PRESS SELECT *");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BreakoutView::show_game_over() {
|
void BreakoutView::show_game_over() {
|
||||||
|
// Only draw the static parts once
|
||||||
if (gameover_blink_counter == 0) {
|
if (gameover_blink_counter == 0) {
|
||||||
cls();
|
cls();
|
||||||
background(COLOR_BACKGROUND);
|
background(COLOR_BACKGROUND);
|
||||||
|
|
@ -366,12 +466,12 @@ void BreakoutView::show_game_over() {
|
||||||
painter.draw_string({UI_POS_X_CENTER(11), 150}, style_yellow, "SCORE: " + std::to_string(score));
|
painter.draw_string({UI_POS_X_CENTER(11), 150}, style_yellow, "SCORE: " + std::to_string(score));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto style_green = *ui::Theme::getInstance()->fg_green;
|
// Only flash the "PRESS SELECT" text
|
||||||
|
|
||||||
if (++gameover_blink_counter >= 30) {
|
if (++gameover_blink_counter >= 30) {
|
||||||
gameover_blink_counter = 0;
|
gameover_blink_counter = 1; // Keep it at 1 so we don't redraw everything
|
||||||
gameover_blink_state = !gameover_blink_state;
|
gameover_blink_state = !gameover_blink_state;
|
||||||
|
|
||||||
|
auto style_green = *ui::Theme::getInstance()->fg_green;
|
||||||
painter.fill_rectangle({UI_POS_X_CENTER(13), 198, 96, 20}, Color::black());
|
painter.fill_rectangle({UI_POS_X_CENTER(13), 198, 96, 20}, Color::black());
|
||||||
|
|
||||||
if (gameover_blink_state) {
|
if (gameover_blink_state) {
|
||||||
|
|
@ -385,17 +485,169 @@ void BreakoutView::reset_game() {
|
||||||
score = 0;
|
score = 0;
|
||||||
lives = 3;
|
lives = 3;
|
||||||
game_state = STATE_PLAYING;
|
game_state = STATE_PLAYING;
|
||||||
|
|
||||||
|
dummy.focus();
|
||||||
|
|
||||||
init_level();
|
init_level();
|
||||||
draw_screen();
|
draw_screen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sound effect implementations with timing control
|
||||||
|
void BreakoutView::play_paddle_hit() {
|
||||||
|
if (sound_enabled) {
|
||||||
|
uint32_t current_time = chTimeNow();
|
||||||
|
if (current_time - last_sound_time >= min_sound_interval) {
|
||||||
|
baseband::request_beep_stop(); // Stop any previous sound
|
||||||
|
chThdSleepMilliseconds(5); // Small delay
|
||||||
|
baseband::request_audio_beep(440, 24000, 40); // 440Hz A4, 40ms
|
||||||
|
last_sound_time = current_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BreakoutView::play_brick_hit() {
|
||||||
|
if (sound_enabled) {
|
||||||
|
uint32_t current_time = chTimeNow();
|
||||||
|
if (current_time - last_sound_time >= min_sound_interval) {
|
||||||
|
baseband::request_beep_stop(); // Stop any previous sound
|
||||||
|
chThdSleepMilliseconds(5); // Small delay
|
||||||
|
// Higher pitch that varies with remaining bricks
|
||||||
|
uint32_t freq = 880 + ((25 - brick_count) * 20); // Gets higher as bricks are destroyed
|
||||||
|
baseband::request_audio_beep(freq, 24000, 30);
|
||||||
|
last_sound_time = current_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BreakoutView::play_wall_hit() {
|
||||||
|
if (sound_enabled) {
|
||||||
|
uint32_t current_time = chTimeNow();
|
||||||
|
if (current_time - last_sound_time >= min_sound_interval) {
|
||||||
|
baseband::request_beep_stop(); // Stop any previous sound
|
||||||
|
chThdSleepMilliseconds(5); // Small delay
|
||||||
|
baseband::request_audio_beep(220, 24000, 120); // 220Hz A3, 120ms
|
||||||
|
last_sound_time = current_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BreakoutView::play_death_sound() {
|
||||||
|
if (sound_enabled) {
|
||||||
|
baseband::request_audio_beep(493, 24000, 150); // B4
|
||||||
|
chThdSleepMilliseconds(180);
|
||||||
|
baseband::request_audio_beep(466, 24000, 150); // Bb4
|
||||||
|
chThdSleepMilliseconds(180);
|
||||||
|
baseband::request_audio_beep(440, 24000, 150); // A4
|
||||||
|
chThdSleepMilliseconds(180);
|
||||||
|
baseband::request_audio_beep(415, 24000, 150); // Ab4
|
||||||
|
chThdSleepMilliseconds(180);
|
||||||
|
baseband::request_audio_beep(392, 24000, 150); // G4
|
||||||
|
chThdSleepMilliseconds(180);
|
||||||
|
baseband::request_audio_beep(369, 24000, 150); // Gb4
|
||||||
|
chThdSleepMilliseconds(180);
|
||||||
|
baseband::request_audio_beep(349, 24000, 150); // F4
|
||||||
|
chThdSleepMilliseconds(180);
|
||||||
|
baseband::request_audio_beep(329, 24000, 150); // E4
|
||||||
|
chThdSleepMilliseconds(180);
|
||||||
|
baseband::request_audio_beep(293, 24000, 150); // D4
|
||||||
|
chThdSleepMilliseconds(180);
|
||||||
|
baseband::request_audio_beep(261, 24000, 150); // C4
|
||||||
|
chThdSleepMilliseconds(180);
|
||||||
|
baseband::request_audio_beep(196, 24000, 200); // G3
|
||||||
|
chThdSleepMilliseconds(230);
|
||||||
|
baseband::request_audio_beep(130, 24000, 300); // C3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BreakoutView::play_game_over() {
|
||||||
|
if (sound_enabled) {
|
||||||
|
// First phrase
|
||||||
|
baseband::request_audio_beep(392, 24000, 300); // G4
|
||||||
|
chThdSleepMilliseconds(350);
|
||||||
|
baseband::request_audio_beep(392, 24000, 150); // G4
|
||||||
|
chThdSleepMilliseconds(200);
|
||||||
|
baseband::request_audio_beep(392, 24000, 150); // G4
|
||||||
|
chThdSleepMilliseconds(200);
|
||||||
|
baseband::request_audio_beep(311, 24000, 500); // Eb4 - long
|
||||||
|
chThdSleepMilliseconds(600);
|
||||||
|
|
||||||
|
// Second phrase
|
||||||
|
baseband::request_audio_beep(349, 24000, 300); // F4
|
||||||
|
chThdSleepMilliseconds(350);
|
||||||
|
baseband::request_audio_beep(349, 24000, 150); // F4
|
||||||
|
chThdSleepMilliseconds(200);
|
||||||
|
baseband::request_audio_beep(349, 24000, 150); // F4
|
||||||
|
chThdSleepMilliseconds(200);
|
||||||
|
baseband::request_audio_beep(294, 24000, 500); // D4 - long
|
||||||
|
chThdSleepMilliseconds(600);
|
||||||
|
|
||||||
|
// Final phrase - slower and more dramatic
|
||||||
|
baseband::request_audio_beep(262, 24000, 200); // C4
|
||||||
|
chThdSleepMilliseconds(250);
|
||||||
|
baseband::request_audio_beep(247, 24000, 200); // B3
|
||||||
|
chThdSleepMilliseconds(250);
|
||||||
|
baseband::request_audio_beep(233, 24000, 200); // Bb3
|
||||||
|
chThdSleepMilliseconds(250);
|
||||||
|
baseband::request_audio_beep(220, 24000, 200); // A3
|
||||||
|
chThdSleepMilliseconds(250);
|
||||||
|
baseband::request_audio_beep(196, 24000, 200); // G3
|
||||||
|
chThdSleepMilliseconds(250);
|
||||||
|
baseband::request_audio_beep(175, 24000, 200); // F3
|
||||||
|
chThdSleepMilliseconds(250);
|
||||||
|
baseband::request_audio_beep(131, 24000, 600); // C3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BreakoutView::play_level_complete() {
|
||||||
|
if (sound_enabled) {
|
||||||
|
baseband::request_beep_stop(); // Stop any previous sound
|
||||||
|
chThdSleepMilliseconds(10);
|
||||||
|
baseband::request_audio_beep(1047, 24000, 300); // 1047Hz C6, 300ms
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BreakoutView::BreakoutView(NavigationView& nav)
|
BreakoutView::BreakoutView(NavigationView& nav)
|
||||||
: nav_{nav}, bricks{} { // Add bricks{} here
|
: nav_{nav}, bricks{} {
|
||||||
|
// Initialize audio system for beeps FIRST
|
||||||
|
baseband::run_prepared_image(portapack::memory::map::m4_code.base()); // Load the audio beep baseband
|
||||||
|
|
||||||
add_children({&dummy});
|
add_children({&dummy});
|
||||||
|
|
||||||
|
// Initialize audio
|
||||||
|
audio::set_rate(audio::Rate::Hz_24000);
|
||||||
|
audio::output::start();
|
||||||
|
|
||||||
|
// Set initial volume to 80% and ensure sound is ON
|
||||||
|
volume_level = 80;
|
||||||
|
sound_enabled = true; // Make sure this is explicitly set to ON
|
||||||
|
|
||||||
|
// Set the actual volume
|
||||||
|
int db = (volume_level - 99) * 60 / 99; // Maps to roughly -12dB for 80%
|
||||||
|
audio::headphone::set_volume(volume_t::decibel(db));
|
||||||
|
|
||||||
attach(1.0 / 60.0);
|
attach(1.0 / 60.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BreakoutView::~BreakoutView() {
|
||||||
|
// Clean shutdown
|
||||||
|
baseband::request_beep_stop();
|
||||||
|
receiver_model.disable();
|
||||||
|
baseband::shutdown();
|
||||||
|
audio::output::stop();
|
||||||
|
}
|
||||||
|
|
||||||
void BreakoutView::on_show() {
|
void BreakoutView::on_show() {
|
||||||
|
// Nothing needed here, initialization done in constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
void BreakoutView::on_hide() {
|
||||||
|
// Additional cleanup
|
||||||
|
baseband::request_beep_stop();
|
||||||
|
audio::output::stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BreakoutView::focus() {
|
||||||
|
dummy.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BreakoutView::paint(Painter& painter) {
|
void BreakoutView::paint(Painter& painter) {
|
||||||
|
|
@ -433,16 +685,33 @@ bool BreakoutView::on_key(const KeyEvent key) {
|
||||||
} else if (game_state == STATE_PLAYING && ball_attached) {
|
} else if (game_state == STATE_PLAYING && ball_attached) {
|
||||||
launch_ball();
|
launch_ball();
|
||||||
} else if (game_state == STATE_GAME_OVER) {
|
} else if (game_state == STATE_GAME_OVER) {
|
||||||
reset_game();
|
// Return to menu after game over
|
||||||
|
game_state = STATE_MENU;
|
||||||
|
menu_blink_counter = 0;
|
||||||
|
init_game();
|
||||||
}
|
}
|
||||||
} else if (key == KeyEvent::Left && game_state == STATE_PLAYING) {
|
} else if (key == KeyEvent::Left && game_state == STATE_PLAYING) {
|
||||||
move_paddle_left();
|
move_paddle_left();
|
||||||
} else if (key == KeyEvent::Right && game_state == STATE_PLAYING) {
|
} else if (key == KeyEvent::Right && game_state == STATE_PLAYING) {
|
||||||
move_paddle_right();
|
move_paddle_right();
|
||||||
|
} else if (key == KeyEvent::Up && game_state == STATE_MENU) {
|
||||||
|
// Only adjust volume in menu
|
||||||
|
adjust_volume(5);
|
||||||
|
if (!sound_enabled) sound_enabled = true;
|
||||||
|
set_dirty();
|
||||||
|
} else if (key == KeyEvent::Down && game_state == STATE_MENU) {
|
||||||
|
// Only adjust volume in menu
|
||||||
|
adjust_volume(-5);
|
||||||
|
set_dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
set_dirty();
|
set_dirty();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BreakoutView::on_touch(const TouchEvent event) {
|
||||||
|
(void)event; // Intentionally unused - touch disabled to avoid nav bar issues
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ui::external_app::breakout
|
} // namespace ui::external_app::breakout
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
#include "ui.hpp"
|
#include "ui.hpp"
|
||||||
#include "ui_navigation.hpp"
|
#include "ui_navigation.hpp"
|
||||||
|
#include "ui_receiver.hpp"
|
||||||
#include "event_m0.hpp"
|
#include "event_m0.hpp"
|
||||||
#include "message.hpp"
|
#include "message.hpp"
|
||||||
#include "irq_controls.hpp"
|
#include "irq_controls.hpp"
|
||||||
|
|
@ -56,15 +57,18 @@ enum {
|
||||||
class BreakoutView : public View {
|
class BreakoutView : public View {
|
||||||
public:
|
public:
|
||||||
BreakoutView(NavigationView& nav);
|
BreakoutView(NavigationView& nav);
|
||||||
|
~BreakoutView();
|
||||||
void on_show() override;
|
void on_show() override;
|
||||||
|
void on_hide() override;
|
||||||
|
|
||||||
std::string title() const override { return "Breakout"; }
|
std::string title() const override { return "Breakout"; }
|
||||||
|
|
||||||
void focus() override { dummy.focus(); }
|
void focus() override;
|
||||||
void paint(Painter& painter) override;
|
void paint(Painter& painter) override;
|
||||||
void frame_sync();
|
void frame_sync();
|
||||||
bool on_encoder(const EncoderEvent event) override;
|
bool on_encoder(const EncoderEvent event) override;
|
||||||
bool on_key(KeyEvent key) override;
|
bool on_key(KeyEvent key) override;
|
||||||
|
bool on_touch(const TouchEvent event) override;
|
||||||
|
|
||||||
void cls();
|
void cls();
|
||||||
void background(int color);
|
void background(int color);
|
||||||
|
|
@ -81,6 +85,7 @@ class BreakoutView : public View {
|
||||||
void draw_lives();
|
void draw_lives();
|
||||||
void draw_level();
|
void draw_level();
|
||||||
void draw_borders();
|
void draw_borders();
|
||||||
|
void draw_menu_volume_bar();
|
||||||
void move_paddle_left();
|
void move_paddle_left();
|
||||||
void move_paddle_right();
|
void move_paddle_right();
|
||||||
void launch_ball();
|
void launch_ball();
|
||||||
|
|
@ -96,6 +101,15 @@ class BreakoutView : public View {
|
||||||
void check_game_timer();
|
void check_game_timer();
|
||||||
void attach(double delay_sec);
|
void attach(double delay_sec);
|
||||||
void detach();
|
void detach();
|
||||||
|
void adjust_volume(int delta);
|
||||||
|
|
||||||
|
// Sound effect methods
|
||||||
|
void play_paddle_hit();
|
||||||
|
void play_brick_hit();
|
||||||
|
void play_wall_hit();
|
||||||
|
void play_death_sound();
|
||||||
|
void play_game_over();
|
||||||
|
void play_level_complete();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const Color pp_colors[9] = {
|
const Color pp_colors[9] = {
|
||||||
|
|
@ -147,6 +161,12 @@ class BreakoutView : public View {
|
||||||
double game_update_timeout = 0;
|
double game_update_timeout = 0;
|
||||||
uint32_t game_update_counter = 0;
|
uint32_t game_update_counter = 0;
|
||||||
|
|
||||||
|
// Audio control
|
||||||
|
bool sound_enabled = true;
|
||||||
|
int volume_level = 80; // 0-99
|
||||||
|
uint32_t last_sound_time = 0;
|
||||||
|
uint32_t min_sound_interval = 20; // Minimum ms between sounds
|
||||||
|
|
||||||
Button dummy{
|
Button dummy{
|
||||||
{screen_width, 0, 0, 0},
|
{screen_width, 0, 0, 0},
|
||||||
""};
|
""};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue