Initial public release of the 2024A software.
This commit is contained in:
parent
7b9ad3edfd
commit
303e9e1dad
361 changed files with 60083 additions and 2 deletions
462
components/Audio/I2S_Audio.c
Normal file
462
components/Audio/I2S_Audio.c
Normal file
|
@ -0,0 +1,462 @@
|
|||
#include <SystemK.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <driver/i2s_std.h>
|
||||
#include <esp_spiffs.h>
|
||||
#include <audio_player.h>
|
||||
|
||||
// I2S Configuration
|
||||
#define I2S_DOUT GPIO_NUM_11
|
||||
#define I2S_BCLK GPIO_NUM_12
|
||||
#define I2S_LRC GPIO_NUM_13
|
||||
#define I2S_DIN I2S_GPIO_UNUSED
|
||||
#define I2S_SCLK I2S_GPIO_UNUSED
|
||||
|
||||
#define AUDIO_TASK_STACK_SIZE 4096
|
||||
|
||||
static StackType_t xStack[AUDIO_TASK_STACK_SIZE];
|
||||
static StaticTask_t xTaskBuffer;
|
||||
static QueueHandle_t xQueueAudio;
|
||||
static TaskHandle_t xAudioTaskHandle;
|
||||
void I2SAudioTask(void *pvParameters);
|
||||
|
||||
static const char *TAG = "I2S Audio";
|
||||
static i2s_chan_handle_t tx_handle;
|
||||
|
||||
#define I2S_GPIO_CFG \
|
||||
{ \
|
||||
.mclk = I2S_SCLK, \
|
||||
.bclk = I2S_BCLK, \
|
||||
.ws = I2S_LRC, \
|
||||
.dout = I2S_DOUT, \
|
||||
.din = I2S_DIN, \
|
||||
.invert_flags = { \
|
||||
.mclk_inv = false, \
|
||||
.bclk_inv = false, \
|
||||
.ws_inv = false, \
|
||||
}, \
|
||||
}
|
||||
|
||||
#define I2S_DUPLEX_STEREO_CFG(_sample_rate) \
|
||||
{ \
|
||||
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(_sample_rate), \
|
||||
.slot_cfg = I2S_STD_PHILIP_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), \
|
||||
.gpio_cfg = I2S_GPIO_CFG, \
|
||||
}
|
||||
|
||||
static esp_err_t I2S_Audio_Set_Mute(AUDIO_PLAYER_MUTE_SETTING setting)
|
||||
{
|
||||
if (setting == AUDIO_PLAYER_MUTE)
|
||||
{
|
||||
KLOG_DEBUG(TAG, "Muted.");
|
||||
}
|
||||
else
|
||||
{
|
||||
KLOG_DEBUG(TAG, "Muted.");
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
static esp_err_t I2S_Audio_Write(void *audio_buffer, size_t len, size_t *bytes_written, uint32_t timeout_ms)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
ret = i2s_channel_write(tx_handle, (char *)audio_buffer, len, bytes_written, timeout_ms);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t I2S_Audio_Reconfigure_Clock(uint32_t rate, uint32_t bits_cfg, i2s_slot_mode_t ch)
|
||||
{
|
||||
KLOG_INFO(TAG, "Reconfiguring clock for %lu sps, %lu bps, %d channels.", rate, bits_cfg, ch);
|
||||
|
||||
esp_err_t ret = ESP_OK;
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(rate),
|
||||
.slot_cfg = I2S_STD_PHILIP_SLOT_DEFAULT_CONFIG((i2s_data_bit_width_t)bits_cfg, (i2s_slot_mode_t)ch),
|
||||
.gpio_cfg = I2S_GPIO_CFG};
|
||||
|
||||
ret |= i2s_channel_disable(tx_handle);
|
||||
ret |= i2s_channel_reconfig_std_clock(tx_handle, &std_cfg.clk_cfg);
|
||||
ret |= i2s_channel_reconfig_std_slot(tx_handle, &std_cfg.slot_cfg);
|
||||
ret |= i2s_channel_enable(tx_handle);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Initialize_Audio(void)
|
||||
{
|
||||
KLOG_INFO(TAG, "Initializing I2S Audio...");
|
||||
|
||||
// Setup a standard configuration and the channel.
|
||||
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
|
||||
chan_cfg.auto_clear = true; // Auto clear the legacy data in the DMA buffer.
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, NULL));
|
||||
|
||||
// Setup the I2S configuration.
|
||||
i2s_std_config_t std_cfg = I2S_DUPLEX_STEREO_CFG(44100);
|
||||
esp_err_t ret = i2s_channel_init_std_mode(tx_handle, &std_cfg);
|
||||
|
||||
if (ret != ESP_OK)
|
||||
{
|
||||
if (ret == ESP_ERR_NO_MEM)
|
||||
{
|
||||
KLOG_ERROR(TAG, "No memory for storing the channel information");
|
||||
}
|
||||
else if (ret == ESP_ERR_INVALID_ARG)
|
||||
{
|
||||
KLOG_ERROR(TAG, "NULL pointer or invalid configuration");
|
||||
}
|
||||
else if (ret == ESP_ERR_INVALID_STATE)
|
||||
{
|
||||
KLOG_ERROR(TAG, "This channel is not registered");
|
||||
}
|
||||
else
|
||||
{
|
||||
KLOG_ERROR(TAG, "Failed to initialize I2S Audio (%s)", esp_err_to_name(ret));
|
||||
}
|
||||
}
|
||||
|
||||
i2s_channel_enable(tx_handle);
|
||||
|
||||
xQueueAudio = xQueueCreate(5, sizeof(AudioAction_T));
|
||||
if (xQueueAudio == NULL)
|
||||
{
|
||||
KLOG_ERROR(TAG, "Couldn't create the I2S Audio queue.");
|
||||
}
|
||||
|
||||
audio_player_config_t config = {.mute_fn = I2S_Audio_Set_Mute,
|
||||
.write_fn = I2S_Audio_Write,
|
||||
.clk_set_fn = I2S_Audio_Reconfigure_Clock,
|
||||
.priority = 10,
|
||||
.coreID = APP_CPU_NUM};
|
||||
ret = audio_player_new(config);
|
||||
if (ret != ESP_OK)
|
||||
{
|
||||
KLOG_ERROR(TAG, "Couldn't create the audio player.");
|
||||
}
|
||||
|
||||
xAudioTaskHandle = xTaskCreateStaticPinnedToCore(
|
||||
I2SAudioTask, // Function that implements the task.
|
||||
"I2S Audio", // Text name for the task.
|
||||
AUDIO_TASK_STACK_SIZE, // Stack size in words, not bytes.
|
||||
NULL, // Parameter passed into the task.
|
||||
1, // Priority at which the task is created.
|
||||
xStack, // Array to use as the task's stack.
|
||||
&xTaskBuffer, // Variable to hold the task's data structure.
|
||||
APP_CPU_NUM); // Core where the task should run.
|
||||
|
||||
if (xAudioTaskHandle == NULL)
|
||||
{
|
||||
KLOG_ERROR(TAG, "Couldn't create the I2S Audio task.");
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t Play_Audio_File(const char *filename)
|
||||
{
|
||||
KLOG_INFO(TAG, "Attempting to play file: %s", filename);
|
||||
KLOG_DEBUG(TAG, "Free heap: %ld bytes", esp_get_free_heap_size());
|
||||
|
||||
FILE *fh = fopen(filename, "rb");
|
||||
if (fh == NULL)
|
||||
{
|
||||
KLOG_ERROR(TAG, "Failed to open audio file %s! Error: %s", filename, strerror(errno));
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
fseek(fh, 0, SEEK_END);
|
||||
long file_size = ftell(fh);
|
||||
fseek(fh, 0, SEEK_SET);
|
||||
KLOG_DEBUG(TAG, "File size: %ld bytes", file_size);
|
||||
|
||||
esp_err_t ret = audio_player_play(fh);
|
||||
if (ret != ESP_OK)
|
||||
{
|
||||
KLOG_ERROR(TAG, "Couldn't play audio file %s! Error: %d", filename, ret);
|
||||
fclose(fh);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
SystemKResult_T Perform_Audio_Action(AudioAction_T *action)
|
||||
{
|
||||
if (xQueueSend(xQueueAudio, action, 0) == pdTRUE)
|
||||
{
|
||||
return SYSTEMK_RESULT_SUCCESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
return SYSTEMK_RESULT_QUEUE_IS_FULL;
|
||||
}
|
||||
}
|
||||
|
||||
char *concat_path(const char *directory_path, const char *filename)
|
||||
{
|
||||
size_t needed = snprintf(NULL, 0, "%s/%s", directory_path, filename) + 1;
|
||||
char *full_path = malloc(needed);
|
||||
if (full_path != NULL)
|
||||
{
|
||||
snprintf(full_path, needed, "%s/%s", directory_path, filename);
|
||||
}
|
||||
return full_path;
|
||||
}
|
||||
|
||||
char *find_filename(const char *directory_path, const char *prefix)
|
||||
{
|
||||
DIR *dir = opendir(directory_path);
|
||||
if (dir == NULL)
|
||||
{
|
||||
KLOG_ERROR(TAG, "Failed to open directory '%s': %s!", directory_path, strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct dirent *entry;
|
||||
char *filename = NULL;
|
||||
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
if (entry->d_type == DT_REG && strncmp(entry->d_name, prefix, strlen(prefix)) == 0)
|
||||
{
|
||||
filename = strdup(entry->d_name);
|
||||
if (filename == NULL)
|
||||
{
|
||||
KLOG_ERROR(TAG, "Memory allocation failed!");
|
||||
}
|
||||
else
|
||||
{
|
||||
KLOG_DEBUG(TAG, "Found matching file: %s", filename);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
if (filename == NULL)
|
||||
{
|
||||
KLOG_WARN(TAG, "No file beginning with '%s' found in directory '%s'!", prefix, directory_path);
|
||||
}
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
SystemKResult_T Play_Sound_By_Prefix(const char *prefix)
|
||||
{
|
||||
SystemKResult_T result = SYSTEMK_RESULT_SUCCESS;
|
||||
|
||||
const char *sounds_dir = "/usb/01";
|
||||
|
||||
char *filename = find_filename(sounds_dir, prefix);
|
||||
|
||||
if (filename != NULL)
|
||||
{
|
||||
if (audio_player_get_state() == AUDIO_PLAYER_STATE_PLAYING)
|
||||
{
|
||||
audio_player_stop();
|
||||
}
|
||||
Play_Audio_File(concat_path(sounds_dir, filename));
|
||||
}
|
||||
else
|
||||
{
|
||||
result = SYSTEMK_RESULT_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
SystemKResult_T Play_Number_Sound(uint8_t number)
|
||||
{
|
||||
SystemKResult_T result = SYSTEMK_RESULT_SUCCESS;
|
||||
|
||||
char number_str[4];
|
||||
snprintf(number_str, 4, "%03u", number);
|
||||
|
||||
const char *sounds_dir = "/usb/10";
|
||||
|
||||
char *filename = find_filename(sounds_dir, (const char *)number_str);
|
||||
|
||||
if (filename != NULL)
|
||||
{
|
||||
if (audio_player_get_state() == AUDIO_PLAYER_STATE_PLAYING)
|
||||
{
|
||||
audio_player_stop();
|
||||
}
|
||||
Play_Audio_File(concat_path(sounds_dir, filename));
|
||||
}
|
||||
else
|
||||
{
|
||||
result = SYSTEMK_RESULT_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void I2SAudioTask(void *pvParameters)
|
||||
{
|
||||
// Wait for system initialization.
|
||||
// TODO: Make this closed-loop!
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
|
||||
while (true)
|
||||
{
|
||||
AudioAction_T action;
|
||||
|
||||
if (xQueueReceive(xQueueAudio, &action, portMAX_DELAY) == pdPASS)
|
||||
{
|
||||
|
||||
switch (action.ID)
|
||||
{
|
||||
case AUDIO_SET_VOLUME:
|
||||
break;
|
||||
|
||||
case AUDIO_SILENCE:
|
||||
if (audio_player_get_state() == AUDIO_PLAYER_STATE_PLAYING)
|
||||
{
|
||||
audio_player_stop();
|
||||
}
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_STARTUP_SOUND:
|
||||
Play_Sound_By_Prefix("001");
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_SHOT_FIRED:
|
||||
Play_Sound_By_Prefix("002");
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_TAG_RECEIVED:
|
||||
Play_Sound_By_Prefix("003");
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_TAGGED_OUT:
|
||||
Play_Sound_By_Prefix("004");
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_MISFIRE:
|
||||
Play_Sound_By_Prefix("005");
|
||||
break;
|
||||
|
||||
case AUDIO_PRONOUNCE_NUMBER_0_TO_100:
|
||||
uint8_t number = *((uint8_t *)action.Data);
|
||||
if (number > 100)
|
||||
{
|
||||
number = 100;
|
||||
}
|
||||
else if (number == 0)
|
||||
{
|
||||
number = 101;
|
||||
}
|
||||
Play_Number_Sound(number);
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_MENU_PROMPT:
|
||||
Play_Sound_By_Prefix("006");
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_SELECTION_INDICATOR:
|
||||
Play_Sound_By_Prefix("007");
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_HEALTH_REMAINING:
|
||||
Play_Sound_By_Prefix("008");
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_ELECTRONIC_DANCE_MUSIC:
|
||||
Play_Sound_By_Prefix("009");
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_GENERIC_ERROR:
|
||||
Play_Sound_By_Prefix("010");
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_VOLUME_PROMPT:
|
||||
Play_Sound_By_Prefix("011");
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_RIGHT_HANDED:
|
||||
Play_Sound_By_Prefix("012");
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_LEFT_HANDED:
|
||||
Play_Sound_By_Prefix("013");
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_GAME_ON:
|
||||
Play_Sound_By_Prefix("014");
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_HARDWARE_SETTINGS_PROMPT:
|
||||
Play_Sound_By_Prefix("015");
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_GAME_SETTINGS_PROMPT:
|
||||
Play_Sound_By_Prefix("016");
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_BONK:
|
||||
Play_Sound_By_Prefix("017");
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_NEAR_MISS:
|
||||
Play_Sound_By_Prefix("018");
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_PLAYER_ID_PROMPT:
|
||||
Play_Sound_By_Prefix("019");
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_TEAM_ID_PROMPT:
|
||||
Play_Sound_By_Prefix("020");
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_FRIENDLY_FIRE:
|
||||
KLOG_WARN(TAG, "\"Friendly Fire\" audio is disabled in this build.");
|
||||
//Play_Sound_By_Prefix("021");
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_STARTING_THEME:
|
||||
Play_Sound_By_Prefix("022");
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_BOOP:
|
||||
Play_Sound_By_Prefix("023");
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_BEEP:
|
||||
Play_Sound_By_Prefix("024");
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_REPROGRAMMING:
|
||||
Play_Sound_By_Prefix("025");
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_BOMB:
|
||||
Play_Sound_By_Prefix("026");
|
||||
break;
|
||||
|
||||
case AUDIO_PLAY_GAME_OVER:
|
||||
Play_Sound_By_Prefix("027");
|
||||
break;
|
||||
|
||||
default:
|
||||
Play_Audio_File("/spiffs/bad.wav");
|
||||
break;
|
||||
}
|
||||
|
||||
if (action.Play_To_Completion == true)
|
||||
{
|
||||
// Allow some time for the audio to start.
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
|
||||
while (audio_player_get_state() != AUDIO_PLAYER_STATE_IDLE)
|
||||
{
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
KEvent_T command_received_event = {.ID = KEVENT_AUDIO_COMPLETED, .Data = (void *)action.ID};
|
||||
Post_KEvent(&command_received_event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue