Merge pull request #361 from Haseosama/420

Music Beeper By Haseo
This commit is contained in:
RogueMaster
2022-10-13 15:24:51 -04:00
committed by GitHub
16 changed files with 1948 additions and 1 deletions
+3 -1
View File
@@ -3,7 +3,9 @@ App(
name="Basic applications for plug-in menu",
apptype=FlipperAppType.METAPACKAGE,
provides=[
"music_player",
"music_player",
"music_beeper",
"snake_game",
"bt_hid",
],
)
@@ -0,0 +1,12 @@
App(
appid="blackjack",
name="Blackjack",
apptype=FlipperAppType.EXTERNAL,
entry_point="blackjack_app",
cdefines=["APP_BLACKJACK"],
requires=["gui"],
stack_size=1 * 1024,
order=30,
fap_icon="blackjack_10px.png",
fap_category="Games",
)
+372
View File
@@ -0,0 +1,372 @@
#include <gui/gui.h>
#include <stdlib.h>
#include <dolphin/dolphin.h>
#include <math.h>
#include "defines.h"
#include "card.h"
#include "util.h"
#include "ui.h"
#define APP_NAME "Blackjack"
#define STARTING_MONEY 200
#define DEALER_MAX 17
void start_round(GameState *game_state);
static void draw_ui(Canvas *const canvas, const GameState *game_state) {
draw_money(canvas, game_state->player_score);
draw_score(canvas, true, handCount(game_state->player_cards, game_state->player_card_count));
if (!game_state->animating && game_state->state == GameStatePlay) {
draw_play_menu(canvas, game_state);
}
}
static void render_callback(Canvas *const canvas, void *ctx) {
const GameState *game_state = acquire_mutex((ValueMutex *) ctx, 25);
if (game_state == NULL) {
return;
}
canvas_set_color(canvas, ColorBlack);
canvas_draw_frame(canvas, 0, 0, 128, 64);
switch (game_state->state) {
case GameStateStart:
case GameStateGameOver:
draw_message_scene(canvas, game_state);
break;
case GameStatePlay:
draw_player_scene(canvas, game_state);
break;
case GameStateDealer:
draw_dealer_scene(canvas, game_state);
break;
}
if (game_state->state != GameStateStart && game_state->state != GameStateGameOver) {
animateQueue(game_state, canvas);
draw_ui(canvas, game_state);
}
release_mutex((ValueMutex *) ctx, game_state);
}
//region card draw
Card draw_card(GameState *game_state) {
Card c = game_state->deck.cards[game_state->deck.index];
game_state->deck.index++;
return c;
}
char *letters[4] = {"spade", "hearth", "diamond", "club"};
void drawPlayerCard(GameState *game_state) {
Card c = draw_card(game_state);
game_state->player_cards[game_state->player_card_count] = c;
game_state->player_card_count++;
}
void drawDealerCard(GameState *game_state) {
Card c = draw_card(game_state);
game_state->dealer_cards[game_state->dealer_card_count] = c;
game_state->dealer_card_count++;
FURI_LOG_D(APP_NAME, "drawing dealer %s %i", letters[c.pip], c.character + 2);
}
//endregion
//region queue callbacks
void to_lose_state(const GameState *game_state, Canvas *const canvas) {
UNUSED(game_state);
popupFrame(canvas);
elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "You lost");
}
void to_bust_state(const GameState *game_state, Canvas *const canvas) {
UNUSED(game_state);
popupFrame(canvas);
elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Busted!");
}
void to_draw_state(const GameState *game_state, Canvas *const canvas) {
UNUSED(game_state);
popupFrame(canvas);
elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Draw");
}
void to_dealer_turn(const GameState *game_state, Canvas *const canvas) {
UNUSED(game_state);
popupFrame(canvas);
elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Dealers turn");
}
void to_win_state(const GameState *game_state, Canvas *const canvas) {
UNUSED(game_state);
popupFrame(canvas);
elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "You win");
}
void to_start(const GameState *game_state, Canvas *const canvas) {
UNUSED(game_state);
popupFrame(canvas);
elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Round started");
}
void before_start(GameState *gameState) {
gameState->dealer_card_count = 0;
gameState->player_card_count = 0;
}
void start(GameState *game_state) {
start_round(game_state);
}
void draw(GameState *game_state) {
game_state->player_score += game_state->bet;
game_state->bet = 0;
queue(game_state, start, before_start, to_start);
}
void game_over(GameState *game_state) {
game_state->state = GameStateGameOver;
}
void lose(GameState *game_state) {
game_state->state = GameStatePlay;
game_state->bet = 0;
if (game_state->player_score >= ROUND_PRICE)
queue(game_state, start, before_start, to_start);
else
queue(game_state, game_over, NULL, NULL);
}
void win(GameState *game_state) {
game_state->state = GameStatePlay;
game_state->player_score += game_state->bet * 2;
game_state->bet = 0;
queue(game_state, start, before_start, to_start);
}
void dealerTurn(GameState *game_state) {
game_state->state = GameStateDealer;
}
//endregion
void player_tick(GameState *game_state) {
uint8_t score = handCount(game_state->player_cards, game_state->player_card_count);
if ((game_state->doubled && score <= 21) || score == 21) {
queue(game_state, dealerTurn, NULL, NULL);
} else if (score > 21) {
queue(game_state, lose, NULL, to_bust_state);
} else {
if (game_state->selectDirection == DirectionUp && game_state->selectedMenu > 0) {
game_state->selectedMenu--;
}
if (game_state->selectDirection == DirectionDown && game_state->selectedMenu < 2) {
game_state->selectedMenu++;
}
if (game_state->selectDirection == Select) {
//double
if (!game_state->doubled && game_state->selectedMenu == 0 &&
game_state->player_score >= ROUND_PRICE) {
game_state->player_score -= ROUND_PRICE;
game_state->bet += ROUND_PRICE;
game_state->doubled = true;
game_state->selectedMenu = 1;
queue(game_state, drawPlayerCard, NULL, draw_card_animation);
game_state->player_cards[game_state->player_card_count] = game_state->deck.cards[game_state->deck.index];
score = handCount(game_state->player_cards, game_state->player_card_count + 1);
if (score > 21)
queue(game_state, lose, NULL, to_bust_state);
else
queue(game_state, dealerTurn, NULL, to_dealer_turn);
} //hit
else if (game_state->selectedMenu == 1) {
queue(game_state, drawPlayerCard, NULL, draw_card_animation);
} //stay
else if (game_state->selectedMenu == 2) {
queue(game_state, dealerTurn, NULL, to_dealer_turn);
}
}
}
}
void dealer_tick(GameState *game_state) {
uint8_t dealer_score = handCount(game_state->dealer_cards, game_state->dealer_card_count);
uint8_t player_score = handCount(game_state->player_cards, game_state->player_card_count);
if (dealer_score >= DEALER_MAX) {
if (dealer_score > 21 || dealer_score < player_score)
queue(game_state, win, NULL, to_win_state);
else if (dealer_score > player_score)
queue(game_state, lose, NULL, to_lose_state);
else if (dealer_score == player_score)
queue(game_state, draw, NULL, to_draw_state);
} else {
queue(game_state, drawDealerCard, NULL, draw_card_animation);
}
}
void tick(GameState *game_state) {
game_state->last_tick = furi_get_tick();
if (!game_state->started && game_state->state == GameStatePlay) {
game_state->started = true;
drawDealerCard(game_state);
queue(game_state, drawPlayerCard, NULL, draw_card_animation);
queue(game_state, drawDealerCard, NULL, draw_card_animation);
queue(game_state, drawPlayerCard, NULL, draw_card_animation);
}
if (!run_queue(game_state)) {
if (game_state->state == GameStatePlay) {
player_tick(game_state);
} else if (game_state->state == GameStateDealer) {
dealer_tick(game_state);
}
}
game_state->selectDirection = None;
}
void start_round(GameState *game_state) {
game_state->player_card_count = 0;
game_state->dealer_card_count = 0;
game_state->selectedMenu = 0;
game_state->started = false;
game_state->doubled = false;
game_state->animating = true;
game_state->animationStart = 0;
shuffleDeck(&(game_state->deck));
game_state->doubled = false;
game_state->bet = ROUND_PRICE;
if (game_state->player_score < ROUND_PRICE) {
game_state->state = GameStateGameOver;
} else {
game_state->player_score -= ROUND_PRICE;
}
game_state->state = GameStatePlay;
}
void init(GameState *game_state) {
game_state->last_tick = 0;
game_state->player_score = STARTING_MONEY;
generateDeck(&(game_state->deck));
start_round(game_state);
}
static void input_callback(InputEvent *input_event, FuriMessageQueue *event_queue) {
furi_assert(event_queue);
AppEvent event = {.type = EventTypeKey, .input = *input_event};
furi_message_queue_put(event_queue, &event, FuriWaitForever);
}
static void update_timer_callback(FuriMessageQueue *event_queue) {
furi_assert(event_queue);
AppEvent event = {.type = EventTypeTick};
furi_message_queue_put(event_queue, &event, 0);
}
int32_t blackjack_app(void *p) {
UNUSED(p);
int32_t return_code = 0;
FuriMessageQueue *event_queue = furi_message_queue_alloc(8, sizeof(AppEvent));
GameState *game_state = malloc(sizeof(GameState));
init(game_state);
game_state->state = GameStateStart;
ValueMutex state_mutex;
if (!init_mutex(&state_mutex, game_state, sizeof(GameState))) {
FURI_LOG_E(APP_NAME, "cannot create mutex\r\n");
return_code = 255;
goto free_and_exit;
}
ViewPort *view_port = view_port_alloc();
view_port_draw_callback_set(view_port, render_callback, &state_mutex);
view_port_input_callback_set(view_port, input_callback, event_queue);
FuriTimer *timer =
furi_timer_alloc(update_timer_callback, FuriTimerTypePeriodic, event_queue);
furi_timer_start(timer, furi_kernel_get_tick_frequency() / 25);
Gui *gui = furi_record_open("gui");
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
AppEvent event;
for (bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
GameState *game_state = (GameState *) acquire_mutex_block(&state_mutex);
if (event_status == FuriStatusOk) {
if (event.type == EventTypeKey) {
if (event.input.type == InputTypePress) {
switch (event.input.key) {
case InputKeyUp:
game_state->selectDirection = DirectionUp;
break;
case InputKeyDown:
game_state->selectDirection = DirectionDown;
break;
case InputKeyRight:
game_state->selectDirection = DirectionRight;
break;
case InputKeyLeft:
game_state->selectDirection = DirectionLeft;
break;
case InputKeyBack:
processing = false;
break;
case InputKeyOk:
if (game_state->state == GameStateGameOver || game_state->state == GameStateStart) {
init(game_state);
} else {
game_state->selectDirection = Select;
}
break;
}
}
} else if (event.type == EventTypeTick) {
tick(game_state);
}
} else {
FURI_LOG_D(APP_NAME, "osMessageQueue: event timeout");
// event timeout
}
view_port_update(view_port);
release_mutex(&state_mutex, game_state);
}
furi_timer_free(timer);
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
furi_record_close(RECORD_GUI);
view_port_free(view_port);
delete_mutex(&state_mutex);
free_and_exit:
queue_clear();
free(game_state);
furi_message_queue_free(event_queue);
return return_code;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

+239
View File
@@ -0,0 +1,239 @@
#include "card.h"
#include <math.h>
//region CardDesign
bool pips[4][49] =
{
{
//spades
0, 0, 0, 1, 0, 0, 0,
0, 0, 1, 1, 1, 0, 0,
0, 1, 1, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1,
1, 1, 0, 1, 0, 1, 1,
0, 0, 0, 1, 0, 0, 0,
0, 0, 1, 1, 1, 0, 0
},
{
//hearts
0, 1, 0, 0, 0, 1, 0,
1, 1, 1, 0, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1, 0,
0, 0, 1, 1, 1, 0, 0,
0, 0, 0, 1, 0, 0, 0,
},
{
//diamonds
0, 0, 0, 1, 0, 0, 0,
0, 0, 1, 1, 1, 0, 0,
0, 1, 1, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1, 0,
0, 0, 1, 1, 1, 0, 0,
0, 0, 0, 1, 0, 0, 0
},
{
//clubs
0, 0, 1, 1, 1, 0, 0,
0, 0, 1, 1, 1, 0, 0,
1, 1, 0, 1, 0, 1, 1,
1, 1, 1, 1, 1, 1, 1,
1, 1, 0, 1, 0, 1, 1,
0, 0, 0, 1, 0, 0, 0,
0, 0, 1, 1, 1, 0, 0
}
};
bool backDesign[4] = {
0, 1,
1, 0
};
//endregion
uint8_t characters[13] =
{
2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K', 'A'
};
//region Player card positions
uint8_t playerCardPositions[22][4] = {
//first row
{108, 38, 0, 0},
{98, 38, 0, 1},
{88, 38, 0, 1},
{78, 38, 0, 1},
{68, 38, 0, 1},
{58, 38, 0, 1},
{48, 38, 0, 1},
{38, 38, 0, 1},
//second row
{104, 26, 1, 0},
{94, 26, 1, 1},
{84, 26, 1, 1},
{74, 26, 1, 1},
{64, 26, 1, 1},
{54, 26, 1, 1},
{44, 26, 1, 1},
//third row
{99, 14, 1, 0},
{89, 14, 1, 1},
{79, 14, 1, 1},
{69, 14, 1, 1},
{59, 14, 1, 1},
{49, 14, 1, 1},
};
//endregion
void drawPlayerDeck(const Card cards[21], uint8_t count, Canvas *const canvas) {
for (uint8_t i = 0; i < count; i++) {
CardState state = Normal;
if (playerCardPositions[i][2] == 1 && playerCardPositions[i][3] == 1)
state = BottomAndRightCut;
else if (playerCardPositions[i][3] == 1)
state = RightCut;
else if (playerCardPositions[i][2] == 1)
state = BottomCut;
drawCardAt(playerCardPositions[i][0], playerCardPositions[i][1], cards[i].pip, cards[i].character, state,
canvas);
}
}
void drawCardAt(uint8_t pos_x, uint8_t pos_y, uint8_t pip, uint8_t character, CardState state, Canvas *const canvas) {
if (state == Normal) {
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, pos_x, pos_y, CARD_WIDHT, CARD_HEIGHT);
canvas_set_color(canvas, ColorBlack);
canvas_draw_frame(canvas, pos_x, pos_y, CARD_WIDHT, CARD_HEIGHT);
} else {
if (state == BottomCut || state == BottomAndRightCut)
canvas_draw_line(canvas, pos_x, pos_y, pos_x, pos_y + CARD_HALF_HEIGHT - 1); //half height line
if (state == BottomCut) {
canvas_draw_line(canvas, pos_x, pos_y, pos_x + CARD_WIDHT - 1, pos_y); //full width line
canvas_draw_line(canvas, pos_x + CARD_WIDHT - 1, pos_y, pos_x + CARD_WIDHT - 1,
pos_y + CARD_HALF_HEIGHT - 1); //half height line
}
if (state == BottomAndRightCut) {
canvas_draw_line(canvas, pos_x, pos_y, pos_x + CARD_HALF_WIDHT - 1, pos_y); //half width
}
if (state == RightCut) {
canvas_draw_line(canvas, pos_x, pos_y, pos_x + CARD_HALF_WIDHT - 1, pos_y); //half width
canvas_draw_line(canvas, pos_x, pos_y, pos_x, pos_y + CARD_HEIGHT - 1); //full height line
canvas_draw_line(canvas, pos_x, pos_y + CARD_HEIGHT - 1, pos_x + CARD_HALF_WIDHT - 1,
pos_y + CARD_HEIGHT - 1); //full height line
}
}
uint8_t left = pos_x + CORNER_MARGIN;
uint8_t right = (pos_x + CARD_WIDHT - CORNER_MARGIN - 7);
uint8_t top = pos_y + CORNER_MARGIN;
uint8_t bottom = (pos_y + CARD_HEIGHT - CORNER_MARGIN - 7);
for (uint8_t x = 0; x < 7; x++) {
for (uint8_t y = 0; y < 7; y++) {
if (pips[pip][x + y * 7]) {
if (state == Normal || state == BottomCut)
canvas_draw_dot(canvas, right + x + 1, top + y);
if (state == Normal || state == RightCut)
canvas_draw_dot(canvas, left + x - 1, bottom + y);
}
}
}
canvas_set_font(canvas, FontSecondary);
char drawChar[3];
if (character < 9)
snprintf(drawChar, sizeof(drawChar), "%i", character + 2);
else {
snprintf(drawChar, sizeof(drawChar), "%c", characters[character]);
}
canvas_set_font_direction(canvas, CanvasDirectionLeftToRight);
canvas_draw_str_aligned(canvas, left + 2, top + 3, AlignCenter, AlignCenter, drawChar);
canvas_set_font_direction(canvas, CanvasDirectionRightToLeft);
if (state == Normal) { //flipper crashes on non center aligned text when upside down
uint8_t margin = 9;
if (character == 8) //10 needs bigger margin
margin = 12;
canvas_draw_str_aligned(canvas, right + margin, bottom - 3, AlignCenter, AlignCenter, drawChar);
}
canvas_set_font_direction(canvas, CanvasDirectionLeftToRight);
//canvas_draw_str(canvas, left, top, drawChar );
}
void drawCardBackAt(uint8_t pos_x, uint8_t pos_y, Canvas *const canvas) {
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, pos_x, pos_y, CARD_WIDHT, CARD_HEIGHT);
canvas_set_color(canvas, ColorBlack);
canvas_draw_frame(canvas, pos_x, pos_y, CARD_WIDHT, CARD_HEIGHT);
for (uint8_t x = 0; x < CARD_WIDHT - 2; x++) {
for (uint8_t y = 0; y < CARD_HEIGHT - 2; y++) {
uint8_t _x = x;
uint8_t _y = y * 2;
if (backDesign[(_x + _y) % 4]) {
canvas_draw_dot(canvas, pos_x + x + 1, pos_y + y + 1);
}
}
}
}
void generateDeck(Deck *deck_ptr) {
uint16_t counter = 0;
for (uint8_t deck = 0; deck < DECK_COUNT; deck++) {
for (uint8_t pip = 0; pip < 4; pip++) {
for (uint8_t label = 0; label < 13; label++) {
deck_ptr->cards[counter] = (Card)
{
pip, label
};
counter++;
}
}
}
}
void shuffleDeck(Deck *deck_ptr) {
srand(DWT->CYCCNT);
deck_ptr->index = 0;
int max = DECK_COUNT * 52;
for (int i = 0; i < max; i++) {
int r = i + (rand() % (max - i));
Card tmp = deck_ptr->cards[i];
deck_ptr->cards[i] = deck_ptr->cards[r];
deck_ptr->cards[r] = tmp;
}
}
uint8_t handCount(const Card cards[21], uint8_t count) {
uint8_t aceCount = 0;
uint8_t score = 0;
for (uint8_t i = 0; i < count; i++) {
if (cards[i].character == 12)
aceCount++;
else {
if (cards[i].character > 8)
score += 10;
else
score += cards[i].character + 2;
}
}
for (uint8_t i = 0; i < aceCount; i++) {
if ((score + 11) <= 21) score += 11;
else score++;
}
return score;
}
+40
View File
@@ -0,0 +1,40 @@
#ifndef _card_h
#define _card_h
#include <gui/gui.h>
#define DECK_COUNT 6
#define CARD_HEIGHT 24
#define CARD_HALF_HEIGHT CARD_HEIGHT/2
#define CARD_WIDHT 18
#define CARD_HALF_WIDHT CARD_WIDHT/2
#define CORNER_MARGIN 3
#define LEGEND_SIZE 10
typedef enum {
Normal, BottomCut, RightCut, BottomAndRightCut, TopCut, LeftCut, TopAndLeftCut
} CardState;
typedef struct {
uint8_t pip;
uint8_t character;
} Card;
typedef struct {
Card cards[52 * DECK_COUNT];
int index;
} Deck;
void drawPlayerDeck(const Card cards[21], uint8_t count, Canvas *const canvas);
void drawCardAt(uint8_t pos_x, uint8_t pos_y, uint8_t pip, uint8_t character, CardState state, Canvas *const canvas);
void drawCardBackAt(uint8_t pos_x, uint8_t pos_y, Canvas *const canvas);
void generateDeck(Deck *deck_ptr);
void shuffleDeck(Deck *deck_ptr);
uint8_t handCount(const Card cards[21], uint8_t count);
#endif
+61
View File
@@ -0,0 +1,61 @@
#pragma once
#include <furi.h>
#include <input/input.h>
#include <gui/elements.h>
#include "card.h"
#define ANIMATION_TIME furi_ms_to_ticks(1500)
#define ANIMATION_END_MARGIN furi_ms_to_ticks(200)
#define ROUND_PRICE 10
typedef enum {
EventTypeTick,
EventTypeKey,
} EventType;
typedef struct {
EventType type;
InputEvent input;
} AppEvent;
typedef enum {
GameStateGameOver,
GameStateStart,
GameStatePlay,
GameStateDealer,
} PlayState;
typedef enum {
DirectionUp,
DirectionRight,
DirectionDown,
DirectionLeft,
Select,
None
} Direction;
typedef struct {
Card player_cards[21];
Card dealer_cards[21];
uint8_t player_card_count;
uint8_t dealer_card_count;
Direction selectDirection;
uint32_t player_score;
uint8_t multiplier;
uint32_t bet;
uint8_t player_time;
bool doubled;
bool animating;
bool started;
uint8_t selectedMenu;
Deck deck;
PlayState state;
unsigned int last_tick;
unsigned int animationStart;
bool dealer_animating;
unsigned int delay_tick;
} GameState;
+127
View File
@@ -0,0 +1,127 @@
#include "ui.h"
#include "card.h"
#include <math.h>
#include "util.h"
const char MoneyMul[4] = {
'K', 'B', 'T', 'S'
};
void draw_player_scene(Canvas *const canvas, const GameState *game_state) {
int max_card = game_state->player_card_count;
if (max_card > 0)
drawPlayerDeck((game_state->player_cards), max_card, canvas);
drawCardBackAt(13, 5, canvas);
max_card = game_state->dealer_card_count;
if (max_card > 1) {
drawCardAt(2, 2, game_state->dealer_cards[1].pip, game_state->dealer_cards[1].character, Normal,
canvas);
}
}
void draw_dealer_scene(Canvas *const canvas, const GameState *game_state) {
uint8_t max_card = game_state->dealer_card_count;
drawPlayerDeck((game_state->dealer_cards), max_card, canvas);
}
void draw_card_animation(const GameState *game_state, Canvas *const canvas) {
float t = (float) (furi_get_tick() - game_state->animationStart) / (ANIMATION_TIME - ANIMATION_END_MARGIN);
t *= 2;
Card animatingCard = game_state->deck.cards[game_state->deck.index];
if (t > 1) {
int cardY = round(lerp(-CARD_HEIGHT, 10, 1));
drawCardAt(64 - CARD_HALF_WIDHT, cardY, animatingCard.pip,
animatingCard.character, Normal, canvas);
} else {
int cardY = round(lerp(-CARD_HEIGHT, 10, t));
drawCardAt(64 - CARD_HALF_WIDHT, cardY, animatingCard.pip,
animatingCard.character, Normal, canvas);
// drawCardBackAt(64 - CARD_HALF_WIDHT, cardY, canvas);
}
}
void popupFrame(Canvas *const canvas) {
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, 32, 15, 66, 13);
canvas_set_color(canvas, ColorBlack);
canvas_draw_frame(canvas, 32, 15, 66, 13);
canvas_set_font(canvas, FontSecondary);
}
void draw_message_scene(Canvas *const canvas, const GameState *game_state) {
switch (game_state->state) {
case GameStateStart:
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, 64, 5, AlignCenter, AlignTop, "Blackjack");
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 64, 24, AlignCenter, AlignTop, "Made by Doofy");
elements_multiline_text_aligned(canvas, 64, 38, AlignCenter, AlignTop, "Press center button\nto start");
break;
case GameStateGameOver:
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, 64, 5, AlignCenter, AlignTop, "Game Over");
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 64, 24, AlignCenter, AlignTop, "Press center button\nto start");
break;
default:
break;
}
}
void draw_play_menu(Canvas *const canvas, const GameState *game_state) {
const char *menus[3] = {"Double", "Hit", "Stay"};
for (uint8_t m = 0; m < 3; m++) {
if (m == 0 && (game_state->doubled || game_state->player_score < ROUND_PRICE)) continue;
int y = m * 13 + 25;
canvas_set_color(canvas, ColorBlack);
if (game_state->selectedMenu == m) {
canvas_set_color(canvas, ColorBlack);
canvas_draw_box(canvas, 1, y, 31, 12);
} else {
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, 1, y, 31, 12);
canvas_set_color(canvas, ColorBlack);
canvas_draw_frame(canvas, 1, y, 31, 12);
}
if (game_state->selectedMenu == m)
canvas_set_color(canvas, ColorWhite);
else
canvas_set_color(canvas, ColorBlack);
canvas_draw_str_aligned(canvas, 16, y + 6, AlignCenter, AlignCenter, menus[m]);
}
}
void draw_score(Canvas *const canvas, bool top, uint8_t amount) {
char drawChar[20];
snprintf(drawChar, sizeof(drawChar), "Player score: %i", amount);
if (top)
canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, drawChar);
else
canvas_draw_str_aligned(canvas, 64, 62, AlignCenter, AlignBottom, drawChar);
}
void draw_money(Canvas *const canvas, uint32_t score) {
canvas_set_font(canvas, FontSecondary);
char drawChar[10];
uint32_t currAmount = score;
if (currAmount < 1000) {
snprintf(drawChar, sizeof(drawChar), "$%lu", currAmount);
} else {
char c = 'K';
for (uint8_t i = 0; i < 4; i++) {
currAmount = currAmount / 1000;
if (currAmount < 1000) {
c = MoneyMul[i];
break;
}
}
snprintf(drawChar, sizeof(drawChar), "$%lu %c", currAmount, c);
}
canvas_draw_str_aligned(canvas, 126, 2, AlignRight, AlignTop, drawChar);
}
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include "defines.h"
#include <gui/gui.h>
void draw_player_scene(Canvas *const canvas, const GameState *game_state);
void draw_dealer_scene(Canvas *const canvas, const GameState *game_state);
void draw_message_scene(Canvas *const canvas, const GameState *game_state);
void draw_play_menu(Canvas *const canvas, const GameState *game_state);
void draw_score(Canvas *const canvas, bool top, uint8_t amount);
void draw_money(Canvas *const canvas, uint32_t score);
void draw_card_animation(const GameState *game_state, Canvas *const canvas);
void popupFrame(Canvas *const canvas);
+65
View File
@@ -0,0 +1,65 @@
#include "util.h"
static List *afterDelay;
float lerp(float v0, float v1, float t) {
return (1 - t) * v0 + t * v1;
}
void queue(GameState *game_state,
void (*callback)(GameState *game_state),
void (*start)(GameState *game_state),
void (*processing)(const GameState *gameState, Canvas *const canvas)
) {
if (afterDelay == NULL) {
game_state->animationStart = game_state->last_tick;
afterDelay = malloc(sizeof(List));
afterDelay->callback = callback;
afterDelay->processing = processing;
afterDelay->start = start;
afterDelay->next = NULL;
} else {
List *next = afterDelay;
while (next->next != NULL) { next = (List *) (next->next); }
next->next = malloc(sizeof(List));
((List *) next->next)->callback = callback;
((List *) next->next)->processing = processing;
((List *) next->next)->start = start;
((List *) next->next)->next = NULL;
}
}
void queue_clear() {
while (afterDelay != NULL) {
List *f = afterDelay;
free(f);
afterDelay = f->next;
}
}
void dequeue(GameState *game_state) {
((List *) afterDelay)->callback(game_state);
List *f = afterDelay;
afterDelay = (List *) afterDelay->next;
free(f);
if (afterDelay != NULL && afterDelay->start != NULL)afterDelay->start(game_state);
game_state->animationStart = game_state->last_tick;
}
void animateQueue(const GameState *game_state, Canvas *const canvas) {
if (afterDelay != NULL && ((List *) afterDelay)->processing != NULL) {
((List *) afterDelay)->processing(game_state, canvas);
}
}
bool run_queue(GameState *game_state) {
if (afterDelay != NULL) {
game_state->animating = true;
if ((game_state->last_tick - game_state->animationStart) > ANIMATION_TIME) {
dequeue(game_state);
}
return true;
}
game_state->animating = false;
return false;
}
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include "defines.h"
typedef struct{
void (*callback)(GameState *game_state);
void (*processing)(const GameState *game_state, Canvas *const canvas);
void (*start)(GameState *game_state);
void *next;
} List;
float lerp(float v0, float v1, float t);
void queue(GameState *game_state,
void (*callback)(GameState *game_state),
void (*start)(GameState *game_state),
void (*processing)(const GameState *gameState, Canvas *const canvas));
bool run_queue(GameState *gameState);
void animateQueue(const GameState *gameState, Canvas *const canvas);
void queue_clear();
@@ -0,0 +1,24 @@
App(
appid="Music_Beeper",
name="Music Beeper",
apptype=FlipperAppType.EXTERNAL,
entry_point="music_beeper_app",
cdefines=["APP_MUSIC_BEEPER"],
requires=[
"gui",
"dialogs",
],
provides=["music_beeper_start"],
stack_size=2 * 1024,
order=45,
fap_icon="../../../assets/icons/Archive/music_10px.png",
fap_category="Music",
)
App(
appid="music_beeper_start",
apptype=FlipperAppType.STARTUP,
entry_point="music_beeper_on_system_start",
requires=["music_beeper"],
order=30,
)
@@ -0,0 +1,367 @@
#include "music_beeper_worker.h"
#include <furi.h>
#include <furi_hal.h>
#include <assets_icons.h>
#include <gui/gui.h>
#include <dialogs/dialogs.h>
#include <storage/storage.h>
#define TAG "MusicBeeper"
#define MUSIC_BEEPER_APP_PATH_FOLDER ANY_PATH("music_beeper")
#define MUSIC_BEEPER_APP_EXTENSION "*"
#define MUSIC_BEEPER_SEMITONE_HISTORY_SIZE 4
typedef struct {
uint8_t semitone_history[MUSIC_BEEPER_SEMITONE_HISTORY_SIZE];
uint8_t duration_history[MUSIC_BEEPER_SEMITONE_HISTORY_SIZE];
uint8_t volume;
uint8_t semitone;
uint8_t dots;
uint8_t duration;
float position;
} MusicBeeperModel;
typedef struct {
MusicBeeperModel* model;
FuriMutex** model_mutex;
FuriMessageQueue* input_queue;
ViewPort* view_port;
Gui* gui;
MusicBeeperWorker* worker;
} MusicBeeper;
static const float MUSIC_BEEPER_VOLUMES[] = {0, .25, .5, .75, 1};
static const char* semitone_to_note(int8_t semitone) {
switch(semitone) {
case 0:
return "C";
case 1:
return "C#";
case 2:
return "D";
case 3:
return "D#";
case 4:
return "E";
case 5:
return "F";
case 6:
return "F#";
case 7:
return "G";
case 8:
return "G#";
case 9:
return "A";
case 10:
return "A#";
case 11:
return "B";
default:
return "--";
}
}
static bool is_white_note(uint8_t semitone, uint8_t id) {
switch(semitone) {
case 0:
if(id == 0) return true;
break;
case 2:
if(id == 1) return true;
break;
case 4:
if(id == 2) return true;
break;
case 5:
if(id == 3) return true;
break;
case 7:
if(id == 4) return true;
break;
case 9:
if(id == 5) return true;
break;
case 11:
if(id == 6) return true;
break;
default:
break;
}
return false;
}
static bool is_black_note(uint8_t semitone, uint8_t id) {
switch(semitone) {
case 1:
if(id == 0) return true;
break;
case 3:
if(id == 1) return true;
break;
case 6:
if(id == 3) return true;
break;
case 8:
if(id == 4) return true;
break;
case 10:
if(id == 5) return true;
break;
default:
break;
}
return false;
}
static void render_callback(Canvas* canvas, void* ctx) {
MusicBeeper* music_beeper = ctx;
furi_check(furi_mutex_acquire(music_beeper->model_mutex, FuriWaitForever) == FuriStatusOk);
canvas_clear(canvas);
canvas_set_color(canvas, ColorBlack);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 0, 12, "MusicBeeper");
uint8_t x_pos = 0;
uint8_t y_pos = 24;
const uint8_t white_w = 10;
const uint8_t white_h = 40;
const int8_t black_x = 6;
const int8_t black_y = -5;
const uint8_t black_w = 8;
const uint8_t black_h = 32;
// white keys
for(size_t i = 0; i < 7; i++) {
if(is_white_note(music_beeper->model->semitone, i)) {
canvas_draw_box(canvas, x_pos + white_w * i, y_pos, white_w + 1, white_h);
} else {
canvas_draw_frame(canvas, x_pos + white_w * i, y_pos, white_w + 1, white_h);
}
}
// black keys
for(size_t i = 0; i < 7; i++) {
if(i != 2 && i != 6) {
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(
canvas, x_pos + white_w * i + black_x, y_pos + black_y, black_w + 1, black_h);
canvas_set_color(canvas, ColorBlack);
if(is_black_note(music_beeper->model->semitone, i)) {
canvas_draw_box(
canvas, x_pos + white_w * i + black_x, y_pos + black_y, black_w + 1, black_h);
} else {
canvas_draw_frame(
canvas, x_pos + white_w * i + black_x, y_pos + black_y, black_w + 1, black_h);
}
}
}
// volume view_port
x_pos = 124;
y_pos = 0;
const uint8_t volume_h =
(64 / (COUNT_OF(MUSIC_BEEPER_VOLUMES) - 1)) * music_beeper->model->volume;
canvas_draw_frame(canvas, x_pos, y_pos, 4, 64);
canvas_draw_box(canvas, x_pos, y_pos + (64 - volume_h), 4, volume_h);
// note stack view_port
x_pos = 73;
y_pos = 0;
canvas_set_color(canvas, ColorBlack);
canvas_set_font(canvas, FontPrimary);
canvas_draw_frame(canvas, x_pos, y_pos, 49, 64);
canvas_draw_line(canvas, x_pos + 28, 0, x_pos + 28, 64);
char duration_text[16];
for(uint8_t i = 0; i < MUSIC_BEEPER_SEMITONE_HISTORY_SIZE; i++) {
if(music_beeper->model->duration_history[i] == 0xFF) {
snprintf(duration_text, 15, "--");
} else {
snprintf(duration_text, 15, "%d", music_beeper->model->duration_history[i]);
}
if(i == 0) {
canvas_draw_box(canvas, x_pos, y_pos + 48, 49, 16);
canvas_set_color(canvas, ColorWhite);
} else {
canvas_set_color(canvas, ColorBlack);
}
canvas_draw_str(
canvas,
x_pos + 4,
64 - 16 * i - 3,
semitone_to_note(music_beeper->model->semitone_history[i]));
canvas_draw_str(canvas, x_pos + 31, 64 - 16 * i - 3, duration_text);
canvas_draw_line(canvas, x_pos, 64 - 16 * i, x_pos + 48, 64 - 16 * i);
}
furi_mutex_release(music_beeper->model_mutex);
}
static void input_callback(InputEvent* input_event, void* ctx) {
MusicBeeper* music_beeper = ctx;
if(input_event->type == InputTypeShort) {
furi_message_queue_put(music_beeper->input_queue, input_event, 0);
}
}
static void music_beeper_worker_callback(
uint8_t semitone,
uint8_t dots,
uint8_t duration,
float position,
void* context) {
MusicBeeper* music_beeper = context;
furi_check(furi_mutex_acquire(music_beeper->model_mutex, FuriWaitForever) == FuriStatusOk);
for(size_t i = 0; i < MUSIC_BEEPER_SEMITONE_HISTORY_SIZE - 1; i++) {
size_t r = MUSIC_BEEPER_SEMITONE_HISTORY_SIZE - 1 - i;
music_beeper->model->duration_history[r] = music_beeper->model->duration_history[r - 1];
music_beeper->model->semitone_history[r] = music_beeper->model->semitone_history[r - 1];
}
semitone = (semitone == 0xFF) ? 0xFF : semitone % 12;
music_beeper->model->semitone = semitone;
music_beeper->model->dots = dots;
music_beeper->model->duration = duration;
music_beeper->model->position = position;
music_beeper->model->semitone_history[0] = semitone;
music_beeper->model->duration_history[0] = duration;
furi_mutex_release(music_beeper->model_mutex);
view_port_update(music_beeper->view_port);
}
void music_beeper_clear(MusicBeeper* instance) {
memset(instance->model->duration_history, 0xff, MUSIC_BEEPER_SEMITONE_HISTORY_SIZE);
memset(instance->model->semitone_history, 0xff, MUSIC_BEEPER_SEMITONE_HISTORY_SIZE);
music_beeper_worker_clear(instance->worker);
}
MusicBeeper* music_beeper_alloc() {
MusicBeeper* instance = malloc(sizeof(MusicBeeper));
instance->model = malloc(sizeof(MusicBeeperModel));
instance->model->volume = 4;
instance->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
instance->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
instance->worker = music_beeper_worker_alloc();
music_beeper_worker_set_volume(
instance->worker, MUSIC_BEEPER_VOLUMES[instance->model->volume]);
music_beeper_worker_set_callback(instance->worker, music_beeper_worker_callback, instance);
music_beeper_clear(instance);
instance->view_port = view_port_alloc();
view_port_draw_callback_set(instance->view_port, render_callback, instance);
view_port_input_callback_set(instance->view_port, input_callback, instance);
// Open GUI and register view_port
instance->gui = furi_record_open(RECORD_GUI);
gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen);
return instance;
}
void music_beeper_free(MusicBeeper* instance) {
gui_remove_view_port(instance->gui, instance->view_port);
furi_record_close(RECORD_GUI);
view_port_free(instance->view_port);
music_beeper_worker_free(instance->worker);
furi_message_queue_free(instance->input_queue);
furi_mutex_free(instance->model_mutex);
free(instance->model);
free(instance);
}
int32_t music_beeper_app(void* p) {
MusicBeeper* music_beeper = music_beeper_alloc();
FuriString* file_path;
file_path = furi_string_alloc();
do {
if(p && strlen(p)) {
furi_string_set(file_path, (const char*)p);
} else {
furi_string_set(file_path, MUSIC_BEEPER_APP_PATH_FOLDER);
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(
&browser_options, MUSIC_BEEPER_APP_EXTENSION, &I_music_10px);
browser_options.hide_ext = false;
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options);
furi_record_close(RECORD_DIALOGS);
if(!res) {
FURI_LOG_E(TAG, "No file selected");
break;
}
}
if(!music_beeper_worker_load(music_beeper->worker, furi_string_get_cstr(file_path))) {
FURI_LOG_E(TAG, "Unable to load file");
break;
}
music_beeper_worker_start(music_beeper->worker);
InputEvent input;
while(furi_message_queue_get(music_beeper->input_queue, &input, FuriWaitForever) ==
FuriStatusOk) {
furi_check(
furi_mutex_acquire(music_beeper->model_mutex, FuriWaitForever) == FuriStatusOk);
if(input.key == InputKeyBack) {
furi_mutex_release(music_beeper->model_mutex);
break;
} else if(input.key == InputKeyUp) {
if(music_beeper->model->volume < COUNT_OF(MUSIC_BEEPER_VOLUMES) - 1)
music_beeper->model->volume++;
music_beeper_worker_set_volume(
music_beeper->worker, MUSIC_BEEPER_VOLUMES[music_beeper->model->volume]);
} else if(input.key == InputKeyDown) {
if(music_beeper->model->volume > 0) music_beeper->model->volume--;
music_beeper_worker_set_volume(
music_beeper->worker, MUSIC_BEEPER_VOLUMES[music_beeper->model->volume]);
}
furi_mutex_release(music_beeper->model_mutex);
view_port_update(music_beeper->view_port);
}
music_beeper_worker_stop(music_beeper->worker);
if(p && strlen(p)) break; // Exit instead of going to browser if launched with arg
music_beeper_clear(music_beeper);
} while(1);
furi_string_free(file_path);
music_beeper_free(music_beeper);
return 0;
}
@@ -0,0 +1,48 @@
#include <furi.h>
#include <cli/cli.h>
#include <storage/storage.h>
#include "music_beeper_worker.h"
static void music_beeper_cli(Cli* cli, FuriString* args, void* context) {
UNUSED(context);
MusicBeeperWorker* music_beeper_worker = music_beeper_worker_alloc();
Storage* storage = furi_record_open(RECORD_STORAGE);
do {
if(storage_common_stat(storage, furi_string_get_cstr(args), NULL) == FSE_OK) {
if(!music_beeper_worker_load(music_beeper_worker, furi_string_get_cstr(args))) {
printf("Failed to open file %s\r\n", furi_string_get_cstr(args));
break;
}
} else {
if(!music_beeper_worker_load_rtttl_from_string(
music_beeper_worker, furi_string_get_cstr(args))) {
printf("Argument is not a file or RTTTL\r\n");
break;
}
}
printf("Press CTRL+C to stop\r\n");
music_beeper_worker_set_volume(music_beeper_worker, 1.0f);
music_beeper_worker_start(music_beeper_worker);
while(!cli_cmd_interrupt_received(cli)) {
furi_delay_ms(50);
}
music_beeper_worker_stop(music_beeper_worker);
} while(0);
furi_record_close(RECORD_STORAGE);
music_beeper_worker_free(music_beeper_worker);
}
void music_beeper_on_system_start() {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "music_beeper", CliCommandFlagDefault, music_beeper_cli, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(music_beeper_cli);
#endif
}
@@ -0,0 +1,506 @@
#include "music_beeper_worker.h"
#include <furi_hal.h>
#include <furi.h>
#include <storage/storage.h>
#include <lib/flipper_format/flipper_format.h>
#include <m-array.h>
#define TAG "MusicBeeperWorker"
#define MUSIC_BEEPER_FILETYPE "Flipper Music Format"
#define MUSIC_BEEPER_VERSION 0
#define SEMITONE_PAUSE 0xFF
#define NOTE_C4 261.63f
#define NOTE_C4_SEMITONE (4.0f * 12.0f)
#define TWO_POW_TWELTH_ROOT 1.059463094359f
typedef struct {
uint8_t semitone;
uint8_t duration;
uint8_t dots;
} NoteBlock;
ARRAY_DEF(NoteBlockArray, NoteBlock, M_POD_OPLIST);
struct MusicBeeperWorker {
FuriThread* thread;
bool should_work;
MusicBeeperWorkerCallback callback;
void* callback_context;
float volume;
uint32_t bpm;
uint32_t duration;
uint32_t octave;
NoteBlockArray_t notes;
};
static int32_t music_beeper_worker_thread_callback(void* context) {
furi_assert(context);
MusicBeeperWorker* instance = context;
NoteBlockArray_it_t it;
NoteBlockArray_it(it, instance->notes);
while(instance->should_work) {
if(NoteBlockArray_end_p(it)) {
NoteBlockArray_it(it, instance->notes);
furi_delay_ms(10);
} else {
NoteBlock* note_block = NoteBlockArray_ref(it);
float note_from_a4 = (float)note_block->semitone - NOTE_C4_SEMITONE;
float frequency = NOTE_C4 * powf(TWO_POW_TWELTH_ROOT, note_from_a4);
float duration =
60.0 * furi_kernel_get_tick_frequency() * 4 / instance->bpm / note_block->duration;
uint32_t dots = note_block->dots;
while(dots > 0) {
duration += duration / 2;
dots--;
}
uint32_t next_tick = furi_get_tick() + duration;
float volume = instance->volume;
if(instance->callback) {
instance->callback(
note_block->semitone,
note_block->dots,
note_block->duration,
0.0,
instance->callback_context);
}
furi_hal_speaker_stop();
furi_hal_speaker_start(frequency, volume);
while(instance->should_work && furi_get_tick() < next_tick) {
volume *= 1;
furi_hal_speaker_set_volume(volume);
furi_delay_ms(2);
}
NoteBlockArray_next(it);
}
}
furi_hal_speaker_stop();
return 0;
}
MusicBeeperWorker* music_beeper_worker_alloc() {
MusicBeeperWorker* instance = malloc(sizeof(MusicBeeperWorker));
NoteBlockArray_init(instance->notes);
instance->thread = furi_thread_alloc();
furi_thread_set_name(instance->thread, "MusicBeeperWorker");
furi_thread_set_stack_size(instance->thread, 1024);
furi_thread_set_context(instance->thread, instance);
furi_thread_set_callback(instance->thread, music_beeper_worker_thread_callback);
instance->volume = 1.0f;
return instance;
}
void music_beeper_worker_clear(MusicBeeperWorker* instance) {
NoteBlockArray_reset(instance->notes);
}
void music_beeper_worker_free(MusicBeeperWorker* instance) {
furi_assert(instance);
furi_thread_free(instance->thread);
NoteBlockArray_clear(instance->notes);
free(instance);
}
static bool is_digit(const char c) {
return isdigit(c) != 0;
}
static bool is_letter(const char c) {
return islower(c) != 0 || isupper(c) != 0;
}
static bool is_space(const char c) {
return c == ' ' || c == '\t';
}
static size_t extract_number(const char* string, uint32_t* number) {
size_t ret = 0;
*number = 0;
while(is_digit(*string)) {
*number *= 10;
*number += (*string - '0');
string++;
ret++;
}
return ret;
}
static size_t extract_dots(const char* string, uint32_t* number) {
size_t ret = 0;
*number = 0;
while(*string == '.') {
*number += 1;
string++;
ret++;
}
return ret;
}
static size_t extract_char(const char* string, char* symbol) {
if(is_letter(*string)) {
*symbol = *string;
return 1;
} else {
return 0;
}
}
static size_t extract_sharp(const char* string, char* symbol) {
if(*string == '#' || *string == '_') {
*symbol = '#';
return 1;
} else {
return 0;
}
}
static size_t skip_till(const char* string, const char symbol) {
size_t ret = 0;
while(*string != '\0' && *string != symbol) {
string++;
ret++;
}
if(*string != symbol) {
ret = 0;
}
return ret;
}
static bool music_beeper_worker_add_note(
MusicBeeperWorker* instance,
uint8_t semitone,
uint8_t duration,
uint8_t dots) {
NoteBlock note_block;
note_block.semitone = semitone;
note_block.duration = duration;
note_block.dots = dots;
NoteBlockArray_push_back(instance->notes, note_block);
return true;
}
static int8_t note_to_semitone(const char note) {
switch(note) {
case 'C':
return 0;
// C#
case 'D':
return 2;
// D#
case 'E':
return 4;
case 'F':
return 5;
// F#
case 'G':
return 7;
// G#
case 'A':
return 9;
// A#
case 'B':
return 11;
default:
return 0;
}
}
static bool music_beeper_worker_parse_notes(MusicBeeperWorker* instance, const char* string) {
const char* cursor = string;
bool result = true;
while(*cursor != '\0') {
if(!is_space(*cursor)) {
uint32_t duration = 0;
char note_char = '\0';
char sharp_char = '\0';
uint32_t octave = 0;
uint32_t dots = 0;
// Parsing
cursor += extract_number(cursor, &duration);
cursor += extract_char(cursor, &note_char);
cursor += extract_sharp(cursor, &sharp_char);
cursor += extract_number(cursor, &octave);
cursor += extract_dots(cursor, &dots);
// Post processing
note_char = toupper(note_char);
if(!duration) {
duration = instance->duration;
}
if(!octave) {
octave = instance->octave;
}
// Validation
bool is_valid = true;
is_valid &= (duration >= 1 && duration <= 128);
is_valid &= ((note_char >= 'A' && note_char <= 'G') || note_char == 'P');
is_valid &= (sharp_char == '#' || sharp_char == '\0');
is_valid &= (octave <= 16);
is_valid &= (dots <= 16);
if(!is_valid) {
FURI_LOG_E(
TAG,
"Invalid note: %lu%c%c%lu.%lu",
duration,
note_char == '\0' ? '_' : note_char,
sharp_char == '\0' ? '_' : sharp_char,
octave,
dots);
result = false;
break;
}
// Note to semitones
uint8_t semitone = 0;
if(note_char == 'P') {
semitone = SEMITONE_PAUSE;
} else {
semitone += octave * 12;
semitone += note_to_semitone(note_char);
semitone += sharp_char == '#' ? 1 : 0;
}
if(music_beeper_worker_add_note(instance, semitone, duration, dots)) {
FURI_LOG_D(
TAG,
"Added note: %c%c%lu.%lu = %u %lu",
note_char == '\0' ? '_' : note_char,
sharp_char == '\0' ? '_' : sharp_char,
octave,
dots,
semitone,
duration);
} else {
FURI_LOG_E(
TAG,
"Invalid note: %c%c%lu.%lu = %u %lu",
note_char == '\0' ? '_' : note_char,
sharp_char == '\0' ? '_' : sharp_char,
octave,
dots,
semitone,
duration);
}
cursor += skip_till(cursor, ',');
}
if(*cursor != '\0') cursor++;
}
return result;
}
bool music_beeper_worker_load(MusicBeeperWorker* instance, const char* file_path) {
furi_assert(instance);
furi_assert(file_path);
bool ret = false;
if(strcasestr(file_path, ".fmf")) {
ret = music_beeper_worker_load_fmf_from_file(instance, file_path);
} else {
ret = music_beeper_worker_load_rtttl_from_file(instance, file_path);
}
return ret;
}
bool music_beeper_worker_load_fmf_from_file(MusicBeeperWorker* instance, const char* file_path) {
furi_assert(instance);
furi_assert(file_path);
bool result = false;
FuriString* temp_str;
temp_str = furi_string_alloc();
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* file = flipper_format_file_alloc(storage);
do {
if(!flipper_format_file_open_existing(file, file_path)) break;
uint32_t version = 0;
if(!flipper_format_read_header(file, temp_str, &version)) break;
if(furi_string_cmp_str(temp_str, MUSIC_BEEPER_FILETYPE) ||
(version != MUSIC_BEEPER_VERSION)) {
FURI_LOG_E(TAG, "Incorrect file format or version");
break;
}
if(!flipper_format_read_uint32(file, "BPM", &instance->bpm, 1)) {
FURI_LOG_E(TAG, "BPM is missing");
break;
}
if(!flipper_format_read_uint32(file, "Duration", &instance->duration, 1)) {
FURI_LOG_E(TAG, "Duration is missing");
break;
}
if(!flipper_format_read_uint32(file, "Octave", &instance->octave, 1)) {
FURI_LOG_E(TAG, "Octave is missing");
break;
}
if(!flipper_format_read_string(file, "Notes", temp_str)) {
FURI_LOG_E(TAG, "Notes is missing");
break;
}
if(!music_beeper_worker_parse_notes(instance, furi_string_get_cstr(temp_str))) {
break;
}
result = true;
} while(false);
furi_record_close(RECORD_STORAGE);
flipper_format_free(file);
furi_string_free(temp_str);
return result;
}
bool music_beeper_worker_load_rtttl_from_file(MusicBeeperWorker* instance, const char* file_path) {
furi_assert(instance);
furi_assert(file_path);
bool result = false;
FuriString* content;
content = furi_string_alloc();
Storage* storage = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(storage);
do {
if(!storage_file_open(file, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
FURI_LOG_E(TAG, "Unable to open file");
break;
};
uint16_t ret = 0;
do {
uint8_t buffer[65] = {0};
ret = storage_file_read(file, buffer, sizeof(buffer) - 1);
for(size_t i = 0; i < ret; i++) {
furi_string_push_back(content, buffer[i]);
}
} while(ret > 0);
furi_string_trim(content);
if(!furi_string_size(content)) {
FURI_LOG_E(TAG, "Empty file");
break;
}
if(!music_beeper_worker_load_rtttl_from_string(instance, furi_string_get_cstr(content))) {
FURI_LOG_E(TAG, "Invalid file content");
break;
}
result = true;
} while(0);
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
furi_string_free(content);
return result;
}
bool music_beeper_worker_load_rtttl_from_string(MusicBeeperWorker* instance, const char* string) {
furi_assert(instance);
const char* cursor = string;
// Skip name
cursor += skip_till(cursor, ':');
if(*cursor != ':') {
return false;
}
// Duration
cursor += skip_till(cursor, '=');
if(*cursor != '=') {
return false;
}
cursor++;
cursor += extract_number(cursor, &instance->duration);
// Octave
cursor += skip_till(cursor, '=');
if(*cursor != '=') {
return false;
}
cursor++;
cursor += extract_number(cursor, &instance->octave);
// BPM
cursor += skip_till(cursor, '=');
if(*cursor != '=') {
return false;
}
cursor++;
cursor += extract_number(cursor, &instance->bpm);
// Notes
cursor += skip_till(cursor, ':');
if(*cursor != ':') {
return false;
}
cursor++;
if(!music_beeper_worker_parse_notes(instance, cursor)) {
return false;
}
return true;
}
void music_beeper_worker_set_callback(
MusicBeeperWorker* instance,
MusicBeeperWorkerCallback callback,
void* context) {
furi_assert(instance);
instance->callback = callback;
instance->callback_context = context;
}
void music_beeper_worker_set_volume(MusicBeeperWorker* instance, float volume) {
furi_assert(instance);
instance->volume = volume;
}
void music_beeper_worker_start(MusicBeeperWorker* instance) {
furi_assert(instance);
furi_assert(instance->should_work == false);
instance->should_work = true;
furi_thread_start(instance->thread);
}
void music_beeper_worker_stop(MusicBeeperWorker* instance) {
furi_assert(instance);
furi_assert(instance->should_work == true);
instance->should_work = false;
furi_thread_join(instance->thread);
}
@@ -0,0 +1,46 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef void (*MusicBeeperWorkerCallback)(
uint8_t semitone,
uint8_t dots,
uint8_t duration,
float position,
void* context);
typedef struct MusicBeeperWorker MusicBeeperWorker;
MusicBeeperWorker* music_beeper_worker_alloc();
void music_beeper_worker_clear(MusicBeeperWorker* instance);
void music_beeper_worker_free(MusicBeeperWorker* instance);
bool music_beeper_worker_load(MusicBeeperWorker* instance, const char* file_path);
bool music_beeper_worker_load_fmf_from_file(MusicBeeperWorker* instance, const char* file_path);
bool music_beeper_worker_load_rtttl_from_file(MusicBeeperWorker* instance, const char* file_path);
bool music_beeper_worker_load_rtttl_from_string(MusicBeeperWorker* instance, const char* string);
void music_beeper_worker_set_callback(
MusicBeeperWorker* instance,
MusicBeeperWorkerCallback callback,
void* context);
void music_beeper_worker_set_volume(MusicBeeperWorker* instance, float volume);
void music_beeper_worker_start(MusicBeeperWorker* instance);
void music_beeper_worker_stop(MusicBeeperWorker* instance);
#ifdef __cplusplus
}
#endif