Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite buzzer to play melodies in FreeRTOS task, fix Lua memory usage (GC) #5

Merged
merged 10 commits into from
Mar 11, 2024
8 changes: 8 additions & 0 deletions docs/manual/keira/lua/reference/buzzer.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
``buzzer`` - П'єзо-динамік
--------------------------

Функції для роботи з п'єзо-динаміком.

.. note:: Ці функції не блокують виконання програми: всі звуки та мелодії відтворюються в фоновому режимі.

.. lua:autoclass:: buzzer
1 change: 1 addition & 0 deletions docs/manual/keira/lua/reference/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ Lua API
geometry
gpio
util
buzzer
state
1 change: 1 addition & 0 deletions docs/manual/keira/lua/reference/lilka.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
Детальніше про їх поведінку можна прочитати в :ref:`секції про написання ігор на Lua <lua-games>`.

.. lua:module:: lilka
:noindex:

.. lua:function:: init()

Expand Down
64 changes: 64 additions & 0 deletions firmware/keira/sdcard/asteroids/asteroids.lua
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,67 @@ end
PRESS_START = resources.load_image(ROOT .. "press_start.bmp")
YOU_ARE_DEAD = resources.load_image(ROOT .. "game_over.bmp")

-- Buzzer sound for shooting.
-- First element of each item is frequency, second is note size.
SHOOT_SOUND = {
{880, 8},
{784, 8},
{698, 8},
{659, 8},
{587, 8},
{523, 8},
{440, 8},
{392, 8},
{349, 8},
{330, 8},
{294, 8},
{262, 8},
}

BOOM_SOUND = {
{ 440, 8 },
{ 392, 8 },
{ 349, 8 },
{ 330, 8 },
{ 294, 8 },
{ 262, 8 },
{ 220, 8 },
{ 196, 8 },
{ 175, 8 },
{ 165, 8 },
{ 147, 8 },
{ 131, 8 },
{ 123, 8 },
{ 110, 8 },
{ 98, 8 },
{ 88, 8 },
}

-- Low-pitch (100-200 Hz) noise.
DEATH_SOUND = {
{ 200, 8 },
{ 100, 8 },
{ 150, 8 },
{ 200, 8 },
{ 100, 8 },
{ 150, 8 },
{ 100, 8 },
{ 150, 8 },
{ 100, 8 },
{ 150, 8 },
{ 50, 8 },
{ 100, 8 },
{ 50, 8 },
{ 100, 8 },
{ 50, 8 },
{ 100, 8 },
{ 50, 8 },
{ 100, 8 },
{ 50, 8 },
{ 100, 8 },
{ 50, 8 },
}

-------------------------------------------------------------------------------
-- Ігрові класи
-------------------------------------------------------------------------------
Expand Down Expand Up @@ -397,6 +458,7 @@ function lilka.update(delta)
bullet.speed_x = ship.speed_x / 2 + display.width * dir_x
bullet.speed_y = ship.speed_y / 2 + display.width * dir_y
table.insert(bullets, bullet)
buzzer.play_melody(SHOOT_SOUND, 400)
end

-- Вихід з гри
Expand Down Expand Up @@ -443,6 +505,7 @@ function lilka.update(delta)
-- Якщо куля влучила в астероїд, то вони обидвоє вмирають
asteroid:kill()
bullet:kill()
buzzer.play_melody(BOOM_SOUND, 400)
end
end
if math.dist(asteroid.x, asteroid.y, ship.x, ship.y) < (asteroid.sprite.width / 3 + ship.width / 2) then
Expand All @@ -452,6 +515,7 @@ function lilka.update(delta)
asteroid:kill()
ship:kill()
game_state = STATES.GAME_OVER
buzzer.play_melody(DEATH_SOUND, 400)
end
end
end
Expand Down
48 changes: 35 additions & 13 deletions firmware/keira/src/app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,33 @@
App::App(const char* name) : App(name, 0, 24, lilka::display.width(), lilka::display.height() - 24) {
}

