Skip to content

Commit de510bf

Browse files
authored
feat(t-deck): Add audio output and sdcard support (#417)
* feat(t-deck): Add audio output and sdcard support * Update `espp::TDeck` to support audio output with same API as `espp::EspBox` for interoperability * Update t-deck example to demonstrate audio the same way that the esp-box example does Improves the utility and functionality of the device to better support the T-Deck. This is also part of the work to allow the esp-box-emu project to run on the TDeck. Build and run `t-deck/example` on a TDeck and ensure it works and the sound plays correctly. * update comment * remove unneeded code * update commment / example * remove boot button which is unneeded since trackball accounts for it * explicit cast
1 parent 6d0d0f6 commit de510bf

File tree

9 files changed

+553
-39
lines changed

9 files changed

+553
-39
lines changed

components/esp-box/include/esp-box.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,16 @@ class EspBox : public BaseComponent {
5959
/// Alias the IMU used by the ESP-Box
6060
using Imu = espp::Icm42607<icm42607::Interface::I2C>;
6161

62+
/// Alias for the touch callback when touch events are received
63+
using touch_callback_t = std::function<void(const TouchpadData &)>;
64+
6265
/// The type of the box
6366
enum class BoxType {
6467
UNKNOWN, ///< unknown box
6568
BOX, ///< ESP32-S3-BOX
6669
BOX3, ///< ESP32-S3-BOX-3
6770
};
6871

69-
using touch_callback_t = std::function<void(const TouchpadData &)>;
70-
7172
/// @brief Access the singleton instance of the EspBox class
7273
/// @return Reference to the singleton instance of the EspBox class
7374
static EspBox &get() {

components/t-deck/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
idf_component_register(
33
INCLUDE_DIRS "include"
44
SRC_DIRS "src"
5-
REQUIRES driver base_component display display_drivers i2c input_drivers interrupt gt911 task t_keyboard
5+
REQUIRES driver base_component display display_drivers fatfs i2c input_drivers interrupt gt911 task t_keyboard
66
REQUIRED_IDF_TARGETS "esp32s3"
77
)
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
idf_component_register(SRC_DIRS "."
2-
INCLUDE_DIRS ".")
2+
INCLUDE_DIRS "."
3+
EMBED_TXTFILES click.wav)
35.1 KB
Binary file not shown.

components/t-deck/example/main/t_deck_example.cpp

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@ using namespace std::chrono_literals;
88

99
static constexpr size_t MAX_CIRCLES = 100;
1010
static std::deque<lv_obj_t *> circles;
11+
static std::vector<uint8_t> audio_bytes;
1112

1213
static std::recursive_mutex lvgl_mutex;
1314
static void draw_circle(int x0, int y0, int radius);
1415
static void clear_circles();
1516

17+
static size_t load_audio();
18+
static void play_click(espp::TDeck &tdeck);
19+
1620
LV_IMG_DECLARE(mouse_cursor_icon);
1721

1822
extern "C" void app_main(void) {
@@ -28,14 +32,33 @@ extern "C" void app_main(void) {
2832
auto keypress_callback = [&](uint8_t key) {
2933
logger.info("Key pressed: {}", key);
3034
if (key == 8) {
35+
// delete key will clear the circles
36+
logger.info("Clearing circles");
3137
std::lock_guard<std::recursive_mutex> lock(lvgl_mutex);
3238
clear_circles();
3339
} else if (key == ' ') {
40+
// space key will rotate the display
41+
logger.info("Rotating display");
3442
std::lock_guard<std::recursive_mutex> lock(lvgl_mutex);
3543
clear_circles();
3644
rotation = static_cast<lv_display_rotation_t>((static_cast<int>(rotation) + 1) % 4);
3745
lv_display_t *disp = lv_display_get_default();
3846
lv_disp_set_rotation(disp, rotation);
47+
} else if (key == 'm') {
48+
// 'm' key will toggle audio mute
49+
logger.info("Toggling mute");
50+
tdeck.mute(!tdeck.is_muted());
51+
logger.info("Muted: {}", tdeck.is_muted());
52+
} else if (key == 'n') {
53+
// 'n' key will decrease audio volume (left of 'm' key)
54+
logger.info("Decreasing volume");
55+
tdeck.volume(tdeck.volume() - 10.0f);
56+
logger.info("Volume: {}", tdeck.volume());
57+
} else if (key == '$') {
58+
// '$' key will increase audio volume (right of 'm' key)
59+
logger.info("Increasing volume");
60+
tdeck.volume(tdeck.volume() + 10.0f);
61+
logger.info("Volume: {}", tdeck.volume());
3962
}
4063
};
4164

@@ -51,6 +74,7 @@ extern "C" void app_main(void) {
5174
previous_touchpad_data = touchpad_data;
5275
// if there is a touch point, draw a circle
5376
if (touchpad_data.num_touch_points > 0) {
77+
play_click(tdeck);
5478
std::lock_guard<std::recursive_mutex> lock(lvgl_mutex);
5579
draw_circle(touchpad_data.x, touchpad_data.y, 10);
5680
}
@@ -61,12 +85,21 @@ extern "C" void app_main(void) {
6185
logger.debug("Trackball: {}", trackball);
6286
};
6387

88+
// initialize the uSD card
89+
if (!tdeck.initialize_sdcard()) {
90+
logger.warn("Failed to initialize uSD card, there may not be a uSD card inserted!");
91+
}
6492
// initialize the Keyboard
6593
bool start_task = true;
6694
if (!tdeck.initialize_keyboard(start_task, keypress_callback)) {
6795
logger.error("Failed to initialize Keyboard!");
6896
return;
6997
}
98+
// initialize the sound
99+
if (!tdeck.initialize_sound()) {
100+
logger.error("Failed to initialize sound!");
101+
return;
102+
}
70103
// initialize the LCD
71104
if (!tdeck.initialize_lcd()) {
72105
logger.error("Failed to initialize LCD!");
@@ -142,6 +175,14 @@ extern "C" void app_main(void) {
142175
}});
143176
lv_task.start();
144177

178+
// load the audio file (wav file bundled in memory)
179+
size_t wav_size = load_audio();
180+
logger.info("Loaded {} bytes of audio", wav_size);
181+
182+
// unmute the audio and set the volume to 20%
183+
tdeck.mute(false);
184+
tdeck.volume(20.0f);
185+
145186
// set the display brightness to be 75%
146187
tdeck.brightness(75.0f);
147188

@@ -177,3 +218,40 @@ static void clear_circles() {
177218
// clear the vector
178219
circles.clear();
179220
}
221+
222+
static size_t load_audio() {
223+
// if the audio_bytes vector is already populated, return the size
224+
if (audio_bytes.size() > 0) {
225+
return audio_bytes.size();
226+
}
227+
228+
// these are configured in the CMakeLists.txt file
229+
extern const char wav_start[] asm("_binary_click_wav_start"); // cppcheck-suppress syntaxError
230+
extern const char wav_end[] asm("_binary_click_wav_end"); // cppcheck-suppress syntaxError
231+
232+
// -1 due to the size being 1 byte too large, I think because end is the byte
233+
// immediately after the last byte in the memory but I'm not sure - cmm 2022-08-20
234+
//
235+
// Suppression as these are linker symbols and cppcheck doesn't know how to ensure
236+
// they are the same object
237+
// cppcheck-suppress comparePointers
238+
size_t wav_size = (wav_end - wav_start) - 1;
239+
FILE *fp = fmemopen((void *)wav_start, wav_size, "rb");
240+
// read the file into the audio_bytes vector
241+
audio_bytes.resize(wav_size);
242+
fread(audio_bytes.data(), 1, wav_size, fp);
243+
fclose(fp);
244+
return wav_size;
245+
}
246+
247+
static void play_click(espp::TDeck &tdeck) {
248+
// use the box.play_audio() function to play a sound, breaking it into
249+
// audio_buffer_size chunks
250+
auto audio_buffer_size = tdeck.audio_buffer_size();
251+
size_t offset = 0;
252+
while (offset < audio_bytes.size()) {
253+
size_t bytes_to_play = std::min(audio_buffer_size, audio_bytes.size() - offset);
254+
tdeck.play_audio(audio_bytes.data() + offset, bytes_to_play);
255+
offset += bytes_to_play;
256+
}
257+
}

0 commit comments

Comments
 (0)