├── LICENSE ├── README.md ├── playback ├── README.md ├── example_mp3.h └── playback.ino ├── processing ├── README.md ├── processing.ino └── verblib.h ├── recording ├── README.md └── recording.ino ├── streaming ├── .gitignore ├── .vscode │ └── extensions.json ├── README.md ├── include │ └── README ├── lib │ └── README ├── platformio.ini ├── src │ ├── AudioStreamer.cpp │ ├── AudioStreamer.h │ ├── WebServer.cpp │ ├── WebServer.h │ ├── html.h │ ├── main.cpp │ └── secrets.h └── test │ └── README └── v2.x.x ├── README.md ├── playback ├── README.md ├── example_mp3.h └── playback.ino └── processing ├── README.md ├── processing.ino └── verblib.h /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Lumination Labs, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sonatino Examples 2 | 3 | This repository contains sample code for the [Sonatino](https://sonatino.com) audio development board. 4 | 5 | Documentation for Sonatino can be found [here](https://sonatino.com/docs). 6 | 7 | **Note**: For board revisions prior to 3.x.x, use the examples in the [v2.x.x](v2.x.x/) folder. Your revision number can be found on the bottom side of the board. 8 | 9 | --- 10 | 11 | ## Included Example Projects: 12 | 13 | | Name | Environment | Description | 14 | | ------------------------- | ----------- | ------------------------------------------------------------------ | 15 | | [playback](playback/) | Arduino IDE | Demonstrates how to play an MP3 file | 16 | | [processing](processing/) | Arduino IDE | Demonstrates real-time audio processing with a basic reverb effect | 17 | | [recording](recording/) | Arduino IDE | Demonstrates recording audio to a microSD card | 18 | | [streaming](streaming/) | PlatformIO | Demonstrates receiving an audio stream over WiFi | 19 | 20 | ## Running The Examples 21 | 22 | Each example project is designed to run in a specific environment. The following instructions will guide you through running the examples in the respective environments. 23 | 24 | - Arduino IDE Projects 25 | 26 | - To run an example, open the `.ino` file with Arduino IDE, connect Sonatino using a USB-C cable, and press **Upload**. 27 | - Note: You'll need to first install [Arduino-ESP32](https://docs.espressif.com/projects/arduino-esp32/en/latest/installing.html#installing-using-arduino-ide) and the [ESP8266Audio](https://github.com/earlephilhower/ESP8266Audio) library if you haven't already done so. 28 | 29 | - PlatformIO Projects 30 | - Open the folder containing the example in PlatformIO, and it should install the necessary libraries and platform (espressif32). Connect Sonatino using a USB-C cable, then build and upload the project. 31 | 32 | --- 33 | 34 | For more information, refer to the **Getting Started** section of the Sonatino documentation. 35 | -------------------------------------------------------------------------------- /playback/README.md: -------------------------------------------------------------------------------- 1 | # Audio Playback Example 2 | 3 | This example demonstrates how to play an MP3 file, either from program memory or from a microSD card. 4 | -------------------------------------------------------------------------------- /playback/playback.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | // MP3 example excerpt from: 11 | // "Impact Andante" 12 | // Kevin MacLeod (incompetech.com) 13 | // Licensed under Creative Commons: By Attribution 3.0 14 | // http://creativecommons.org/licenses/by/3.0/ 15 | #include "example_mp3.h" 16 | 17 | // Or play an MP3 file from the microSD card: 18 | #define USE_MICROSD_CARD false 19 | #define FILE_PATH "/test.mp3" 20 | 21 | // Audio files can also be flashed to the ESP32-S3's flash memory (SPIFFS, FatFs, etc.) 22 | // See https://github.com/earlephilhower/ESP8266Audio/tree/master/examples for more examples 23 | 24 | 25 | #define DAC_BCLK_PIN 15 26 | #define DAC_LRCLK_PIN 16 27 | #define DAC_DATA_PIN 17 28 | #define MICROSD_SPI_SS_PIN 41 29 | #define MICROSD_SPI_SCK_PIN 42 30 | #define MICROSD_SPI_MOSI_PIN 43 31 | #define MICROSD_SPI_MISO_PIN 44 32 | #define RGB_LED_PIN 45 33 | 34 | 35 | AudioGeneratorMP3a *mp3; 36 | AudioFileSourcePROGMEM *file; 37 | AudioFileSourceSD *sd_file; 38 | AudioOutputI2S *dac; 39 | unsigned long last_led_blink = 0; 40 | int led_color_index = 0; 41 | int colors[][3] = { 42 | {255, 0, 0}, // Red 43 | {0, 255, 0}, // Green 44 | {0, 0, 255}, // Blue 45 | {255, 255, 0}, // Yellow 46 | {255, 0, 255} // Magenta 47 | }; 48 | 49 | 50 | void playMP3() { 51 | if (USE_MICROSD_CARD) { 52 | sd_file = new AudioFileSourceSD(FILE_PATH); 53 | mp3->begin(sd_file, dac); 54 | } else { 55 | file = new AudioFileSourcePROGMEM(example_data, sizeof(example_data)); 56 | mp3->begin(file, dac); 57 | } 58 | Serial.println("Playing MP3..."); 59 | } 60 | 61 | void setup() { 62 | // "USB CDC On Boot" must be enabled 63 | Serial.begin(115200); 64 | WiFi.mode(WIFI_OFF); 65 | 66 | pinMode(RGB_LED_PIN, OUTPUT); 67 | 68 | if (USE_MICROSD_CARD) { 69 | // Set up access to microSD card 70 | SPI.begin(MICROSD_SPI_SCK_PIN, MICROSD_SPI_MISO_PIN, MICROSD_SPI_MOSI_PIN, MICROSD_SPI_SS_PIN); 71 | if (!SD.begin(MICROSD_SPI_SS_PIN)) { 72 | while (1) { 73 | Serial.println("Card mount failed"); 74 | delay(1000); 75 | } 76 | } 77 | } 78 | 79 | dac = new AudioOutputI2S(); 80 | dac->SetPinout(DAC_BCLK_PIN, DAC_LRCLK_PIN, DAC_DATA_PIN); 81 | 82 | // Output can be very loud, so we're setting the gain less than 1 83 | dac->SetGain(0.1); 84 | mp3 = new AudioGeneratorMP3a(); 85 | playMP3(); 86 | 87 | Serial.println("Ready"); 88 | } 89 | 90 | void loop() { 91 | if (mp3->isRunning()) { 92 | if (!mp3->loop()) { 93 | mp3->stop(); 94 | Serial.println("Done"); 95 | 96 | // Play the file again 97 | playMP3(); 98 | } 99 | } 100 | 101 | // Cycle through some LED colors 102 | if (millis() - last_led_blink > 1000) { 103 | last_led_blink = millis(); 104 | 105 | neopixelWrite( 106 | RGB_LED_PIN, 107 | colors[led_color_index][0], 108 | colors[led_color_index][1], 109 | colors[led_color_index][2] 110 | ); 111 | 112 | led_color_index = (led_color_index + 1) % (sizeof(colors) / sizeof(colors[0])); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /processing/README.md: -------------------------------------------------------------------------------- 1 | # Audio Processing Example 2 | 3 | This example demonstrates real-time audio processing with a basic reverb effect. 4 | -------------------------------------------------------------------------------- /processing/processing.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define VERBLIB_IMPLEMENTATION 5 | #include "verblib.h" 6 | 7 | // To use this example, you must connect an audio input source (e.g. microphone, 8 | // line-in) and an audio output device to Sonatino using a 3.5mm TRRS cable. A 9 | // headset device with a microphone is a good choice, but you can also use a 10 | // TRRS adapter to connect separate audio devices. 11 | 12 | // Be sure to read the Sonatino documentation and set the board's level 13 | // selection DIP switches appropriately for your input and output levels. 14 | 15 | 16 | #define BUFFER_SAMPLES 50 17 | #define CHANNELS 2 18 | #define USE_REVERB true 19 | #define SAMPLE_RATE 48000 20 | 21 | #define MCLK_PIN 0 22 | #define DAC_BCLK_PIN 15 23 | #define DAC_LRCLK_PIN 16 24 | #define DAC_DATA_PIN 17 25 | #define ADC_BCLK_PIN 38 26 | #define ADC_LRCLK_PIN 39 27 | #define ADC_DATA_PIN 40 28 | 29 | float output_gain = 0.3; // Set output gain 30 | 31 | int16_t input_buffer[BUFFER_SAMPLES]; 32 | float filter_buffer[BUFFER_SAMPLES * CHANNELS]; 33 | verblib rv; 34 | 35 | void processAudio(void *pvParameters) { 36 | size_t bytes_read = 0; 37 | float input_sample; 38 | int16_t out_sample[2]; 39 | 40 | while (1) { 41 | i2s_read(I2S_NUM_1, input_buffer, BUFFER_SAMPLES * sizeof(int16_t), 42 | &bytes_read, portMAX_DELAY); 43 | 44 | if (bytes_read > 0) { 45 | // Read mono samples and copy into the stereo filter_buffer 46 | for (uint32_t i = 0; i < bytes_read / sizeof(int16_t); i++) { 47 | input_sample = 48 | (float)input_buffer[i] / (float)std::numeric_limits::max(); 49 | 50 | filter_buffer[i * CHANNELS] = input_sample; 51 | filter_buffer[i * CHANNELS + 1] = input_sample; 52 | } 53 | 54 | if (USE_REVERB) { 55 | verblib_process(&rv, filter_buffer, filter_buffer, bytes_read / 2); 56 | } 57 | 58 | // Send samples to output 59 | for (uint32_t i = 0; i < bytes_read / sizeof(int16_t) * CHANNELS; 60 | i += CHANNELS) { 61 | out_sample[0] = filter_buffer[i] * 62 | (float)std::numeric_limits::max() * 63 | output_gain; 64 | out_sample[1] = filter_buffer[i + 1] * 65 | (float)std::numeric_limits::max() * 66 | output_gain; 67 | 68 | size_t bytes_written; 69 | i2s_write(I2S_NUM_0, out_sample, sizeof(out_sample), &bytes_written, 70 | portMAX_DELAY); 71 | } 72 | } 73 | } 74 | } 75 | 76 | void setup() { 77 | Serial.begin(115200); 78 | WiFi.mode(WIFI_OFF); 79 | 80 | verblib_initialize(&rv, SAMPLE_RATE, CHANNELS); 81 | 82 | // Configure audio output (DAC) 83 | i2s_config_t i2s_config_out = { 84 | .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), 85 | .sample_rate = SAMPLE_RATE, 86 | .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, 87 | .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, 88 | .communication_format = I2S_COMM_FORMAT_I2S_MSB, 89 | .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, 90 | .dma_buf_count = 8, 91 | .dma_buf_len = 128, 92 | .use_apll = 1, 93 | .tx_desc_auto_clear = true, 94 | }; 95 | i2s_pin_config_t pin_config_out = {.mck_io_num = MCLK_PIN, 96 | .bck_io_num = DAC_BCLK_PIN, 97 | .ws_io_num = DAC_LRCLK_PIN, 98 | .data_out_num = DAC_DATA_PIN, 99 | .data_in_num = I2S_PIN_NO_CHANGE}; 100 | esp_err_t err; 101 | err = i2s_driver_install(I2S_NUM_0, &i2s_config_out, 0, NULL); 102 | if (err != ESP_OK) { 103 | while (1) { 104 | Serial.println("Failed to configure I2S driver for audio output"); 105 | delay(1000); 106 | } 107 | } 108 | err = i2s_set_pin(I2S_NUM_0, &pin_config_out); 109 | if (err != ESP_OK) { 110 | while (1) { 111 | Serial.println("Failed to set I2S pins for audio output"); 112 | delay(1000); 113 | } 114 | } 115 | i2s_zero_dma_buffer(I2S_NUM_0); 116 | 117 | // Configure audio input (ADC) 118 | i2s_config_t i2s_config_in = { 119 | .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX), 120 | .sample_rate = SAMPLE_RATE, 121 | .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, 122 | .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, 123 | .communication_format = I2S_COMM_FORMAT_I2S_MSB, 124 | .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, 125 | .dma_buf_count = 8, 126 | .dma_buf_len = 1024, 127 | .use_apll = 1, 128 | }; 129 | i2s_pin_config_t pin_config_in = {.mck_io_num = MCLK_PIN, 130 | .bck_io_num = ADC_BCLK_PIN, 131 | .ws_io_num = ADC_LRCLK_PIN, 132 | .data_out_num = I2S_PIN_NO_CHANGE, 133 | .data_in_num = ADC_DATA_PIN}; 134 | err = i2s_driver_install(I2S_NUM_1, &i2s_config_in, 0, NULL); 135 | if (err != ESP_OK) { 136 | while (1) { 137 | Serial.println("Failed to configure I2S driver for audio input"); 138 | delay(1000); 139 | } 140 | } 141 | err = i2s_set_pin(I2S_NUM_1, &pin_config_in); 142 | if (err != ESP_OK) { 143 | while (1) { 144 | Serial.println("Failed to set I2S pins for audio input"); 145 | delay(1000); 146 | } 147 | } 148 | 149 | // Start audio processing task 150 | xTaskCreatePinnedToCore(processAudio, "processAudio", 4096, NULL, 1, NULL, 0); 151 | 152 | Serial.println("Ready"); 153 | } 154 | 155 | void loop() {} 156 | -------------------------------------------------------------------------------- /processing/verblib.h: -------------------------------------------------------------------------------- 1 | /* Reverb Library 2 | * Verblib version 0.5 - 2022-10-25 3 | * 4 | * Philip Bennefall - philip@blastbay.com 5 | * 6 | * See the end of this file for licensing terms. 7 | * This reverb is based on Freeverb, a public domain reverb written by Jezar at Dreampoint. 8 | * 9 | * IMPORTANT: The reverb currently only works with 1 or 2 channels, at sample rates of 22050 HZ and above. 10 | * These restrictions may be lifted in a future version. 11 | * 12 | * USAGE 13 | * 14 | * This is a single-file library. To use it, do something like the following in one .c file. 15 | * #define VERBLIB_IMPLEMENTATION 16 | * #include "verblib.h" 17 | * 18 | * You can then #include this file in other parts of the program as you would with any other header file. 19 | */ 20 | 21 | #ifndef VERBLIB_H 22 | #define VERBLIB_H 23 | 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | 28 | /* COMPILE-TIME OPTIONS */ 29 | 30 | /* The maximum sample rate that should be supported, specified as a multiple of 44100. */ 31 | #ifndef verblib_max_sample_rate_multiplier 32 | #define verblib_max_sample_rate_multiplier 2 33 | #endif 34 | 35 | /* The silence threshold which is used when calculating decay time. */ 36 | #ifndef verblib_silence_threshold 37 | #define verblib_silence_threshold 80.0 /* In dB (absolute). */ 38 | #endif 39 | 40 | /* PUBLIC API */ 41 | 42 | typedef struct verblib verblib; 43 | 44 | /* Initialize a verblib structure. 45 | * 46 | * Call this function to initialize the verblib structure. 47 | * Returns nonzero (true) on success or 0 (false) on failure. 48 | * The function will only fail if one or more of the parameters are invalid. 49 | */ 50 | int verblib_initialize ( verblib* verb, unsigned long sample_rate, unsigned int channels ); 51 | 52 | /* Run the reverb. 53 | * 54 | * Call this function continuously to generate your output. 55 | * output_buffer may be the same pointer as input_buffer if in place processing is desired. 56 | * frames specifies the number of sample frames that should be processed. 57 | */ 58 | void verblib_process ( verblib* verb, const float* input_buffer, float* output_buffer, unsigned long frames ); 59 | 60 | /* Set the size of the room, between 0.0 and 1.0. */ 61 | void verblib_set_room_size ( verblib* verb, float value ); 62 | 63 | /* Get the size of the room. */ 64 | float verblib_get_room_size ( const verblib* verb ); 65 | 66 | /* Set the amount of damping, between 0.0 and 1.0. */ 67 | void verblib_set_damping ( verblib* verb, float value ); 68 | 69 | /* Get the amount of damping. */ 70 | float verblib_get_damping ( const verblib* verb ); 71 | 72 | /* Set the stereo width of the reverb, between 0.0 and 1.0. */ 73 | void verblib_set_width ( verblib* verb, float value ); 74 | 75 | /* Get the stereo width of the reverb. */ 76 | float verblib_get_width ( const verblib* verb ); 77 | 78 | /* Set the volume of the wet signal, between 0.0 and 1.0. */ 79 | void verblib_set_wet ( verblib* verb, float value ); 80 | 81 | /* Get the volume of the wet signal. */ 82 | float verblib_get_wet ( const verblib* verb ); 83 | 84 | /* Set the volume of the dry signal, between 0.0 and 1.0. */ 85 | void verblib_set_dry ( verblib* verb, float value ); 86 | 87 | /* Get the volume of the dry signal. */ 88 | float verblib_get_dry ( const verblib* verb ); 89 | 90 | /* Set the stereo width of the input signal sent to the reverb, 0.0 or greater. 91 | * Values less than 1.0 narrow the signal, 1.0 sends the input signal unmodified, values greater than 1.0 widen the signal. 92 | */ 93 | void verblib_set_input_width ( verblib* verb, float value ); 94 | 95 | /* Get the stereo width of the input signal sent to the reverb. */ 96 | float verblib_get_input_width ( const verblib* verb ); 97 | 98 | /* Set the mode of the reverb, where values below 0.5 mean normal and values above mean frozen. */ 99 | void verblib_set_mode ( verblib* verb, float value ); 100 | 101 | /* Get the mode of the reverb. */ 102 | float verblib_get_mode ( const verblib* verb ); 103 | 104 | /* Get the decay time in sample frames based on the current room size setting. */ 105 | /* If freeze mode is active, the decay time is infinite and this function returns 0. */ 106 | unsigned long verblib_get_decay_time_in_frames ( const verblib* verb ); 107 | 108 | /* INTERNAL STRUCTURES */ 109 | 110 | /* Allpass filter */ 111 | typedef struct verblib_allpass verblib_allpass; 112 | struct verblib_allpass 113 | { 114 | float* buffer; 115 | float feedback; 116 | int bufsize; 117 | int bufidx; 118 | }; 119 | 120 | /* Comb filter */ 121 | typedef struct verblib_comb verblib_comb; 122 | struct verblib_comb 123 | { 124 | float* buffer; 125 | float feedback; 126 | float filterstore; 127 | float damp1; 128 | float damp2; 129 | int bufsize; 130 | int bufidx; 131 | }; 132 | 133 | /* Reverb model tuning values */ 134 | #define verblib_numcombs 8 135 | #define verblib_numallpasses 4 136 | #define verblib_muted 0.0f 137 | #define verblib_fixedgain 0.015f 138 | #define verblib_scalewet 3.0f 139 | #define verblib_scaledry 2.0f 140 | #define verblib_scaledamp 0.8f 141 | #define verblib_scaleroom 0.28f 142 | #define verblib_offsetroom 0.7f 143 | #define verblib_initialroom 0.5f 144 | #define verblib_initialdamp 0.25f 145 | #define verblib_initialwet 1.0f/verblib_scalewet 146 | #define verblib_initialdry 0.0f 147 | #define verblib_initialwidth 1.0f 148 | #define verblib_initialinputwidth 0.0f 149 | #define verblib_initialmode 0.0f 150 | #define verblib_freezemode 0.5f 151 | #define verblib_stereospread 23 152 | 153 | /* 154 | * These values assume 44.1KHz sample rate, but will be verblib_scaled appropriately. 155 | * The values were obtained by listening tests. 156 | */ 157 | #define verblib_combtuningL1 1116 158 | #define verblib_combtuningR1 (1116+verblib_stereospread) 159 | #define verblib_combtuningL2 1188 160 | #define verblib_combtuningR2 (1188+verblib_stereospread) 161 | #define verblib_combtuningL3 1277 162 | #define verblib_combtuningR3 (1277+verblib_stereospread) 163 | #define verblib_combtuningL4 1356 164 | #define verblib_combtuningR4 (1356+verblib_stereospread) 165 | #define verblib_combtuningL5 1422 166 | #define verblib_combtuningR5 (1422+verblib_stereospread) 167 | #define verblib_combtuningL6 1491 168 | #define verblib_combtuningR6 (1491+verblib_stereospread) 169 | #define verblib_combtuningL7 1557 170 | #define verblib_combtuningR7 (1557+verblib_stereospread) 171 | #define verblib_combtuningL8 1617 172 | #define verblib_combtuningR8 (1617+verblib_stereospread) 173 | #define verblib_allpasstuningL1 556 174 | #define verblib_allpasstuningR1 (556+verblib_stereospread) 175 | #define verblib_allpasstuningL2 441 176 | #define verblib_allpasstuningR2 (441+verblib_stereospread) 177 | #define verblib_allpasstuningL3 341 178 | #define verblib_allpasstuningR3 (341+verblib_stereospread) 179 | #define verblib_allpasstuningL4 225 180 | #define verblib_allpasstuningR4 (225+verblib_stereospread) 181 | 182 | /* The main reverb structure. This is the structure that you will create an instance of when using the reverb. */ 183 | struct verblib 184 | { 185 | unsigned int channels; 186 | float gain; 187 | float roomsize, roomsize1; 188 | float damp, damp1; 189 | float wet, wet1, wet2; 190 | float dry; 191 | float width; 192 | float input_width; 193 | float mode; 194 | 195 | /* 196 | * The following are all declared inline 197 | * to remove the need for dynamic allocation. 198 | */ 199 | 200 | /* Comb filters */ 201 | verblib_comb combL[verblib_numcombs]; 202 | verblib_comb combR[verblib_numcombs]; 203 | 204 | /* Allpass filters */ 205 | verblib_allpass allpassL[verblib_numallpasses]; 206 | verblib_allpass allpassR[verblib_numallpasses]; 207 | 208 | /* Buffers for the combs */ 209 | float bufcombL1[verblib_combtuningL1* verblib_max_sample_rate_multiplier]; 210 | float bufcombR1[verblib_combtuningR1* verblib_max_sample_rate_multiplier]; 211 | float bufcombL2[verblib_combtuningL2* verblib_max_sample_rate_multiplier]; 212 | float bufcombR2[verblib_combtuningR2* verblib_max_sample_rate_multiplier]; 213 | float bufcombL3[verblib_combtuningL3* verblib_max_sample_rate_multiplier]; 214 | float bufcombR3[verblib_combtuningR3* verblib_max_sample_rate_multiplier]; 215 | float bufcombL4[verblib_combtuningL4* verblib_max_sample_rate_multiplier]; 216 | float bufcombR4[verblib_combtuningR4* verblib_max_sample_rate_multiplier]; 217 | float bufcombL5[verblib_combtuningL5* verblib_max_sample_rate_multiplier]; 218 | float bufcombR5[verblib_combtuningR5* verblib_max_sample_rate_multiplier]; 219 | float bufcombL6[verblib_combtuningL6* verblib_max_sample_rate_multiplier]; 220 | float bufcombR6[verblib_combtuningR6* verblib_max_sample_rate_multiplier]; 221 | float bufcombL7[verblib_combtuningL7* verblib_max_sample_rate_multiplier]; 222 | float bufcombR7[verblib_combtuningR7* verblib_max_sample_rate_multiplier]; 223 | float bufcombL8[verblib_combtuningL8* verblib_max_sample_rate_multiplier]; 224 | float bufcombR8[verblib_combtuningR8* verblib_max_sample_rate_multiplier]; 225 | 226 | /* Buffers for the allpasses */ 227 | float bufallpassL1[verblib_allpasstuningL1* verblib_max_sample_rate_multiplier]; 228 | float bufallpassR1[verblib_allpasstuningR1* verblib_max_sample_rate_multiplier]; 229 | float bufallpassL2[verblib_allpasstuningL2* verblib_max_sample_rate_multiplier]; 230 | float bufallpassR2[verblib_allpasstuningR2* verblib_max_sample_rate_multiplier]; 231 | float bufallpassL3[verblib_allpasstuningL3* verblib_max_sample_rate_multiplier]; 232 | float bufallpassR3[verblib_allpasstuningR3* verblib_max_sample_rate_multiplier]; 233 | float bufallpassL4[verblib_allpasstuningL4* verblib_max_sample_rate_multiplier]; 234 | float bufallpassR4[verblib_allpasstuningR4* verblib_max_sample_rate_multiplier]; 235 | }; 236 | 237 | #ifdef __cplusplus 238 | } 239 | #endif 240 | 241 | #endif /* VERBLIB_H */ 242 | 243 | /* IMPLEMENTATION */ 244 | 245 | #ifdef VERBLIB_IMPLEMENTATION 246 | 247 | #include 248 | #include 249 | 250 | #ifdef _MSC_VER 251 | #define VERBLIB_INLINE __forceinline 252 | #else 253 | #ifdef __GNUC__ 254 | #define VERBLIB_INLINE inline __attribute__((always_inline)) 255 | #else 256 | #define VERBLIB_INLINE inline 257 | #endif 258 | #endif 259 | 260 | #define verblib_max(x, y) (((x) > (y)) ? (x) : (y)) 261 | 262 | #define undenormalise(sample) sample+=1.0f; sample-=1.0f; 263 | 264 | /* Allpass filter */ 265 | static void verblib_allpass_initialize ( verblib_allpass* allpass, float* buf, int size ) 266 | { 267 | allpass->buffer = buf; 268 | allpass->bufsize = size; 269 | allpass->bufidx = 0; 270 | } 271 | 272 | static VERBLIB_INLINE float verblib_allpass_process ( verblib_allpass* allpass, float input ) 273 | { 274 | float output; 275 | float bufout; 276 | 277 | bufout = allpass->buffer[allpass->bufidx]; 278 | undenormalise ( bufout ); 279 | 280 | output = -input + bufout; 281 | allpass->buffer[allpass->bufidx] = input + ( bufout * allpass->feedback ); 282 | 283 | if ( ++allpass->bufidx >= allpass->bufsize ) 284 | { 285 | allpass->bufidx = 0; 286 | } 287 | 288 | return output; 289 | } 290 | 291 | static void verblib_allpass_mute ( verblib_allpass* allpass ) 292 | { 293 | int i; 294 | for ( i = 0; i < allpass->bufsize; i++ ) 295 | { 296 | allpass->buffer[i] = 0.0f; 297 | } 298 | } 299 | 300 | /* Comb filter */ 301 | static void verblib_comb_initialize ( verblib_comb* comb, float* buf, int size ) 302 | { 303 | comb->buffer = buf; 304 | comb->bufsize = size; 305 | comb->filterstore = 0.0f; 306 | comb->bufidx = 0; 307 | } 308 | 309 | static void verblib_comb_mute ( verblib_comb* comb ) 310 | { 311 | int i; 312 | for ( i = 0; i < comb->bufsize; i++ ) 313 | { 314 | comb->buffer[i] = 0.0f; 315 | } 316 | } 317 | 318 | static void verblib_comb_set_damp ( verblib_comb* comb, float val ) 319 | { 320 | comb->damp1 = val; 321 | comb->damp2 = 1.0f - val; 322 | } 323 | 324 | static VERBLIB_INLINE float verblib_comb_process ( verblib_comb* comb, float input ) 325 | { 326 | float output; 327 | 328 | output = comb->buffer[comb->bufidx]; 329 | undenormalise ( output ); 330 | 331 | comb->filterstore = ( output * comb->damp2 ) + ( comb->filterstore * comb->damp1 ); 332 | undenormalise ( comb->filterstore ); 333 | 334 | comb->buffer[comb->bufidx] = input + ( comb->filterstore * comb->feedback ); 335 | 336 | if ( ++comb->bufidx >= comb->bufsize ) 337 | { 338 | comb->bufidx = 0; 339 | } 340 | 341 | return output; 342 | } 343 | 344 | static void verblib_update ( verblib* verb ) 345 | { 346 | /* Recalculate internal values after parameter change. */ 347 | 348 | int i; 349 | 350 | verb->wet1 = verb->wet * ( verb->width / 2.0f + 0.5f ); 351 | verb->wet2 = verb->wet * ( ( 1.0f - verb->width ) / 2.0f ); 352 | 353 | if ( verb->mode >= verblib_freezemode ) 354 | { 355 | verb->roomsize1 = 1.0f; 356 | verb->damp1 = 0.0f; 357 | verb->gain = verblib_muted; 358 | } 359 | else 360 | { 361 | verb->roomsize1 = verb->roomsize; 362 | verb->damp1 = verb->damp; 363 | verb->gain = verblib_fixedgain; 364 | } 365 | 366 | for ( i = 0; i < verblib_numcombs; i++ ) 367 | { 368 | verb->combL[i].feedback = verb->roomsize1; 369 | verb->combR[i].feedback = verb->roomsize1; 370 | verblib_comb_set_damp ( &verb->combL[i], verb->damp1 ); 371 | verblib_comb_set_damp ( &verb->combR[i], verb->damp1 ); 372 | } 373 | 374 | } 375 | 376 | static void verblib_mute ( verblib* verb ) 377 | { 378 | int i; 379 | if ( verblib_get_mode ( verb ) >= verblib_freezemode ) 380 | { 381 | return; 382 | } 383 | 384 | for ( i = 0; i < verblib_numcombs; i++ ) 385 | { 386 | verblib_comb_mute ( &verb->combL[i] ); 387 | verblib_comb_mute ( &verb->combR[i] ); 388 | } 389 | for ( i = 0; i < verblib_numallpasses; i++ ) 390 | { 391 | verblib_allpass_mute ( &verb->allpassL[i] ); 392 | verblib_allpass_mute ( &verb->allpassR[i] ); 393 | } 394 | } 395 | 396 | static int verblib_get_verblib_scaled_buffer_size ( unsigned long sample_rate, unsigned long value ) 397 | { 398 | long double result = ( long double ) sample_rate; 399 | result /= 44100.0; 400 | result = ( ( long double ) value ) * result; 401 | if ( result < 1.0 ) 402 | { 403 | result = 1.0; 404 | } 405 | return ( int ) result; 406 | } 407 | 408 | int verblib_initialize ( verblib* verb, unsigned long sample_rate, unsigned int channels ) 409 | { 410 | int i; 411 | 412 | if ( channels != 1 && channels != 2 ) 413 | { 414 | return 0; /* Currently supports only 1 or 2 channels. */ 415 | } 416 | if ( sample_rate < 22050 ) 417 | { 418 | return 0; /* The minimum supported sample rate is 22050 HZ. */ 419 | } 420 | else if ( sample_rate > 44100 * verblib_max_sample_rate_multiplier ) 421 | { 422 | return 0; /* The sample rate is too high. */ 423 | } 424 | 425 | verb->channels = channels; 426 | 427 | /* Tie the components to their buffers. */ 428 | verblib_comb_initialize ( &verb->combL[0], verb->bufcombL1, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningL1 ) ); 429 | verblib_comb_initialize ( &verb->combR[0], verb->bufcombR1, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningR1 ) ); 430 | verblib_comb_initialize ( &verb->combL[1], verb->bufcombL2, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningL2 ) ); 431 | verblib_comb_initialize ( &verb->combR[1], verb->bufcombR2, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningR2 ) ); 432 | verblib_comb_initialize ( &verb->combL[2], verb->bufcombL3, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningL3 ) ); 433 | verblib_comb_initialize ( &verb->combR[2], verb->bufcombR3, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningR3 ) ); 434 | verblib_comb_initialize ( &verb->combL[3], verb->bufcombL4, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningL4 ) ); 435 | verblib_comb_initialize ( &verb->combR[3], verb->bufcombR4, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningR4 ) ); 436 | verblib_comb_initialize ( &verb->combL[4], verb->bufcombL5, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningL5 ) ); 437 | verblib_comb_initialize ( &verb->combR[4], verb->bufcombR5, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningR5 ) ); 438 | verblib_comb_initialize ( &verb->combL[5], verb->bufcombL6, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningL6 ) ); 439 | verblib_comb_initialize ( &verb->combR[5], verb->bufcombR6, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningR6 ) ); 440 | verblib_comb_initialize ( &verb->combL[6], verb->bufcombL7, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningL7 ) ); 441 | verblib_comb_initialize ( &verb->combR[6], verb->bufcombR7, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningR7 ) ); 442 | verblib_comb_initialize ( &verb->combL[7], verb->bufcombL8, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningL8 ) ); 443 | verblib_comb_initialize ( &verb->combR[7], verb->bufcombR8, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningR8 ) ); 444 | 445 | verblib_allpass_initialize ( &verb->allpassL[0], verb->bufallpassL1, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_allpasstuningL1 ) ); 446 | verblib_allpass_initialize ( &verb->allpassR[0], verb->bufallpassR1, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_allpasstuningR1 ) ); 447 | verblib_allpass_initialize ( &verb->allpassL[1], verb->bufallpassL2, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_allpasstuningL2 ) ); 448 | verblib_allpass_initialize ( &verb->allpassR[1], verb->bufallpassR2, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_allpasstuningR2 ) ); 449 | verblib_allpass_initialize ( &verb->allpassL[2], verb->bufallpassL3, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_allpasstuningL3 ) ); 450 | verblib_allpass_initialize ( &verb->allpassR[2], verb->bufallpassR3, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_allpasstuningR3 ) ); 451 | verblib_allpass_initialize ( &verb->allpassL[3], verb->bufallpassL4, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_allpasstuningL4 ) ); 452 | verblib_allpass_initialize ( &verb->allpassR[3], verb->bufallpassR4, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_allpasstuningR4 ) ); 453 | 454 | /* Set default values. */ 455 | for ( i = 0; i < verblib_numallpasses; i++ ) 456 | { 457 | verb->allpassL[i].feedback = 0.5f; 458 | verb->allpassR[i].feedback = 0.5f; 459 | } 460 | 461 | verblib_set_wet ( verb, verblib_initialwet ); 462 | verblib_set_room_size ( verb, verblib_initialroom ); 463 | verblib_set_dry ( verb, verblib_initialdry ); 464 | verblib_set_damping ( verb, verblib_initialdamp ); 465 | verblib_set_width ( verb, verblib_initialwidth ); 466 | verblib_set_input_width ( verb, verblib_initialinputwidth ); 467 | verblib_set_mode ( verb, verblib_initialmode ); 468 | 469 | /* The buffers will be full of rubbish - so we MUST mute them. */ 470 | verblib_mute ( verb ); 471 | 472 | return 1; 473 | } 474 | 475 | void verblib_process ( verblib* verb, const float* input_buffer, float* output_buffer, unsigned long frames ) 476 | { 477 | int i; 478 | float outL, outR, input; 479 | 480 | if ( verb->channels == 1 ) 481 | { 482 | while ( frames-- > 0 ) 483 | { 484 | outL = 0.0f; 485 | input = ( input_buffer[0] * 2.0f ) * verb->gain; 486 | 487 | /* Accumulate comb filters in parallel. */ 488 | for ( i = 0; i < verblib_numcombs; i++ ) 489 | { 490 | outL += verblib_comb_process ( &verb->combL[i], input ); 491 | } 492 | 493 | /* Feed through allpasses in series. */ 494 | for ( i = 0; i < verblib_numallpasses; i++ ) 495 | { 496 | outL = verblib_allpass_process ( &verb->allpassL[i], outL ); 497 | } 498 | 499 | /* Calculate output REPLACING anything already there. */ 500 | output_buffer[0] = outL * verb->wet1 + input_buffer[0] * verb->dry; 501 | 502 | /* Increment sample pointers. */ 503 | ++input_buffer; 504 | ++output_buffer; 505 | } 506 | } 507 | else if ( verb->channels == 2 ) 508 | { 509 | if ( verb->input_width > 0.0f ) /* Stereo input is widened or narrowed. */ 510 | { 511 | 512 | /* 513 | * The stereo mid/side code is derived from: 514 | * https://www.musicdsp.org/en/latest/Effects/256-stereo-width-control-obtained-via-transfromation-matrix.html 515 | * The description of the code on the above page says: 516 | * 517 | * This work is hereby placed in the public domain for all purposes, including 518 | * use in commercial applications. 519 | */ 520 | 521 | const float tmp = 1 / verblib_max ( 1 + verb->input_width, 2 ); 522 | const float coef_mid = 1 * tmp; 523 | const float coef_side = verb->input_width * tmp; 524 | while ( frames-- > 0 ) 525 | { 526 | const float mid = ( input_buffer[0] + input_buffer[1] ) * coef_mid; 527 | const float side = ( input_buffer[1] - input_buffer[0] ) * coef_side; 528 | const float input_left = ( mid - side ) * ( verb->gain * 2.0f ); 529 | const float input_right = ( mid + side ) * ( verb->gain * 2.0f ); 530 | 531 | outL = outR = 0.0f; 532 | 533 | /* Accumulate comb filters in parallel. */ 534 | for ( i = 0; i < verblib_numcombs; i++ ) 535 | { 536 | outL += verblib_comb_process ( &verb->combL[i], input_left ); 537 | outR += verblib_comb_process ( &verb->combR[i], input_right ); 538 | } 539 | 540 | /* Feed through allpasses in series. */ 541 | for ( i = 0; i < verblib_numallpasses; i++ ) 542 | { 543 | outL = verblib_allpass_process ( &verb->allpassL[i], outL ); 544 | outR = verblib_allpass_process ( &verb->allpassR[i], outR ); 545 | } 546 | 547 | /* Calculate output REPLACING anything already there. */ 548 | output_buffer[0] = outL * verb->wet1 + outR * verb->wet2 + input_buffer[0] * verb->dry; 549 | output_buffer[1] = outR * verb->wet1 + outL * verb->wet2 + input_buffer[1] * verb->dry; 550 | 551 | /* Increment sample pointers. */ 552 | input_buffer += 2; 553 | output_buffer += 2; 554 | } 555 | } 556 | else /* Stereo input is summed to mono. */ 557 | { 558 | while ( frames-- > 0 ) 559 | { 560 | outL = outR = 0.0f; 561 | input = ( input_buffer[0] + input_buffer[1] ) * verb->gain; 562 | 563 | /* Accumulate comb filters in parallel. */ 564 | for ( i = 0; i < verblib_numcombs; i++ ) 565 | { 566 | outL += verblib_comb_process ( &verb->combL[i], input ); 567 | outR += verblib_comb_process ( &verb->combR[i], input ); 568 | } 569 | 570 | /* Feed through allpasses in series. */ 571 | for ( i = 0; i < verblib_numallpasses; i++ ) 572 | { 573 | outL = verblib_allpass_process ( &verb->allpassL[i], outL ); 574 | outR = verblib_allpass_process ( &verb->allpassR[i], outR ); 575 | } 576 | 577 | /* Calculate output REPLACING anything already there. */ 578 | output_buffer[0] = outL * verb->wet1 + outR * verb->wet2 + input_buffer[0] * verb->dry; 579 | output_buffer[1] = outR * verb->wet1 + outL * verb->wet2 + input_buffer[1] * verb->dry; 580 | 581 | /* Increment sample pointers. */ 582 | input_buffer += 2; 583 | output_buffer += 2; 584 | } 585 | } 586 | } 587 | } 588 | 589 | void verblib_set_room_size ( verblib* verb, float value ) 590 | { 591 | verb->roomsize = ( value * verblib_scaleroom ) + verblib_offsetroom; 592 | verblib_update ( verb ); 593 | } 594 | 595 | float verblib_get_room_size ( const verblib* verb ) 596 | { 597 | return ( verb->roomsize - verblib_offsetroom ) / verblib_scaleroom; 598 | } 599 | 600 | void verblib_set_damping ( verblib* verb, float value ) 601 | { 602 | verb->damp = value * verblib_scaledamp; 603 | verblib_update ( verb ); 604 | } 605 | 606 | float verblib_get_damping ( const verblib* verb ) 607 | { 608 | return verb->damp / verblib_scaledamp; 609 | } 610 | 611 | void verblib_set_wet ( verblib* verb, float value ) 612 | { 613 | verb->wet = value * verblib_scalewet; 614 | verblib_update ( verb ); 615 | } 616 | 617 | float verblib_get_wet ( const verblib* verb ) 618 | { 619 | return verb->wet / verblib_scalewet; 620 | } 621 | 622 | void verblib_set_dry ( verblib* verb, float value ) 623 | { 624 | verb->dry = value * verblib_scaledry; 625 | } 626 | 627 | float verblib_get_dry ( const verblib* verb ) 628 | { 629 | return verb->dry / verblib_scaledry; 630 | } 631 | 632 | void verblib_set_width ( verblib* verb, float value ) 633 | { 634 | verb->width = value; 635 | verblib_update ( verb ); 636 | } 637 | 638 | float verblib_get_width ( const verblib* verb ) 639 | { 640 | return verb->width; 641 | } 642 | 643 | void verblib_set_input_width ( verblib* verb, float value ) 644 | { 645 | verb->input_width = value; 646 | } 647 | 648 | float verblib_get_input_width ( const verblib* verb ) 649 | { 650 | return verb->input_width; 651 | } 652 | 653 | void verblib_set_mode ( verblib* verb, float value ) 654 | { 655 | verb->mode = value; 656 | verblib_update ( verb ); 657 | } 658 | 659 | float verblib_get_mode ( const verblib* verb ) 660 | { 661 | if ( verb->mode >= verblib_freezemode ) 662 | { 663 | return 1.0f; 664 | } 665 | return 0.0f; 666 | } 667 | 668 | unsigned long verblib_get_decay_time_in_frames ( const verblib* verb ) 669 | { 670 | double decay; 671 | 672 | if ( verb->mode >= verblib_freezemode ) 673 | { 674 | return 0; /* Freeze mode creates an infinite decay. */ 675 | } 676 | 677 | decay = verblib_silence_threshold / fabs ( -20.0 * log ( 1.0 / verb->roomsize1 ) ); 678 | decay *= ( double ) ( verb->combR[7].bufsize * 2 ); 679 | return ( unsigned long ) decay; 680 | } 681 | 682 | #endif /* VERBLIB_IMPLEMENTATION */ 683 | 684 | /* REVISION HISTORY 685 | * 686 | * Version 0.5 - 2022-10-25 687 | * Added two functions called verblib_set_input_width and verblib_get_input_width. 688 | * 689 | * Version 0.4 - 2021-01-23 690 | * Added a function called verblib_get_decay_time_in_frames. 691 | * 692 | * Version 0.3 - 2021-01-18 693 | * Added support for sample rates of 22050 and above. 694 | * 695 | * Version 0.2 - 2021-01-17 696 | * Added support for processing mono audio. 697 | * 698 | * Version 0.1 - 2021-01-17 699 | * Initial release. 700 | */ 701 | 702 | /* LICENSE 703 | 704 | This software is available under 2 licenses -- choose whichever you prefer. 705 | ------------------------------------------------------------------------------ 706 | ALTERNATIVE A - MIT No Attribution License 707 | Copyright (c) 2022 Philip Bennefall 708 | 709 | Permission is hereby granted, free of charge, to any person obtaining a copy of 710 | this software and associated documentation files (the "Software"), to deal in 711 | the Software without restriction, including without limitation the rights to 712 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 713 | of the Software, and to permit persons to whom the Software is furnished to do 714 | so. 715 | 716 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 717 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 718 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 719 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 720 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 721 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 722 | SOFTWARE. 723 | ------------------------------------------------------------------------------ 724 | ALTERNATIVE B - Public Domain (www.unlicense.org) 725 | This is free and unencumbered software released into the public domain. 726 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 727 | software, either in source code form or as a compiled binary, for any purpose, 728 | commercial or non-commercial, and by any means. 729 | 730 | In jurisdictions that recognize copyright laws, the author or authors of this 731 | software dedicate any and all copyright interest in the software to the public 732 | domain. We make this dedication for the benefit of the public at large and to 733 | the detriment of our heirs and successors. We intend this dedication to be an 734 | overt act of relinquishment in perpetuity of all present and future rights to 735 | this software under copyright law. 736 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 737 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 738 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 739 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 740 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 741 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 742 | ------------------------------------------------------------------------------ 743 | */ -------------------------------------------------------------------------------- /recording/README.md: -------------------------------------------------------------------------------- 1 | # Audio Recording Example 2 | 3 | This example demonstrates audio recording to a microSD card on Sonatino. 4 | 5 | It records 10-second audio clips and saves them as WAV files to the microSD card. The LED blinks red while recording. When a clip is saved, it blinks blue for a short time, then resumes recording the next clip. 6 | 7 | Filenames follow the format `REC_XXXX.wav`, where `XXXX` is a 4-digit number starting with 0001. 8 | -------------------------------------------------------------------------------- /recording/recording.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | // To use this example, you must connect an audio input source (e.g. microphone, 7 | // line-in) and insert a microSD card (formatted with a FAT32 filesystem) into 8 | // the microSD card slot on Sonatino. 9 | 10 | // Be sure to read the Sonatino documentation and set the board's input level 11 | // selection DIP switch appropriately for your input source. 12 | 13 | #define BUFFER_SAMPLES 1024 14 | #define SAMPLE_RATE 48000 15 | #define CHANNELS 1 16 | #define RECORD_TIME_SECONDS 10 17 | #define FILENAME_PREFIX "/REC_" 18 | #define FILENAME_EXTENSION ".wav" 19 | #define TEMP_FILENAME "/TEMP.wav" 20 | 21 | #define MCLK_PIN 0 22 | #define ADC_BCLK_PIN 38 23 | #define ADC_LRCLK_PIN 39 24 | #define ADC_DATA_PIN 40 25 | #define MICROSD_SPI_SS_PIN 41 26 | #define MICROSD_SPI_SCK_PIN 42 27 | #define MICROSD_SPI_MOSI_PIN 43 28 | #define MICROSD_SPI_MISO_PIN 44 29 | #define RGB_LED_PIN 45 30 | 31 | int16_t input_buffer[BUFFER_SAMPLES]; 32 | File audioFile; 33 | volatile uint32_t totalBytesWritten = 0; 34 | volatile uint32_t recordingStartTime = 0; 35 | volatile uint16_t fileIndex = 1; 36 | volatile bool ledState = false; 37 | 38 | void writeWavHeader(File &file, uint32_t sampleRate, uint16_t bitsPerSample, 39 | uint16_t channels, uint32_t totalDataLen) { 40 | uint32_t byteRate = sampleRate * channels * bitsPerSample / 8; 41 | uint16_t blockAlign = channels * bitsPerSample / 8; 42 | 43 | file.seek(0); 44 | file.write((const uint8_t *)"RIFF", 4); 45 | file.write((uint8_t *)&totalDataLen, 4); 46 | file.write((const uint8_t *)"WAVE", 4); 47 | file.write((const uint8_t *)"fmt ", 4); 48 | uint32_t subChunk1Size = 16; 49 | uint16_t audioFormat = 1; 50 | file.write((uint8_t *)&subChunk1Size, 4); 51 | file.write((uint8_t *)&audioFormat, 2); 52 | file.write((uint8_t *)&channels, 2); 53 | file.write((uint8_t *)&sampleRate, 4); 54 | file.write((uint8_t *)&byteRate, 4); 55 | file.write((uint8_t *)&blockAlign, 2); 56 | file.write((uint8_t *)&bitsPerSample, 2); 57 | file.write((const uint8_t *)"data", 4); 58 | uint32_t subChunk2Size = totalDataLen - 36; 59 | file.write((uint8_t *)&subChunk2Size, 4); 60 | } 61 | 62 | String getNextFilename() { 63 | char filename[20]; 64 | 65 | while (true) { 66 | snprintf(filename, sizeof(filename), 67 | FILENAME_PREFIX "%04d" FILENAME_EXTENSION, fileIndex); 68 | if (!SD.exists(filename)) { 69 | return String(filename); 70 | } 71 | fileIndex++; 72 | } 73 | } 74 | 75 | void closeWavFile() { 76 | // Update WAV header with the correct file size 77 | uint32_t fileSize = audioFile.size(); 78 | audioFile.seek(0); 79 | writeWavHeader(audioFile, SAMPLE_RATE, 16, CHANNELS, fileSize - 8); 80 | audioFile.close(); 81 | 82 | // Rename the temp file to the next sequential name 83 | String newFilename = getNextFilename(); 84 | SD.rename(TEMP_FILENAME, newFilename.c_str()); 85 | 86 | // Flash the LED blue quickly to indicate the file was saved 87 | for (int i = 0; i < 6; i++) { 88 | neopixelWrite(RGB_LED_PIN, 0, 0, 255); // Blue 89 | delay(100); 90 | neopixelWrite(RGB_LED_PIN, 0, 0, 0); // Off 91 | delay(100); 92 | } 93 | 94 | Serial.print("Saved recording: "); 95 | Serial.println(newFilename); 96 | } 97 | 98 | void startNewRecording() { 99 | // Start a new recording by opening TEMP_FILENAME and writing the WAV header 100 | audioFile = SD.open(TEMP_FILENAME, FILE_WRITE); 101 | if (!audioFile) { 102 | Serial.println("Failed to create temporary wav file"); 103 | while (1); 104 | } 105 | writeWavHeader(audioFile, SAMPLE_RATE, 16, CHANNELS, 0); 106 | totalBytesWritten = 0; 107 | recordingStartTime = millis(); 108 | } 109 | 110 | void processAudio(void *pvParameters) { 111 | uint32_t lastLedToggleTime = millis(); 112 | size_t bytes_read = 0; 113 | 114 | while (1) { 115 | // Toggle LED 116 | if (millis() - lastLedToggleTime >= 1000) { 117 | ledState = !ledState; 118 | if (ledState) { 119 | neopixelWrite(RGB_LED_PIN, 255, 0, 0); // Red 120 | } else { 121 | neopixelWrite(RGB_LED_PIN, 0, 0, 0); // Off 122 | } 123 | lastLedToggleTime = millis(); 124 | } 125 | 126 | // Read audio data 127 | esp_err_t read_err = i2s_read( 128 | I2S_NUM_1, input_buffer, BUFFER_SAMPLES * sizeof(int16_t), &bytes_read, 129 | 100 / portTICK_RATE_MS); // Use a non-blocking read with a timeout 130 | if (read_err == ESP_OK && bytes_read > 0) { 131 | audioFile.write((uint8_t *)input_buffer, bytes_read); 132 | totalBytesWritten += bytes_read; 133 | } 134 | 135 | // Check if RECORD_TIME_SECONDS have passed 136 | if (millis() - recordingStartTime >= RECORD_TIME_SECONDS * 1000) { 137 | closeWavFile(); 138 | startNewRecording(); 139 | } 140 | 141 | vTaskDelay(1); 142 | } 143 | } 144 | 145 | void setup() { 146 | Serial.begin(115200); 147 | WiFi.mode(WIFI_OFF); 148 | 149 | // Set up access to microSD card 150 | SPI.begin(MICROSD_SPI_SCK_PIN, MICROSD_SPI_MISO_PIN, MICROSD_SPI_MOSI_PIN, 151 | MICROSD_SPI_SS_PIN); 152 | if (!SD.begin(MICROSD_SPI_SS_PIN)) { 153 | while (1) { 154 | Serial.println("Card mount failed"); 155 | delay(1000); 156 | } 157 | } 158 | 159 | // Set up RGB LED 160 | pinMode(RGB_LED_PIN, OUTPUT); 161 | 162 | // Configure audio input 163 | i2s_config_t i2s_config_in = { 164 | .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX), 165 | .sample_rate = SAMPLE_RATE, 166 | .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, 167 | .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, 168 | .communication_format = I2S_COMM_FORMAT_STAND_I2S, 169 | .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, 170 | .dma_buf_count = 8, 171 | .dma_buf_len = BUFFER_SAMPLES, 172 | .use_apll = 1, 173 | }; 174 | i2s_pin_config_t pin_config_in = {.mck_io_num = MCLK_PIN, 175 | .bck_io_num = ADC_BCLK_PIN, 176 | .ws_io_num = ADC_LRCLK_PIN, 177 | .data_out_num = I2S_PIN_NO_CHANGE, 178 | .data_in_num = ADC_DATA_PIN}; 179 | esp_err_t err; 180 | err = i2s_driver_install(I2S_NUM_1, &i2s_config_in, 0, NULL); 181 | if (err != ESP_OK) { 182 | while (1) { 183 | Serial.println("Failed to configure I2S driver for audio input"); 184 | delay(1000); 185 | } 186 | } 187 | err = i2s_set_pin(I2S_NUM_1, &pin_config_in); 188 | if (err != ESP_OK) { 189 | while (1) { 190 | Serial.println("Failed to set I2S pins for audio input"); 191 | delay(1000); 192 | } 193 | } 194 | 195 | // Clear I2S buffer (prevents pop at beginning of first recording) 196 | size_t bytes_read = 0; 197 | for (int i = 0; i < 50; i++) { 198 | i2s_read(I2S_NUM_1, input_buffer, BUFFER_SAMPLES * sizeof(int16_t), 199 | &bytes_read, 100 / portTICK_RATE_MS); 200 | } 201 | 202 | // Start recording 203 | startNewRecording(); 204 | 205 | // Start audio processing task 206 | xTaskCreatePinnedToCore(processAudio, "processAudio", 4096, NULL, 1, NULL, 1); 207 | 208 | Serial.println("Recording..."); 209 | } 210 | 211 | void loop() {} 212 | -------------------------------------------------------------------------------- /streaming/.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /streaming/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": ["platformio.platformio-ide"] 5 | } 6 | -------------------------------------------------------------------------------- /streaming/README.md: -------------------------------------------------------------------------------- 1 | # Web Streaming Example 2 | 3 | This example demonstrates receiving Icecast audio streams over WiFi. It relies on [ESP32-audioI2S](https://github.com/schreibfaul1/ESP32-audioI2S) for playing the streams and [ESPAsyncWebServer](https://github.com/esphome/ESPAsyncWebServer) to serve a simple web interface for controlling playback. 4 | 5 | ## Setup 6 | 7 | This example needs to be built with [PlatformIO](https://platformio.org/) instead of Arduino IDE. 8 | 9 | After opening the project in PlatformIO, edit `src/secrets.h` to match your WiFi network's SSID and password. Then build and upload the project to Sonatino. 10 | 11 | ## Usage 12 | 13 | Your Sonatino's RGB LED will begin to blink **blue** until it connects to your WiFi network. Once connected, it will be a solid **green** color. Use the serial monitor to see the IP address of Sonatino on your network, and then navigate to the IP address in your web browser. 14 | 15 | The web interface allows you to enter an Icecast stream URL or select a random stream for a specific genre of music (using [radio-browser](https://www.radio-browser.info/)). Once you've entered a stream URL, click the "Start" button to start streaming the audio. 16 | 17 | ## Troubleshooting 18 | 19 | You may see the message "`slow stream, dropouts are possible`" in the event log for high bit rate streams. The ESP32-audioI2S project suggests this can be improved with optimized TCP settings, but changing these settings requires compiling Arduino as an ESP-IDF component (i.e., `framework = arduino, espidf` in your `platformio.ini` file) and adjusting settings in menuconfig. This process can be complex and is not covered in this example. For more information, refer to the ESP32-audioI2S [Better_WiFi_throughput example](https://github.com/schreibfaul1/ESP32-audioI2S/tree/master/examples/Better_WiFi_throughput). 20 | -------------------------------------------------------------------------------- /streaming/include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /streaming/lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /streaming/platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:sonatino] 12 | platform = espressif32 13 | board = esp32-s3-devkitc-1 14 | board_upload.flash_size = 16MB 15 | board_upload.maximum_size = 16777216 16 | board_build.arduino.memory_type = qio_opi 17 | framework = arduino 18 | lib_deps = 19 | bblanchon/ArduinoJson@^6.21.3 20 | esphome/ESP32-audioI2S@^2.0.7 21 | esphome/ESPAsyncWebServer-esphome@^3.1.0 22 | esphome/AsyncTCP-esphome@^2.1.3 23 | build_flags = 24 | -DBOARD_HAS_PSRAM 25 | -mfix-esp32-psram-cache-issue 26 | -DARDUINO_USB_MODE=1 27 | -DARDUINO_USB_CDC_ON_BOOT=1 28 | -------------------------------------------------------------------------------- /streaming/src/AudioStreamer.cpp: -------------------------------------------------------------------------------- 1 | #include "AudioStreamer.h" 2 | 3 | #include 4 | 5 | #include "WebServer.h" 6 | 7 | AudioStreamer::AudioStreamer(int _bclkPin, int _lrclkPin, int _dataPin) { 8 | bclkPin = _bclkPin; 9 | lrclkPin = _lrclkPin; 10 | dataPin = _dataPin; 11 | } 12 | 13 | void AudioStreamer::setup() { 14 | audio.setBufsize(32000, 655360); 15 | audio.setPinout(bclkPin, lrclkPin, dataPin, -1); 16 | audio.setVolume(5); 17 | audio.setConnectionTimeout(500, 2700); 18 | } 19 | 20 | void AudioStreamer::clearTitleInfo() { 21 | currentTitle = ""; 22 | currentArtist = ""; 23 | currentStation = ""; 24 | } 25 | 26 | void AudioStreamer::sendStatusUpdate() { 27 | DynamicJsonDocument doc(1024); 28 | doc["isPlaying"] = audio.isRunning(); 29 | doc["isPaused"] = isPaused; 30 | doc["volume"] = audio.getVolume(); 31 | doc["title"] = currentTitle; 32 | doc["artist"] = currentArtist; 33 | doc["station"] = currentStation; 34 | String response; 35 | serializeJson(doc, response); 36 | webServer->audioEvent("status", response); 37 | } 38 | 39 | void AudioStreamer::loop() { 40 | audio.loop(); 41 | 42 | // Periodically send status updates to the web client 43 | if (millis() - lastStatusTime > STATUS_INTERVAL_MS) { 44 | sendStatusUpdate(); 45 | lastStatusTime = millis(); 46 | } 47 | } 48 | 49 | void AudioStreamer::open(const char *url) { 50 | audio.stopSong(); 51 | clearTitleInfo(); 52 | isPaused = false; 53 | sendStatusUpdate(); 54 | audio.connecttohost(url); 55 | } 56 | 57 | void AudioStreamer::stop() { 58 | audio.stopSong(); 59 | clearTitleInfo(); 60 | isPaused = false; 61 | sendStatusUpdate(); 62 | } 63 | 64 | void AudioStreamer::pauseResume() { 65 | audio.pauseResume(); 66 | isPaused = !isPaused; 67 | sendStatusUpdate(); 68 | } 69 | 70 | void AudioStreamer::setVolume(int volume) { audio.setVolume(volume); } 71 | 72 | // Audio callbacks 73 | void audio_info(const char *info) { webServer->audioEvent("log", info); } 74 | 75 | void audio_id3data(const char *info) { 76 | if (strncmp(info, "Title: ", 7) == 0 && strlen(info) > 7) { 77 | audioStreamer->currentTitle = String(info + 7); 78 | audioStreamer->sendStatusUpdate(); 79 | } else if (strncmp(info, "Artist: ", 8) == 0 && strlen(info) > 8) { 80 | audioStreamer->currentArtist = String(info + 8); 81 | audioStreamer->sendStatusUpdate(); 82 | } 83 | } 84 | 85 | void audio_showstation(const char *info) { 86 | audioStreamer->currentStation = String(info); 87 | audioStreamer->sendStatusUpdate(); 88 | } 89 | void audio_showstreamtitle(const char *info) { 90 | audioStreamer->currentTitle = String(info); 91 | audioStreamer->sendStatusUpdate(); 92 | } 93 | 94 | // Global instance 95 | AudioStreamer *audioStreamer = nullptr; 96 | -------------------------------------------------------------------------------- /streaming/src/AudioStreamer.h: -------------------------------------------------------------------------------- 1 | #ifndef AUDIO_STREAMER_H 2 | #define AUDIO_STREAMER_H 3 | 4 | #include "Audio.h" 5 | 6 | #define STATUS_INTERVAL_MS 5000 7 | 8 | class AudioStreamer { 9 | public: 10 | AudioStreamer(int bclkPin, int lrclkPin, int dataPin); 11 | AudioStreamer(){}; 12 | void setup(); 13 | void loop(); 14 | void open(const char *url); 15 | void stop(); 16 | void pauseResume(); 17 | void setVolume(int volume); 18 | void sendStatusUpdate(); 19 | 20 | String currentTitle = ""; 21 | String currentArtist = ""; 22 | String currentStation = ""; 23 | 24 | private: 25 | void clearTitleInfo(); 26 | int bclkPin; 27 | int lrclkPin; 28 | int dataPin; 29 | Audio audio; 30 | bool isPaused = false; 31 | unsigned long lastStatusTime = 0; 32 | }; 33 | 34 | extern AudioStreamer *audioStreamer; 35 | 36 | #endif // AUDIO_STREAMER_H -------------------------------------------------------------------------------- /streaming/src/WebServer.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "WebServer.h" 3 | 4 | #include 5 | #include 6 | 7 | #include "html.h" 8 | 9 | WebServer::WebServer(int port) { 10 | server = new AsyncWebServer(port); 11 | events = new AsyncEventSource("/events"); 12 | } 13 | 14 | void WebServer::setup() { 15 | // Serve the HTML file 16 | server->on("/", HTTP_GET, [](AsyncWebServerRequest *request) { 17 | request->send(200, "text/html", htmlIndex); 18 | }); 19 | 20 | // Handle POST requests 21 | AsyncCallbackJsonWebHandler *jsonHandler = new AsyncCallbackJsonWebHandler( 22 | "/action", [&](AsyncWebServerRequest *request, JsonVariant &json) { 23 | const JsonObject &jsonObj = json.as(); 24 | String action = jsonObj["action"]; 25 | bool success = true; 26 | if (action == "open") { 27 | String urlStr = jsonObj["url"].as(); 28 | const char *url = urlStr.c_str(); 29 | audioStreamer->open(url); 30 | } else if (action == "stop") { 31 | audioStreamer->stop(); 32 | } else if (action == "volume") { 33 | int volume = jsonObj["volume"].as(); 34 | audioStreamer->setVolume(volume); 35 | } else if (action == "pause") { 36 | audioStreamer->pauseResume(); 37 | } else { 38 | success = false; 39 | } 40 | request->send( 41 | 200, "application/json", 42 | "{\"success\":" + String(success ? "true" : "false") + "}"); 43 | }); 44 | 45 | server->addHandler(jsonHandler); 46 | server->addHandler(events); 47 | server->begin(); 48 | } 49 | 50 | // Send event information to the web client 51 | void WebServer::audioEvent(String eventType, String eventData) { 52 | if (events) { 53 | DynamicJsonDocument doc(1024); 54 | doc["event"] = eventType; 55 | doc["data"] = eventData; 56 | String response; 57 | serializeJson(doc, response); 58 | events->send(response.c_str(), "message", millis()); 59 | } 60 | } 61 | 62 | // Global instance 63 | WebServer *webServer = nullptr; 64 | -------------------------------------------------------------------------------- /streaming/src/WebServer.h: -------------------------------------------------------------------------------- 1 | #ifndef WEB_SERVER_H 2 | #define WEB_SERVER_H 3 | 4 | #include 5 | 6 | #include "AudioStreamer.h" 7 | 8 | class WebServer { 9 | public: 10 | WebServer(int port); 11 | void setup(); 12 | void audioEvent(String eventType, String eventData); 13 | 14 | private: 15 | AsyncWebServer *server; 16 | AsyncEventSource *events; 17 | }; 18 | 19 | extern WebServer *webServer; 20 | 21 | #endif // WEB_SERVER_H -------------------------------------------------------------------------------- /streaming/src/html.h: -------------------------------------------------------------------------------- 1 | #ifndef HTML_H 2 | #define HTML_H 3 | 4 | const char* htmlIndex = R"rawliteral( 5 | 6 | 7 | 8 | 9 | 10 | Sonatino Streaming Example 11 | 164 | 165 | 166 |
167 |

Sonatino Streaming Example

168 |

Supports MP3 and AAC streams over HTTP/HTTPS

169 |
170 |
171 | Title: 172 |
173 |
174 | Station: 175 |
176 |
177 | Artist: 178 |
179 |
180 |
181 | 206 |
207 |
Searching...
208 |
209 | Find More Stations 215 |
216 |
217 | 218 |
219 | 220 | 221 | 222 |
223 | 224 | 225 |
226 |
Event Log
227 |
(nothing yet)
228 |
229 | 467 | 468 | 469 | )rawliteral"; 470 | 471 | #endif // HTML_H -------------------------------------------------------------------------------- /streaming/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "AudioStreamer.h" 5 | #include "WebServer.h" 6 | #include "esp_wifi.h" 7 | #include "secrets.h" 8 | 9 | #define DAC_BCLK_PIN 15 10 | #define DAC_LRCLK_PIN 16 11 | #define DAC_DATA_PIN 17 12 | #define RGB_LED_PIN 45 13 | #define SHOW_URL_INTERVAL_MS 10000 14 | 15 | AudioStreamer stream(DAC_BCLK_PIN, DAC_LRCLK_PIN, DAC_DATA_PIN); 16 | WebServer server(80); 17 | bool ledOn = false; 18 | unsigned long lastUrlTime = 0; 19 | 20 | void printUrlToSerial() { 21 | Serial.print("Open http://"); 22 | Serial.print(WiFi.localIP()); 23 | Serial.println("/ in your browser."); 24 | } 25 | 26 | void setup() { 27 | Serial.begin(115200); 28 | 29 | pinMode(RGB_LED_PIN, OUTPUT); 30 | WiFi.mode(WIFI_MODE_STA); 31 | 32 | // Remove or modify the following line if you need more WiFi range 33 | // (high transmit power can contribute to audio noise, depending on antenna) 34 | esp_wifi_set_max_tx_power(8); 35 | 36 | // Try to connect to the WiFi network while flashing the LED blue 37 | WiFi.begin(WIFI_SSID, WIFI_PASS); 38 | while (WiFi.status() != WL_CONNECTED) { 39 | neopixelWrite(RGB_LED_PIN, 0, 0, ledOn ? 25 : 0); 40 | Serial.println("Connecting to WiFi..."); 41 | ledOn = !ledOn; 42 | delay(500); 43 | } 44 | 45 | // We're connected to WiFi. Turn the LED green and print the web interface URL 46 | neopixelWrite(RGB_LED_PIN, 0, 25, 0); 47 | Serial.println("\nConnected!"); 48 | printUrlToSerial(); 49 | 50 | // Set up global pointers to the web server and audio stream 51 | webServer = &server; 52 | audioStreamer = &stream; 53 | 54 | // Set up the audio stream and web server 55 | stream.setup(); 56 | server.setup(); 57 | } 58 | 59 | void loop() { 60 | stream.loop(); 61 | 62 | // Periodically print the web interface URL to the serial console 63 | if (millis() - lastUrlTime > SHOW_URL_INTERVAL_MS) { 64 | printUrlToSerial(); 65 | lastUrlTime = millis(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /streaming/src/secrets.h: -------------------------------------------------------------------------------- 1 | #ifndef SECRETS_H 2 | #define SECRETS_H 3 | 4 | // Enter your WiFi credentials here 5 | #define WIFI_SSID "your_wifi_ssid" 6 | #define WIFI_PASS "your_wifi_password" 7 | 8 | #endif // SECRETS_H 9 | -------------------------------------------------------------------------------- /streaming/test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Test Runner and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/en/latest/advanced/unit-testing/index.html 12 | -------------------------------------------------------------------------------- /v2.x.x/README.md: -------------------------------------------------------------------------------- 1 | # Sonatino Examples 2 | ## (For previous revisions: 2.x.x) 3 | 4 | 5 | This repository contains sample code for the [Sonatino](https://sonatino.com) audio development board. 6 | 7 | Documentation for Sonatino can be found [here](https://sonatino.com/docs). 8 | 9 | --- 10 | 11 | ## Included Example Projects: 12 | 13 | | Name | Description | 14 | | ------------------------- | ------------------------------------------------------------------ | 15 | | [playback](playback/) | Demonstrates how to play an MP3 file | 16 | | [processing](processing/) | Demonstrates real-time audio processing with a basic reverb effect | 17 | 18 | ## Running The Examples 19 | 20 | To run an example, open the `.ino` file with Arduino IDE, connect Sonatino using a USB-C cable, and press **Upload**. 21 | 22 | Note: You'll need to first install [Arduino-ESP32](https://docs.espressif.com/projects/arduino-esp32/en/latest/installing.html#installing-using-arduino-ide) and the [ESP8266Audio](https://github.com/earlephilhower/ESP8266Audio) library if you haven't already done so. 23 | 24 | For more information, refer to the **Getting Started** section of the Sonatino documentation. 25 | -------------------------------------------------------------------------------- /v2.x.x/playback/README.md: -------------------------------------------------------------------------------- 1 | # Audio Playback Example 2 | 3 | This example demonstrates how to play an MP3 file, either from program memory or from a microSD card. 4 | -------------------------------------------------------------------------------- /v2.x.x/playback/playback.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | // MP3 example excerpt from: 11 | // "Impact Andante" 12 | // Kevin MacLeod (incompetech.com) 13 | // Licensed under Creative Commons: By Attribution 3.0 14 | // http://creativecommons.org/licenses/by/3.0/ 15 | #include "example_mp3.h" 16 | 17 | // Or play an MP3 file from the microSD card: 18 | #define USE_MICROSD_CARD false 19 | #define FILE_PATH "/test.mp3" 20 | 21 | // Audio files can also be flashed to the ESP32-S3's flash memory (SPIFFS, FatFs, etc.) 22 | // See https://github.com/earlephilhower/ESP8266Audio/tree/master/examples for more examples 23 | 24 | 25 | #define DAC_BCLK_PIN 15 26 | #define DAC_LRCLK_PIN 16 27 | #define DAC_DATA_PIN 17 28 | #define MICROSD_SPI_SS_PIN 41 29 | #define MICROSD_SPI_SCK_PIN 42 30 | #define MICROSD_SPI_MOSI_PIN 43 31 | #define MICROSD_SPI_MISO_PIN 44 32 | #define BLUE_LED_PIN 45 33 | 34 | 35 | AudioGeneratorMP3a *mp3; 36 | AudioFileSourcePROGMEM *file; 37 | AudioFileSourceSD *sd_file; 38 | AudioOutputI2S *dac; 39 | unsigned long last_led_blink = 0; 40 | 41 | 42 | void playMP3() { 43 | if (USE_MICROSD_CARD) { 44 | sd_file = new AudioFileSourceSD(FILE_PATH); 45 | mp3->begin(sd_file, dac); 46 | } else { 47 | file = new AudioFileSourcePROGMEM(example_data, sizeof(example_data)); 48 | mp3->begin(file, dac); 49 | } 50 | Serial.println("Playing MP3..."); 51 | } 52 | 53 | void setup() { 54 | // "USB CDC On Boot" must be enabled 55 | Serial.begin(115200); 56 | WiFi.mode(WIFI_OFF); 57 | 58 | pinMode(BLUE_LED_PIN, OUTPUT); 59 | 60 | if (USE_MICROSD_CARD) { 61 | // Set up access to microSD card 62 | SPI.begin(MICROSD_SPI_SCK_PIN, MICROSD_SPI_MISO_PIN, MICROSD_SPI_MOSI_PIN, MICROSD_SPI_SS_PIN); 63 | if (!SD.begin(MICROSD_SPI_SS_PIN)) { 64 | while (1) { 65 | Serial.println("Card mount failed"); 66 | delay(1000); 67 | } 68 | } 69 | } 70 | 71 | dac = new AudioOutputI2S(); 72 | dac->SetPinout(DAC_BCLK_PIN, DAC_LRCLK_PIN, DAC_DATA_PIN); 73 | 74 | // Output can be very loud, so we're setting the gain less than 1 75 | dac->SetGain(0.1); 76 | mp3 = new AudioGeneratorMP3a(); 77 | playMP3(); 78 | 79 | Serial.println("Ready"); 80 | } 81 | 82 | void loop() { 83 | if (mp3->isRunning()) { 84 | if (!mp3->loop()) { 85 | mp3->stop(); 86 | Serial.println("Done"); 87 | 88 | // Play the file again 89 | playMP3(); 90 | } 91 | } 92 | 93 | // Blink the blue LED 94 | if (millis() - last_led_blink > 1000) { 95 | last_led_blink = millis(); 96 | digitalWrite(BLUE_LED_PIN, !digitalRead(BLUE_LED_PIN)); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /v2.x.x/processing/README.md: -------------------------------------------------------------------------------- 1 | # Audio Processing Example 2 | 3 | This example demonstrates real-time audio processing with a basic reverb effect. 4 | -------------------------------------------------------------------------------- /v2.x.x/processing/processing.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define VERBLIB_IMPLEMENTATION 6 | #include "verblib.h" 7 | 8 | // To use this example, you must connect an audio input source (e.g. microphone, line-in) and an audio 9 | // output device to Sonatino using a 3.5mm TRRS cable. A headset device with a microphone is a good 10 | // choice, but you can also use a TRRS adapter to connect separate audio devices. 11 | 12 | // Be sure to read the Sonatino documentation and set the board's level selection DIP switches 13 | // appropriately for your input and output levels. 14 | 15 | 16 | #define BUFFER_SAMPLES 50 17 | #define CHANNELS 2 18 | #define USE_REVERB true 19 | #define SAMPLE_RATE 48000 20 | 21 | #define DAC_BCLK_PIN 15 22 | #define DAC_LRCLK_PIN 16 23 | #define DAC_DATA_PIN 17 24 | #define ADC_BCLK_PIN 38 25 | #define ADC_LRCLK_PIN 39 26 | #define ADC_DATA_PIN 40 27 | 28 | 29 | AudioOutputI2S *dac; 30 | int16_t input_buffer[BUFFER_SAMPLES]; 31 | float filter_buffer[BUFFER_SAMPLES * CHANNELS]; 32 | verblib rv; 33 | 34 | 35 | void processAudio(void *pvParameters) { 36 | size_t bytes_read = 0; 37 | float input_sample; 38 | int16_t out_sample[2]; 39 | 40 | while (1) { 41 | i2s_read(I2S_NUM_1, input_buffer, BUFFER_SAMPLES * sizeof(int16_t), 42 | &bytes_read, portMAX_DELAY); 43 | 44 | if (bytes_read > 0) { 45 | // Read mono samples and copy into the stereo filter_buffer 46 | for (uint32_t i = 0; i < bytes_read / sizeof(int16_t); i++) { 47 | input_sample = 48 | (float)input_buffer[i] / (float)std::numeric_limits::max(); 49 | 50 | filter_buffer[i * CHANNELS] = input_sample; 51 | filter_buffer[i * CHANNELS + 1] = input_sample; 52 | } 53 | 54 | if (USE_REVERB) { 55 | verblib_process(&rv, filter_buffer, filter_buffer, bytes_read / 2); 56 | } 57 | 58 | // Send samples to output 59 | for (uint32_t i = 0; i < bytes_read / sizeof(int16_t) * CHANNELS; 60 | i += CHANNELS) { 61 | out_sample[0] = 62 | filter_buffer[i] * (float)std::numeric_limits::max(); 63 | out_sample[1] = 64 | filter_buffer[i + 1] * (float)std::numeric_limits::max(); 65 | dac->ConsumeSample(out_sample); 66 | } 67 | } 68 | } 69 | } 70 | 71 | void setup() { 72 | // "USB CDC On Boot" must be enabled 73 | Serial.begin(115200); 74 | WiFi.mode(WIFI_OFF); 75 | 76 | verblib_initialize(&rv, SAMPLE_RATE, CHANNELS); 77 | 78 | dac = new AudioOutputI2S(); // Uses I2S_NUM_0 79 | dac->SetChannels(CHANNELS); 80 | dac->SetPinout(DAC_BCLK_PIN, DAC_LRCLK_PIN, DAC_DATA_PIN); 81 | dac->SetRate(SAMPLE_RATE); 82 | 83 | // Output can be very loud, so we're setting the gain less than 1 84 | dac->SetGain(0.3); 85 | dac->begin(); 86 | 87 | // Configure audio input 88 | i2s_config_t i2s_config_in = { 89 | .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX), 90 | .sample_rate = SAMPLE_RATE, 91 | .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, 92 | .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, 93 | .communication_format = I2S_COMM_FORMAT_STAND_I2S, 94 | .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, 95 | .dma_buf_count = 8, 96 | .dma_buf_len = 1024, 97 | .use_apll = 1, 98 | }; 99 | i2s_pin_config_t pin_config_in = { 100 | .mck_io_num = 0, 101 | .bck_io_num = ADC_BCLK_PIN, 102 | .ws_io_num = ADC_LRCLK_PIN, 103 | .data_out_num = I2S_PIN_NO_CHANGE, 104 | .data_in_num = ADC_DATA_PIN 105 | }; 106 | esp_err_t err; 107 | err = i2s_driver_install(I2S_NUM_1, &i2s_config_in, 0, NULL); 108 | if (err != ESP_OK) { 109 | while (1) { 110 | Serial.println("Failed to configure I2S driver for audio input"); 111 | delay(1000); 112 | } 113 | } 114 | err = i2s_set_pin(I2S_NUM_1, &pin_config_in); 115 | if (err != ESP_OK) { 116 | while (1) { 117 | Serial.println("Failed to set I2S pins for audio input"); 118 | delay(1000); 119 | } 120 | } 121 | 122 | // Start audio processing task 123 | xTaskCreatePinnedToCore(processAudio, "processAudio", 2048, NULL, 1, NULL, 0); 124 | 125 | Serial.println("Ready"); 126 | } 127 | 128 | void loop() {} 129 | -------------------------------------------------------------------------------- /v2.x.x/processing/verblib.h: -------------------------------------------------------------------------------- 1 | /* Reverb Library 2 | * Verblib version 0.5 - 2022-10-25 3 | * 4 | * Philip Bennefall - philip@blastbay.com 5 | * 6 | * See the end of this file for licensing terms. 7 | * This reverb is based on Freeverb, a public domain reverb written by Jezar at Dreampoint. 8 | * 9 | * IMPORTANT: The reverb currently only works with 1 or 2 channels, at sample rates of 22050 HZ and above. 10 | * These restrictions may be lifted in a future version. 11 | * 12 | * USAGE 13 | * 14 | * This is a single-file library. To use it, do something like the following in one .c file. 15 | * #define VERBLIB_IMPLEMENTATION 16 | * #include "verblib.h" 17 | * 18 | * You can then #include this file in other parts of the program as you would with any other header file. 19 | */ 20 | 21 | #ifndef VERBLIB_H 22 | #define VERBLIB_H 23 | 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | 28 | /* COMPILE-TIME OPTIONS */ 29 | 30 | /* The maximum sample rate that should be supported, specified as a multiple of 44100. */ 31 | #ifndef verblib_max_sample_rate_multiplier 32 | #define verblib_max_sample_rate_multiplier 2 33 | #endif 34 | 35 | /* The silence threshold which is used when calculating decay time. */ 36 | #ifndef verblib_silence_threshold 37 | #define verblib_silence_threshold 80.0 /* In dB (absolute). */ 38 | #endif 39 | 40 | /* PUBLIC API */ 41 | 42 | typedef struct verblib verblib; 43 | 44 | /* Initialize a verblib structure. 45 | * 46 | * Call this function to initialize the verblib structure. 47 | * Returns nonzero (true) on success or 0 (false) on failure. 48 | * The function will only fail if one or more of the parameters are invalid. 49 | */ 50 | int verblib_initialize ( verblib* verb, unsigned long sample_rate, unsigned int channels ); 51 | 52 | /* Run the reverb. 53 | * 54 | * Call this function continuously to generate your output. 55 | * output_buffer may be the same pointer as input_buffer if in place processing is desired. 56 | * frames specifies the number of sample frames that should be processed. 57 | */ 58 | void verblib_process ( verblib* verb, const float* input_buffer, float* output_buffer, unsigned long frames ); 59 | 60 | /* Set the size of the room, between 0.0 and 1.0. */ 61 | void verblib_set_room_size ( verblib* verb, float value ); 62 | 63 | /* Get the size of the room. */ 64 | float verblib_get_room_size ( const verblib* verb ); 65 | 66 | /* Set the amount of damping, between 0.0 and 1.0. */ 67 | void verblib_set_damping ( verblib* verb, float value ); 68 | 69 | /* Get the amount of damping. */ 70 | float verblib_get_damping ( const verblib* verb ); 71 | 72 | /* Set the stereo width of the reverb, between 0.0 and 1.0. */ 73 | void verblib_set_width ( verblib* verb, float value ); 74 | 75 | /* Get the stereo width of the reverb. */ 76 | float verblib_get_width ( const verblib* verb ); 77 | 78 | /* Set the volume of the wet signal, between 0.0 and 1.0. */ 79 | void verblib_set_wet ( verblib* verb, float value ); 80 | 81 | /* Get the volume of the wet signal. */ 82 | float verblib_get_wet ( const verblib* verb ); 83 | 84 | /* Set the volume of the dry signal, between 0.0 and 1.0. */ 85 | void verblib_set_dry ( verblib* verb, float value ); 86 | 87 | /* Get the volume of the dry signal. */ 88 | float verblib_get_dry ( const verblib* verb ); 89 | 90 | /* Set the stereo width of the input signal sent to the reverb, 0.0 or greater. 91 | * Values less than 1.0 narrow the signal, 1.0 sends the input signal unmodified, values greater than 1.0 widen the signal. 92 | */ 93 | void verblib_set_input_width ( verblib* verb, float value ); 94 | 95 | /* Get the stereo width of the input signal sent to the reverb. */ 96 | float verblib_get_input_width ( const verblib* verb ); 97 | 98 | /* Set the mode of the reverb, where values below 0.5 mean normal and values above mean frozen. */ 99 | void verblib_set_mode ( verblib* verb, float value ); 100 | 101 | /* Get the mode of the reverb. */ 102 | float verblib_get_mode ( const verblib* verb ); 103 | 104 | /* Get the decay time in sample frames based on the current room size setting. */ 105 | /* If freeze mode is active, the decay time is infinite and this function returns 0. */ 106 | unsigned long verblib_get_decay_time_in_frames ( const verblib* verb ); 107 | 108 | /* INTERNAL STRUCTURES */ 109 | 110 | /* Allpass filter */ 111 | typedef struct verblib_allpass verblib_allpass; 112 | struct verblib_allpass 113 | { 114 | float* buffer; 115 | float feedback; 116 | int bufsize; 117 | int bufidx; 118 | }; 119 | 120 | /* Comb filter */ 121 | typedef struct verblib_comb verblib_comb; 122 | struct verblib_comb 123 | { 124 | float* buffer; 125 | float feedback; 126 | float filterstore; 127 | float damp1; 128 | float damp2; 129 | int bufsize; 130 | int bufidx; 131 | }; 132 | 133 | /* Reverb model tuning values */ 134 | #define verblib_numcombs 8 135 | #define verblib_numallpasses 4 136 | #define verblib_muted 0.0f 137 | #define verblib_fixedgain 0.015f 138 | #define verblib_scalewet 3.0f 139 | #define verblib_scaledry 2.0f 140 | #define verblib_scaledamp 0.8f 141 | #define verblib_scaleroom 0.28f 142 | #define verblib_offsetroom 0.7f 143 | #define verblib_initialroom 0.5f 144 | #define verblib_initialdamp 0.25f 145 | #define verblib_initialwet 1.0f/verblib_scalewet 146 | #define verblib_initialdry 0.0f 147 | #define verblib_initialwidth 1.0f 148 | #define verblib_initialinputwidth 0.0f 149 | #define verblib_initialmode 0.0f 150 | #define verblib_freezemode 0.5f 151 | #define verblib_stereospread 23 152 | 153 | /* 154 | * These values assume 44.1KHz sample rate, but will be verblib_scaled appropriately. 155 | * The values were obtained by listening tests. 156 | */ 157 | #define verblib_combtuningL1 1116 158 | #define verblib_combtuningR1 (1116+verblib_stereospread) 159 | #define verblib_combtuningL2 1188 160 | #define verblib_combtuningR2 (1188+verblib_stereospread) 161 | #define verblib_combtuningL3 1277 162 | #define verblib_combtuningR3 (1277+verblib_stereospread) 163 | #define verblib_combtuningL4 1356 164 | #define verblib_combtuningR4 (1356+verblib_stereospread) 165 | #define verblib_combtuningL5 1422 166 | #define verblib_combtuningR5 (1422+verblib_stereospread) 167 | #define verblib_combtuningL6 1491 168 | #define verblib_combtuningR6 (1491+verblib_stereospread) 169 | #define verblib_combtuningL7 1557 170 | #define verblib_combtuningR7 (1557+verblib_stereospread) 171 | #define verblib_combtuningL8 1617 172 | #define verblib_combtuningR8 (1617+verblib_stereospread) 173 | #define verblib_allpasstuningL1 556 174 | #define verblib_allpasstuningR1 (556+verblib_stereospread) 175 | #define verblib_allpasstuningL2 441 176 | #define verblib_allpasstuningR2 (441+verblib_stereospread) 177 | #define verblib_allpasstuningL3 341 178 | #define verblib_allpasstuningR3 (341+verblib_stereospread) 179 | #define verblib_allpasstuningL4 225 180 | #define verblib_allpasstuningR4 (225+verblib_stereospread) 181 | 182 | /* The main reverb structure. This is the structure that you will create an instance of when using the reverb. */ 183 | struct verblib 184 | { 185 | unsigned int channels; 186 | float gain; 187 | float roomsize, roomsize1; 188 | float damp, damp1; 189 | float wet, wet1, wet2; 190 | float dry; 191 | float width; 192 | float input_width; 193 | float mode; 194 | 195 | /* 196 | * The following are all declared inline 197 | * to remove the need for dynamic allocation. 198 | */ 199 | 200 | /* Comb filters */ 201 | verblib_comb combL[verblib_numcombs]; 202 | verblib_comb combR[verblib_numcombs]; 203 | 204 | /* Allpass filters */ 205 | verblib_allpass allpassL[verblib_numallpasses]; 206 | verblib_allpass allpassR[verblib_numallpasses]; 207 | 208 | /* Buffers for the combs */ 209 | float bufcombL1[verblib_combtuningL1* verblib_max_sample_rate_multiplier]; 210 | float bufcombR1[verblib_combtuningR1* verblib_max_sample_rate_multiplier]; 211 | float bufcombL2[verblib_combtuningL2* verblib_max_sample_rate_multiplier]; 212 | float bufcombR2[verblib_combtuningR2* verblib_max_sample_rate_multiplier]; 213 | float bufcombL3[verblib_combtuningL3* verblib_max_sample_rate_multiplier]; 214 | float bufcombR3[verblib_combtuningR3* verblib_max_sample_rate_multiplier]; 215 | float bufcombL4[verblib_combtuningL4* verblib_max_sample_rate_multiplier]; 216 | float bufcombR4[verblib_combtuningR4* verblib_max_sample_rate_multiplier]; 217 | float bufcombL5[verblib_combtuningL5* verblib_max_sample_rate_multiplier]; 218 | float bufcombR5[verblib_combtuningR5* verblib_max_sample_rate_multiplier]; 219 | float bufcombL6[verblib_combtuningL6* verblib_max_sample_rate_multiplier]; 220 | float bufcombR6[verblib_combtuningR6* verblib_max_sample_rate_multiplier]; 221 | float bufcombL7[verblib_combtuningL7* verblib_max_sample_rate_multiplier]; 222 | float bufcombR7[verblib_combtuningR7* verblib_max_sample_rate_multiplier]; 223 | float bufcombL8[verblib_combtuningL8* verblib_max_sample_rate_multiplier]; 224 | float bufcombR8[verblib_combtuningR8* verblib_max_sample_rate_multiplier]; 225 | 226 | /* Buffers for the allpasses */ 227 | float bufallpassL1[verblib_allpasstuningL1* verblib_max_sample_rate_multiplier]; 228 | float bufallpassR1[verblib_allpasstuningR1* verblib_max_sample_rate_multiplier]; 229 | float bufallpassL2[verblib_allpasstuningL2* verblib_max_sample_rate_multiplier]; 230 | float bufallpassR2[verblib_allpasstuningR2* verblib_max_sample_rate_multiplier]; 231 | float bufallpassL3[verblib_allpasstuningL3* verblib_max_sample_rate_multiplier]; 232 | float bufallpassR3[verblib_allpasstuningR3* verblib_max_sample_rate_multiplier]; 233 | float bufallpassL4[verblib_allpasstuningL4* verblib_max_sample_rate_multiplier]; 234 | float bufallpassR4[verblib_allpasstuningR4* verblib_max_sample_rate_multiplier]; 235 | }; 236 | 237 | #ifdef __cplusplus 238 | } 239 | #endif 240 | 241 | #endif /* VERBLIB_H */ 242 | 243 | /* IMPLEMENTATION */ 244 | 245 | #ifdef VERBLIB_IMPLEMENTATION 246 | 247 | #include 248 | #include 249 | 250 | #ifdef _MSC_VER 251 | #define VERBLIB_INLINE __forceinline 252 | #else 253 | #ifdef __GNUC__ 254 | #define VERBLIB_INLINE inline __attribute__((always_inline)) 255 | #else 256 | #define VERBLIB_INLINE inline 257 | #endif 258 | #endif 259 | 260 | #define verblib_max(x, y) (((x) > (y)) ? (x) : (y)) 261 | 262 | #define undenormalise(sample) sample+=1.0f; sample-=1.0f; 263 | 264 | /* Allpass filter */ 265 | static void verblib_allpass_initialize ( verblib_allpass* allpass, float* buf, int size ) 266 | { 267 | allpass->buffer = buf; 268 | allpass->bufsize = size; 269 | allpass->bufidx = 0; 270 | } 271 | 272 | static VERBLIB_INLINE float verblib_allpass_process ( verblib_allpass* allpass, float input ) 273 | { 274 | float output; 275 | float bufout; 276 | 277 | bufout = allpass->buffer[allpass->bufidx]; 278 | undenormalise ( bufout ); 279 | 280 | output = -input + bufout; 281 | allpass->buffer[allpass->bufidx] = input + ( bufout * allpass->feedback ); 282 | 283 | if ( ++allpass->bufidx >= allpass->bufsize ) 284 | { 285 | allpass->bufidx = 0; 286 | } 287 | 288 | return output; 289 | } 290 | 291 | static void verblib_allpass_mute ( verblib_allpass* allpass ) 292 | { 293 | int i; 294 | for ( i = 0; i < allpass->bufsize; i++ ) 295 | { 296 | allpass->buffer[i] = 0.0f; 297 | } 298 | } 299 | 300 | /* Comb filter */ 301 | static void verblib_comb_initialize ( verblib_comb* comb, float* buf, int size ) 302 | { 303 | comb->buffer = buf; 304 | comb->bufsize = size; 305 | comb->filterstore = 0.0f; 306 | comb->bufidx = 0; 307 | } 308 | 309 | static void verblib_comb_mute ( verblib_comb* comb ) 310 | { 311 | int i; 312 | for ( i = 0; i < comb->bufsize; i++ ) 313 | { 314 | comb->buffer[i] = 0.0f; 315 | } 316 | } 317 | 318 | static void verblib_comb_set_damp ( verblib_comb* comb, float val ) 319 | { 320 | comb->damp1 = val; 321 | comb->damp2 = 1.0f - val; 322 | } 323 | 324 | static VERBLIB_INLINE float verblib_comb_process ( verblib_comb* comb, float input ) 325 | { 326 | float output; 327 | 328 | output = comb->buffer[comb->bufidx]; 329 | undenormalise ( output ); 330 | 331 | comb->filterstore = ( output * comb->damp2 ) + ( comb->filterstore * comb->damp1 ); 332 | undenormalise ( comb->filterstore ); 333 | 334 | comb->buffer[comb->bufidx] = input + ( comb->filterstore * comb->feedback ); 335 | 336 | if ( ++comb->bufidx >= comb->bufsize ) 337 | { 338 | comb->bufidx = 0; 339 | } 340 | 341 | return output; 342 | } 343 | 344 | static void verblib_update ( verblib* verb ) 345 | { 346 | /* Recalculate internal values after parameter change. */ 347 | 348 | int i; 349 | 350 | verb->wet1 = verb->wet * ( verb->width / 2.0f + 0.5f ); 351 | verb->wet2 = verb->wet * ( ( 1.0f - verb->width ) / 2.0f ); 352 | 353 | if ( verb->mode >= verblib_freezemode ) 354 | { 355 | verb->roomsize1 = 1.0f; 356 | verb->damp1 = 0.0f; 357 | verb->gain = verblib_muted; 358 | } 359 | else 360 | { 361 | verb->roomsize1 = verb->roomsize; 362 | verb->damp1 = verb->damp; 363 | verb->gain = verblib_fixedgain; 364 | } 365 | 366 | for ( i = 0; i < verblib_numcombs; i++ ) 367 | { 368 | verb->combL[i].feedback = verb->roomsize1; 369 | verb->combR[i].feedback = verb->roomsize1; 370 | verblib_comb_set_damp ( &verb->combL[i], verb->damp1 ); 371 | verblib_comb_set_damp ( &verb->combR[i], verb->damp1 ); 372 | } 373 | 374 | } 375 | 376 | static void verblib_mute ( verblib* verb ) 377 | { 378 | int i; 379 | if ( verblib_get_mode ( verb ) >= verblib_freezemode ) 380 | { 381 | return; 382 | } 383 | 384 | for ( i = 0; i < verblib_numcombs; i++ ) 385 | { 386 | verblib_comb_mute ( &verb->combL[i] ); 387 | verblib_comb_mute ( &verb->combR[i] ); 388 | } 389 | for ( i = 0; i < verblib_numallpasses; i++ ) 390 | { 391 | verblib_allpass_mute ( &verb->allpassL[i] ); 392 | verblib_allpass_mute ( &verb->allpassR[i] ); 393 | } 394 | } 395 | 396 | static int verblib_get_verblib_scaled_buffer_size ( unsigned long sample_rate, unsigned long value ) 397 | { 398 | long double result = ( long double ) sample_rate; 399 | result /= 44100.0; 400 | result = ( ( long double ) value ) * result; 401 | if ( result < 1.0 ) 402 | { 403 | result = 1.0; 404 | } 405 | return ( int ) result; 406 | } 407 | 408 | int verblib_initialize ( verblib* verb, unsigned long sample_rate, unsigned int channels ) 409 | { 410 | int i; 411 | 412 | if ( channels != 1 && channels != 2 ) 413 | { 414 | return 0; /* Currently supports only 1 or 2 channels. */ 415 | } 416 | if ( sample_rate < 22050 ) 417 | { 418 | return 0; /* The minimum supported sample rate is 22050 HZ. */ 419 | } 420 | else if ( sample_rate > 44100 * verblib_max_sample_rate_multiplier ) 421 | { 422 | return 0; /* The sample rate is too high. */ 423 | } 424 | 425 | verb->channels = channels; 426 | 427 | /* Tie the components to their buffers. */ 428 | verblib_comb_initialize ( &verb->combL[0], verb->bufcombL1, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningL1 ) ); 429 | verblib_comb_initialize ( &verb->combR[0], verb->bufcombR1, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningR1 ) ); 430 | verblib_comb_initialize ( &verb->combL[1], verb->bufcombL2, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningL2 ) ); 431 | verblib_comb_initialize ( &verb->combR[1], verb->bufcombR2, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningR2 ) ); 432 | verblib_comb_initialize ( &verb->combL[2], verb->bufcombL3, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningL3 ) ); 433 | verblib_comb_initialize ( &verb->combR[2], verb->bufcombR3, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningR3 ) ); 434 | verblib_comb_initialize ( &verb->combL[3], verb->bufcombL4, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningL4 ) ); 435 | verblib_comb_initialize ( &verb->combR[3], verb->bufcombR4, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningR4 ) ); 436 | verblib_comb_initialize ( &verb->combL[4], verb->bufcombL5, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningL5 ) ); 437 | verblib_comb_initialize ( &verb->combR[4], verb->bufcombR5, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningR5 ) ); 438 | verblib_comb_initialize ( &verb->combL[5], verb->bufcombL6, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningL6 ) ); 439 | verblib_comb_initialize ( &verb->combR[5], verb->bufcombR6, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningR6 ) ); 440 | verblib_comb_initialize ( &verb->combL[6], verb->bufcombL7, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningL7 ) ); 441 | verblib_comb_initialize ( &verb->combR[6], verb->bufcombR7, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningR7 ) ); 442 | verblib_comb_initialize ( &verb->combL[7], verb->bufcombL8, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningL8 ) ); 443 | verblib_comb_initialize ( &verb->combR[7], verb->bufcombR8, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningR8 ) ); 444 | 445 | verblib_allpass_initialize ( &verb->allpassL[0], verb->bufallpassL1, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_allpasstuningL1 ) ); 446 | verblib_allpass_initialize ( &verb->allpassR[0], verb->bufallpassR1, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_allpasstuningR1 ) ); 447 | verblib_allpass_initialize ( &verb->allpassL[1], verb->bufallpassL2, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_allpasstuningL2 ) ); 448 | verblib_allpass_initialize ( &verb->allpassR[1], verb->bufallpassR2, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_allpasstuningR2 ) ); 449 | verblib_allpass_initialize ( &verb->allpassL[2], verb->bufallpassL3, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_allpasstuningL3 ) ); 450 | verblib_allpass_initialize ( &verb->allpassR[2], verb->bufallpassR3, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_allpasstuningR3 ) ); 451 | verblib_allpass_initialize ( &verb->allpassL[3], verb->bufallpassL4, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_allpasstuningL4 ) ); 452 | verblib_allpass_initialize ( &verb->allpassR[3], verb->bufallpassR4, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_allpasstuningR4 ) ); 453 | 454 | /* Set default values. */ 455 | for ( i = 0; i < verblib_numallpasses; i++ ) 456 | { 457 | verb->allpassL[i].feedback = 0.5f; 458 | verb->allpassR[i].feedback = 0.5f; 459 | } 460 | 461 | verblib_set_wet ( verb, verblib_initialwet ); 462 | verblib_set_room_size ( verb, verblib_initialroom ); 463 | verblib_set_dry ( verb, verblib_initialdry ); 464 | verblib_set_damping ( verb, verblib_initialdamp ); 465 | verblib_set_width ( verb, verblib_initialwidth ); 466 | verblib_set_input_width ( verb, verblib_initialinputwidth ); 467 | verblib_set_mode ( verb, verblib_initialmode ); 468 | 469 | /* The buffers will be full of rubbish - so we MUST mute them. */ 470 | verblib_mute ( verb ); 471 | 472 | return 1; 473 | } 474 | 475 | void verblib_process ( verblib* verb, const float* input_buffer, float* output_buffer, unsigned long frames ) 476 | { 477 | int i; 478 | float outL, outR, input; 479 | 480 | if ( verb->channels == 1 ) 481 | { 482 | while ( frames-- > 0 ) 483 | { 484 | outL = 0.0f; 485 | input = ( input_buffer[0] * 2.0f ) * verb->gain; 486 | 487 | /* Accumulate comb filters in parallel. */ 488 | for ( i = 0; i < verblib_numcombs; i++ ) 489 | { 490 | outL += verblib_comb_process ( &verb->combL[i], input ); 491 | } 492 | 493 | /* Feed through allpasses in series. */ 494 | for ( i = 0; i < verblib_numallpasses; i++ ) 495 | { 496 | outL = verblib_allpass_process ( &verb->allpassL[i], outL ); 497 | } 498 | 499 | /* Calculate output REPLACING anything already there. */ 500 | output_buffer[0] = outL * verb->wet1 + input_buffer[0] * verb->dry; 501 | 502 | /* Increment sample pointers. */ 503 | ++input_buffer; 504 | ++output_buffer; 505 | } 506 | } 507 | else if ( verb->channels == 2 ) 508 | { 509 | if ( verb->input_width > 0.0f ) /* Stereo input is widened or narrowed. */ 510 | { 511 | 512 | /* 513 | * The stereo mid/side code is derived from: 514 | * https://www.musicdsp.org/en/latest/Effects/256-stereo-width-control-obtained-via-transfromation-matrix.html 515 | * The description of the code on the above page says: 516 | * 517 | * This work is hereby placed in the public domain for all purposes, including 518 | * use in commercial applications. 519 | */ 520 | 521 | const float tmp = 1 / verblib_max ( 1 + verb->input_width, 2 ); 522 | const float coef_mid = 1 * tmp; 523 | const float coef_side = verb->input_width * tmp; 524 | while ( frames-- > 0 ) 525 | { 526 | const float mid = ( input_buffer[0] + input_buffer[1] ) * coef_mid; 527 | const float side = ( input_buffer[1] - input_buffer[0] ) * coef_side; 528 | const float input_left = ( mid - side ) * ( verb->gain * 2.0f ); 529 | const float input_right = ( mid + side ) * ( verb->gain * 2.0f ); 530 | 531 | outL = outR = 0.0f; 532 | 533 | /* Accumulate comb filters in parallel. */ 534 | for ( i = 0; i < verblib_numcombs; i++ ) 535 | { 536 | outL += verblib_comb_process ( &verb->combL[i], input_left ); 537 | outR += verblib_comb_process ( &verb->combR[i], input_right ); 538 | } 539 | 540 | /* Feed through allpasses in series. */ 541 | for ( i = 0; i < verblib_numallpasses; i++ ) 542 | { 543 | outL = verblib_allpass_process ( &verb->allpassL[i], outL ); 544 | outR = verblib_allpass_process ( &verb->allpassR[i], outR ); 545 | } 546 | 547 | /* Calculate output REPLACING anything already there. */ 548 | output_buffer[0] = outL * verb->wet1 + outR * verb->wet2 + input_buffer[0] * verb->dry; 549 | output_buffer[1] = outR * verb->wet1 + outL * verb->wet2 + input_buffer[1] * verb->dry; 550 | 551 | /* Increment sample pointers. */ 552 | input_buffer += 2; 553 | output_buffer += 2; 554 | } 555 | } 556 | else /* Stereo input is summed to mono. */ 557 | { 558 | while ( frames-- > 0 ) 559 | { 560 | outL = outR = 0.0f; 561 | input = ( input_buffer[0] + input_buffer[1] ) * verb->gain; 562 | 563 | /* Accumulate comb filters in parallel. */ 564 | for ( i = 0; i < verblib_numcombs; i++ ) 565 | { 566 | outL += verblib_comb_process ( &verb->combL[i], input ); 567 | outR += verblib_comb_process ( &verb->combR[i], input ); 568 | } 569 | 570 | /* Feed through allpasses in series. */ 571 | for ( i = 0; i < verblib_numallpasses; i++ ) 572 | { 573 | outL = verblib_allpass_process ( &verb->allpassL[i], outL ); 574 | outR = verblib_allpass_process ( &verb->allpassR[i], outR ); 575 | } 576 | 577 | /* Calculate output REPLACING anything already there. */ 578 | output_buffer[0] = outL * verb->wet1 + outR * verb->wet2 + input_buffer[0] * verb->dry; 579 | output_buffer[1] = outR * verb->wet1 + outL * verb->wet2 + input_buffer[1] * verb->dry; 580 | 581 | /* Increment sample pointers. */ 582 | input_buffer += 2; 583 | output_buffer += 2; 584 | } 585 | } 586 | } 587 | } 588 | 589 | void verblib_set_room_size ( verblib* verb, float value ) 590 | { 591 | verb->roomsize = ( value * verblib_scaleroom ) + verblib_offsetroom; 592 | verblib_update ( verb ); 593 | } 594 | 595 | float verblib_get_room_size ( const verblib* verb ) 596 | { 597 | return ( verb->roomsize - verblib_offsetroom ) / verblib_scaleroom; 598 | } 599 | 600 | void verblib_set_damping ( verblib* verb, float value ) 601 | { 602 | verb->damp = value * verblib_scaledamp; 603 | verblib_update ( verb ); 604 | } 605 | 606 | float verblib_get_damping ( const verblib* verb ) 607 | { 608 | return verb->damp / verblib_scaledamp; 609 | } 610 | 611 | void verblib_set_wet ( verblib* verb, float value ) 612 | { 613 | verb->wet = value * verblib_scalewet; 614 | verblib_update ( verb ); 615 | } 616 | 617 | float verblib_get_wet ( const verblib* verb ) 618 | { 619 | return verb->wet / verblib_scalewet; 620 | } 621 | 622 | void verblib_set_dry ( verblib* verb, float value ) 623 | { 624 | verb->dry = value * verblib_scaledry; 625 | } 626 | 627 | float verblib_get_dry ( const verblib* verb ) 628 | { 629 | return verb->dry / verblib_scaledry; 630 | } 631 | 632 | void verblib_set_width ( verblib* verb, float value ) 633 | { 634 | verb->width = value; 635 | verblib_update ( verb ); 636 | } 637 | 638 | float verblib_get_width ( const verblib* verb ) 639 | { 640 | return verb->width; 641 | } 642 | 643 | void verblib_set_input_width ( verblib* verb, float value ) 644 | { 645 | verb->input_width = value; 646 | } 647 | 648 | float verblib_get_input_width ( const verblib* verb ) 649 | { 650 | return verb->input_width; 651 | } 652 | 653 | void verblib_set_mode ( verblib* verb, float value ) 654 | { 655 | verb->mode = value; 656 | verblib_update ( verb ); 657 | } 658 | 659 | float verblib_get_mode ( const verblib* verb ) 660 | { 661 | if ( verb->mode >= verblib_freezemode ) 662 | { 663 | return 1.0f; 664 | } 665 | return 0.0f; 666 | } 667 | 668 | unsigned long verblib_get_decay_time_in_frames ( const verblib* verb ) 669 | { 670 | double decay; 671 | 672 | if ( verb->mode >= verblib_freezemode ) 673 | { 674 | return 0; /* Freeze mode creates an infinite decay. */ 675 | } 676 | 677 | decay = verblib_silence_threshold / fabs ( -20.0 * log ( 1.0 / verb->roomsize1 ) ); 678 | decay *= ( double ) ( verb->combR[7].bufsize * 2 ); 679 | return ( unsigned long ) decay; 680 | } 681 | 682 | #endif /* VERBLIB_IMPLEMENTATION */ 683 | 684 | /* REVISION HISTORY 685 | * 686 | * Version 0.5 - 2022-10-25 687 | * Added two functions called verblib_set_input_width and verblib_get_input_width. 688 | * 689 | * Version 0.4 - 2021-01-23 690 | * Added a function called verblib_get_decay_time_in_frames. 691 | * 692 | * Version 0.3 - 2021-01-18 693 | * Added support for sample rates of 22050 and above. 694 | * 695 | * Version 0.2 - 2021-01-17 696 | * Added support for processing mono audio. 697 | * 698 | * Version 0.1 - 2021-01-17 699 | * Initial release. 700 | */ 701 | 702 | /* LICENSE 703 | 704 | This software is available under 2 licenses -- choose whichever you prefer. 705 | ------------------------------------------------------------------------------ 706 | ALTERNATIVE A - MIT No Attribution License 707 | Copyright (c) 2022 Philip Bennefall 708 | 709 | Permission is hereby granted, free of charge, to any person obtaining a copy of 710 | this software and associated documentation files (the "Software"), to deal in 711 | the Software without restriction, including without limitation the rights to 712 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 713 | of the Software, and to permit persons to whom the Software is furnished to do 714 | so. 715 | 716 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 717 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 718 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 719 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 720 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 721 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 722 | SOFTWARE. 723 | ------------------------------------------------------------------------------ 724 | ALTERNATIVE B - Public Domain (www.unlicense.org) 725 | This is free and unencumbered software released into the public domain. 726 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 727 | software, either in source code form or as a compiled binary, for any purpose, 728 | commercial or non-commercial, and by any means. 729 | 730 | In jurisdictions that recognize copyright laws, the author or authors of this 731 | software dedicate any and all copyright interest in the software to the public 732 | domain. We make this dedication for the benefit of the public at large and to 733 | the detriment of our heirs and successors. We intend this dedication to be an 734 | overt act of relinquishment in perpetuity of all present and future rights to 735 | this software under copyright law. 736 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 737 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 738 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 739 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 740 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 741 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 742 | ------------------------------------------------------------------------------ 743 | */ --------------------------------------------------------------------------------