Skip to content

Commit

Permalink
Merge pull request #5 from and3rson/ad/rtos-buzzer
Browse files Browse the repository at this point in the history
Rewrite buzzer to play melodies in FreeRTOS task, fix Lua memory usage (GC)
  • Loading branch information
and3rson authored Mar 11, 2024
2 parents b1b9b5c + b39a2f2 commit 39f8e83
Show file tree
Hide file tree
Showing 15 changed files with 411 additions and 46 deletions.
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

0 comments on commit 39f8e83

Please sign in to comment.