App::App(const char* name, uint16_t x, uint16_t y, uint16_t w, uint16_t h) : name(name), x(x), y(y), w(w), h(h) {
canvas = new lilka::Canvas(x, y, w, h);
canvas->begin();
canvas->fillScreen(0);
backCanvas = new lilka::Canvas(x, y, w, h);
backCanvas->begin();
backCanvas->fillScreen(0);
backCanvasMutex = xSemaphoreCreateMutex();
isDrawQueued = false;
flags = AppFlags::APP_FLAG_NONE;
App::App(const char* name, uint16_t x, uint16_t y, uint16_t w, uint16_t h) :
name(name),
x(x),
y(y),
w(w),
h(h),
flags(AppFlags::APP_FLAG_NONE),
taskHandle(NULL),
canvas(new lilka::Canvas(x, y, w, h)),
backCanvas(new lilka::Canvas(x, y, w, h)),
isDrawQueued(false),
backCanvasMutex(xSemaphoreCreateMutex()) {
Serial.println(
"Created app " + String(name) + " at " + String(x) + ", " + String(y) + " with size " + String(w) + "x" +
String(h)
);
}

void App::start() {
if (taskHandle != NULL) {
Serial.println("App " + String(name) + " is already running");
return;
}
Serial.println("Starting app " + String(name));
xTaskCreate(_run, name, 32768, this, 1, &taskHandle);
if (xTaskCreate(_run, name, 8192, this, 1, &taskHandle) != pdPASS) {
Serial.println("Failed to create task for app " + String(name) + " (not enough memory?)");
}
}

void App::_run(void* data) {
Expand All @@ -34,20 +42,34 @@ void App::_run(void* data) {
}

void App::suspend() {
// TODO: Check if the task is already suspended
if (taskHandle == NULL) {
Serial.println("App " + String(name) + " is not running, cannot suspend");
return;
}
Serial.println("Suspending app " + String(name) + " (state = " + String(getState()) + ")");
onSuspend();
vTaskSuspend(taskHandle);
}

void App::resume() {
// TODO: Check if the task is already running
if (taskHandle == NULL) {
Serial.println("App " + String(name) + " is not running, cannot resume");
return;
}
Serial.println("Resuming app " + String(name) + " (state = " + String(getState()) + ")");
onResume();
vTaskResume(taskHandle);
}

void App::stop() {
if (taskHandle == NULL) {
Serial.println("App " + String(name) + " is not running, cannot stop");
return;
}
Serial.println("Stopping app " + String(name) + " (state = " + String(getState()) + ")");
onStop();
vTaskDelete(taskHandle);
taskHandle = NULL;
}

App::~App() {
Expand Down
6 changes: 6 additions & 0 deletions firmware/keira/src/app.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ class App {

private:
virtual void run() = 0;
virtual void onSuspend() {
}
virtual void onResume() {
}
virtual void onStop() {
}

const char* name;
uint16_t x, y, w, h;
Expand Down
98 changes: 98 additions & 0 deletions firmware/keira/src/apps/lua/lualilka_buzzer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#include <csetjmp>

#include "lualilka_buzzer.h"

extern jmp_buf stopjmp;

int lualilka_buzzer_play(lua_State* L) {
// Takes 1 or 2 args: frequency or frequency and duration
int n = lua_gettop(L);
if (n < 1 || n > 2) {
return luaL_error(L, "Очікується 1 або 2 аргументи, отримано %d", n);
}
int freq = luaL_checkinteger(L, 1);
if (n == 1) {
lilka::buzzer.play(freq);
} else {
int duration = luaL_checkinteger(L, 2);
lilka::buzzer.play(freq, duration);
}
return 0;
}

int lualilka_buzzer_playMelody(lua_State* L) {
// Takes 2 arg: table of tones and tempo. Each tone is a table of 2 elements: frequency and duration
// Convert them to array of arrays
int n = lua_gettop(L);
if (n != 2) {
return luaL_error(L, "Очікується 2 аргументи, отримано %d", n);
}
luaL_checktype(L, 1, LUA_TTABLE);
luaL_checktype(L, 2, LUA_TNUMBER);
int size = luaL_len(L, 1);
int tempo = luaL_checkinteger(L, 2);
lilka::Tone melody[size];
for (int i = 0; i < size; i++) {
lua_geti(L, 1, i + 1);
luaL_checktype(L, -1, LUA_TTABLE);
lua_rawgeti(L, -1, 1);
lua_rawgeti(L, -2, 2);
melody[i].frequency = luaL_checkinteger(L, -2);
melody[i].size = luaL_checkinteger(L, -1);
lua_pop(L, 3);
}
lua_pop(L, 2);
lilka::buzzer.playMelody(melody, size, tempo);
return 0;
}

int lualilka_buzzer_stop(lua_State* L) {
lilka::buzzer.stop();
return 0;
}

static const luaL_Reg lualilka_buzzer[] = {
{"play", lualilka_buzzer_play},
{"play_melody", lualilka_buzzer_playMelody},
{"stop", lualilka_buzzer_stop},
{NULL, NULL},
};

int lualilka_buzzer_register(lua_State* L) {
using namespace lilka;
// Create global "buzzer" table that contains all buzzer functions
luaL_newlib(L, lualilka_buzzer);
lua_setglobal(L, "buzzer");
// Create notes table for frequencies from B0 to D#8
// clang-format off
const lilka::Note notes[] = {
NOTE_B0,
NOTE_C1, NOTE_CS1, NOTE_D1, NOTE_DS1, NOTE_E1, NOTE_F1, NOTE_FS1, NOTE_G1, NOTE_GS1, NOTE_A1, NOTE_AS1, NOTE_B1,
NOTE_C2, NOTE_CS2, NOTE_D2, NOTE_DS2, NOTE_E2, NOTE_F2, NOTE_FS2, NOTE_G2, NOTE_GS2, NOTE_A2, NOTE_AS2, NOTE_B2,
NOTE_C3, NOTE_CS3, NOTE_D3, NOTE_DS3, NOTE_E3, NOTE_F3, NOTE_FS3, NOTE_G3, NOTE_GS3, NOTE_A3, NOTE_AS3, NOTE_B3,
NOTE_C4, NOTE_CS4, NOTE_D4, NOTE_DS4, NOTE_E4, NOTE_F4, NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4, NOTE_B4,
NOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, NOTE_FS5, NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5,
NOTE_C6, NOTE_CS6, NOTE_D6, NOTE_DS6, NOTE_E6, NOTE_F6, NOTE_FS6, NOTE_G6, NOTE_GS6, NOTE_A6, NOTE_AS6, NOTE_B6,
NOTE_C7, NOTE_CS7, NOTE_D7, NOTE_DS7, NOTE_E7, NOTE_F7, NOTE_FS7, NOTE_G7, NOTE_GS7, NOTE_A7, NOTE_AS7, NOTE_B7,
NOTE_C8, NOTE_CS8, NOTE_D8, NOTE_DS8, REST,
};
const char* noteNames[] = {
"B0",
"C1", "CS1", "D1", "DS1", "E1", "F1", "FS1", "G1", "GS1", "A1", "AS1", "B1",
"C2", "CS2", "D2", "DS2", "E2", "F2", "FS2", "G2", "GS2", "A2", "AS2", "B2",
"C3", "CS3", "D3", "DS3", "E3", "F3", "FS3", "G3", "GS3", "A3", "AS3", "B3",
"C4", "CS4", "D4", "DS4", "E4", "F4", "FS4", "G4", "GS4", "A4", "AS4", "B4",
"C5", "CS5", "D5", "DS5", "E5", "F5", "FS5", "G5", "GS5", "A5", "AS5", "B5",
"C6", "CS6", "D6", "DS6", "E6", "F6", "FS6", "G6", "GS6", "A6", "AS6", "B6",
"C7", "CS7", "D7", "DS7", "E7", "F7", "FS7", "G7", "GS7", "A7", "AS7", "B7",
"C8", "CS8", "D8", "DS8", "REST",
};
// clang-format on
lua_newtable(L);
for (int i = 0; i < sizeof(notes) / sizeof(notes[0]); i++) {
lua_pushinteger(L, notes[i]);
lua_setfield(L, -2, noteNames[i]);
}
lua_setglobal(L, "notes");
return 0;
}
6 changes: 6 additions & 0 deletions firmware/keira/src/apps/lua/lualilka_buzzer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#pragma once

#include <lua.hpp>
#include <lilka.h>

int lualilka_buzzer_register(lua_State* L);
3 changes: 2 additions & 1 deletion firmware/keira/src/apps/lua/lualilka_display.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
Arduino_GFX* getDrawable(lua_State* L) {
lua_getfield(L, LUA_REGISTRYINDEX, "app");
const App* app = static_cast<App*>(lua_touserdata(L, -1));
lua_pop(L, 1);
return app->canvas;
}

Expand Down Expand Up @@ -255,7 +256,7 @@ int lualilka_display_drawImage(lua_State* L) {
int lualilka_display_queueDraw(lua_State* L) {
// Get App from registry
lua_getfield(L, LUA_REGISTRYINDEX, "app");
App* app = static_cast<App*>(app);
App* app = static_cast<App*>(lua_touserdata(L, -1));
lua_pop(L, 1);
// Queue draw
app->queueDraw();
Expand Down
Loading
Loading