esp32 soundboard project
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

187 lines
6.4 KiB

/*
* i2s
*
* http://esp-idf.readthedocs.io/en/latest/api/peripherals/i2s.html
* https://github.com/espressif/esp-idf/blob/375b28650bd1c90d6ac706f63cde9a64d9a7e3e5/examples/peripherals/i2s/main/i2s_example_main.c
* see: https://github.com/MrBuddyCasino/ESP32_MP3_Decoder/blob/master/components/audio_renderer/audio_renderer.c
*/
#include "sound.h"
#include "datasource.h"
#include "sdcard.h"
#include "Arduino.h"
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s.h"
//#include <soc/i2s_reg.h>
#include <math.h>
#include "freertos/semphr.h"
#define SAMPLE_RATE (11025)
#define WAVE_FREQ_HZ (100)
//#define PI 3.14159265
#define SAMPLE_PER_CYCLE (SAMPLE_RATE/WAVE_FREQ_HZ)
#define EXIT_BIT 0x01
using namespace std;
SemaphoreHandle_t xPlayingSemaphore = NULL;
static TaskHandle_t xTaskToNotify = NULL;
Sound* Sound::instance = NULL;
Sound::Sound() {
xPlayingSemaphore = xSemaphoreCreateMutex();
Sound::instance = this;
}
Sound* Sound::getInstance() {
return Sound::instance;
}
void Sound::init() {
unsigned int i, sample_val;
float sin_float, triangle_float, triangle_step = 65536.0 / SAMPLE_PER_CYCLE;
//for 36Khz sample rates, we create 100Hz sine wave, every cycle need 36000/100 = 360 samples (4-bytes each sample)
//using 6 buffers, we need 60-samples per buffer
//2-channels, 16-bit each channel, total buffer is 360*4 = 1440 bytes
i2s_config_t i2s_config;
i2s_config.mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_TX); // Only TX
i2s_config.sample_rate = SAMPLE_RATE;
i2s_config.bits_per_sample = (i2s_bits_per_sample_t)16; //16-bit per channel
i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT; //2-channels
i2s_config.communication_format = (i2s_comm_format_t) (I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB);
i2s_config.dma_buf_count = 6;
i2s_config.dma_buf_len = 60; //
i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; //Interrupt level 1
i2s_pin_config_t pin_config = {
.bck_io_num = 26,
.ws_io_num = 25,
.data_out_num = 22,
.data_in_num = -1 //Not used
};
i2s_driver_install(i2s_num, &i2s_config, 0, NULL);
i2s_set_pin(i2s_num, &pin_config);
/* play a sine wave (example code) */
triangle_float = -32767;
for(i = 0; i < SAMPLE_PER_CYCLE; i++) {
sin_float = sin(i * PI / 180.0);
if(sin_float >= 0)
triangle_float += triangle_step;
else
triangle_float -= triangle_step;
sin_float *= 32767;
sample_val = 0;
sample_val += (short)triangle_float;
sample_val = sample_val << 16;
sample_val += (short) sin_float;
i2s_push_sample(i2s_num, (char *)&sample_val, portMAX_DELAY);
}
i2s_stop(i2s_num);
}
void Sound::end() {
i2s_driver_uninstall(i2s_num); //stop & destroy i2s driver
}
void Sound::play(const char* path) {
if (xTaskToNotify != NULL) {
xTaskNotify( xTaskToNotify,
EXIT_BIT,
eSetBits);
taskYIELD();
}
xTaskCreate(&(Sound::play_task), "play_task", 3072, (void*)path, 5, &xTaskSound);
}
void Sound::play_task(void *pvParameter) {
// Serial.print("initial free stack: "); Serial.println(uxTaskGetStackHighWaterMark( NULL ), DEC); // DEBUG
char *path = (char *) pvParameter;
Serial.println("sound task: playing");
if ( xPlayingSemaphore == NULL || xSemaphoreTake( xPlayingSemaphore, 42 / portTICK_PERIOD_MS) != pdTRUE ) {
Serial.println("Sound task: could not obtain semaphore");
vTaskDelete( NULL );
return;
}
xTaskToNotify = xTaskGetCurrentTaskHandle();
// Serial.print("before SDCard::open(); free stack: "); Serial.println(uxTaskGetStackHighWaterMark( NULL ), DEC); // DEBUG
File file = SDCard::open(path); // ToDo: accessing static member function loads 1500 words onto stack!
if (!file) {
Serial.print("Failed to open file: "); Serial.println(path);
xTaskToNotify = NULL;
xSemaphoreGive( xPlayingSemaphore );
vTaskDelete( NULL );
return;
}
// Serial.print("before new; free stack: "); Serial.println(uxTaskGetStackHighWaterMark( NULL ), DEC); // DEBUG
unsigned char* buf = new unsigned char[BUF_LENGTH];
unsigned int buf_pos;
Serial.print("Allocated buffer at: "); Serial.println((int)buf,HEX);
// Serial.print("after new; free stack: "); Serial.println(uxTaskGetStackHighWaterMark( NULL ), DEC); // DEBUG
i2s_start(i2s_num);
while (SDCard::available(file)) {
// Serial.print("chunk free stack: "); Serial.println(uxTaskGetStackHighWaterMark( NULL ), DEC); // DEBUG
// get chunk of data
SDCard::read(file, buf, BUF_LENGTH); // overwrite buffer
buf_pos = 0;
while (buf_pos < BUF_LENGTH) {
// Serial.print("i2s_push free stack: "); Serial.println(uxTaskGetStackHighWaterMark( NULL ), DEC); // DEBUG
uint16_t *buf16 = (uint16_t *) buf;
int buf16_pos = buf_pos/(sizeof(uint16_t)*2); // ToDo: read number of channels samplerate etc
// convert data
//unsigned int sample = ((unsigned short) DataSource::buffer[DataSource::buf_pos] << 16 & 0xffff0000) | ((unsigned short) DataSource::buffer[DataSource::buf_pos]);
unsigned int sample = ((unsigned short) buf16[buf16_pos] << 16 & 0xffff0000) | ((unsigned short) buf16[buf16_pos]);
// push samples
int num_pushed_bytes = i2s_push_sample(Sound::i2s_num, (char *)&sample, 0);
if (num_pushed_bytes == 0) {
Serial.println("i2s buf filled");
vTaskDelay(20 / portTICK_PERIOD_MS);
}
buf_pos += num_pushed_bytes;
}
// Buffer now empty
uint32_t ulNotifiedValue;
BaseType_t xResult = xTaskNotifyWait( pdFALSE, /* Don't clear bits on entry. */
ULONG_MAX, /* Clear all bits on exit. */
&ulNotifiedValue, /* Stores the notified value. */
0 ); /* xMaxBlockTime */
if( xResult == pdPASS ) { /* A notification was received */
if( ( ulNotifiedValue & EXIT_BIT ) != 0 ) { /* the EXIT BIT was set */
Serial.println("play_task: got notify. exiting.");
break;
}
}
//TickType_t delay = 10 / portTICK_PERIOD_MS; // max delay: 10ms instead of portMAX_DELAY
vTaskDelay(20 / portTICK_PERIOD_MS);
}
Serial.println("sdcard: EOF");
SDCard::close(file);
i2s_stop(i2s_num);
delete[] buf;
Serial.println("task exiting");
xTaskToNotify = NULL;
xSemaphoreGive( xPlayingSemaphore );
vTaskDelete( NULL );
}