From c2f5a4350a110bb38ad25dc02b10ff4349fc1bbd Mon Sep 17 00:00:00 2001 From: moon2 <moon2protonmail@protonmail.com> Date: Tue, 6 Jun 2023 19:28:54 +0000 Subject: [PATCH] Audio IO: Speaker/Headphone Management (not properly exposed in python_payload yet) --- components/badge23/audio.c | 471 ++++++++++++++++----- components/badge23/captouch.c | 5 + components/badge23/espan.c | 33 +- components/badge23/include/badge23/audio.h | 156 ++++++- components/badge23/include/badge23/lock.h | 6 + components/badge23/spio.c | 5 + python_payload/demo_menu.py | 3 +- python_payload/demo_sparabo.py | 5 +- python_payload/menu_settings.py | 5 +- python_payload/menu_tinysynth.py | 5 +- python_payload/utils.py | 8 +- usermodule/micropython.cmake | 1 + usermodule/mp_audio.c | 234 ++++++++++ usermodule/mp_hardware.c | 7 +- 14 files changed, 822 insertions(+), 122 deletions(-) create mode 100644 components/badge23/include/badge23/lock.h create mode 100644 usermodule/mp_audio.c diff --git a/components/badge23/audio.c b/components/badge23/audio.c index 76b130d511..f30dd972e8 100644 --- a/components/badge23/audio.c +++ b/components/badge23/audio.c @@ -1,12 +1,12 @@ #include "badge23/audio.h" #include "badge23/synth.h" #include "badge23/scope.h" +#include "badge23/lock.h" #include "badge23_hwconfig.h" #include "driver/i2s.h" #include "driver/i2c.h" - #include <freertos/FreeRTOS.h> #include <freertos/task.h> #include <freertos/queue.h> @@ -14,103 +14,151 @@ #include <math.h> #include <string.h> -#define TIMEOUT_MS 1000 +#define TIMEOUT_MS 1000 -#define I2C_MASTER_NUM 0 /*!< I2C master i2c port number, the number of i2c peripheral interfaces available will depend on the chip */ +#define I2C_MASTER_NUM 0 /*!< I2C master i2c port number, the number of i2c peripheral interfaces available will depend on the chip */ static void audio_player_task(void* arg); -#define DMA_BUFFER_SIZE 64 -#define DMA_BUFFER_COUNT 2 +#define DMA_BUFFER_SIZE 64 +#define DMA_BUFFER_COUNT 2 #define I2S_PORT 0 +// used for exp(vol_dB * NAT_LOG_DB) +#define NAT_LOG_DB 0.1151292546497023 + +// placeholder for "fake mute" -inf dB (we know floats can do that but we have trust issues when using NAN) +#define SILLY_LOW_VOLUME_DB (-10000.) + +static bool headphones_connected = 0; +static bool headset_connected = 0; +static bool line_in_connected = 0; +static bool headphones_detection_override = 0; +static int32_t software_volume = 0; + +// maybe struct these someday but eh it works +static float headphones_volume_dB = 0; +static bool headphones_mute = 0; +const static float headphones_maximum_volume_system_dB = 3; +static float headphones_maximum_volume_user_dB = headphones_maximum_volume_system_dB; +static float headphones_minimum_volume_user_dB = headphones_maximum_volume_system_dB - 70; + +static float speaker_volume_dB = 0; +static bool speaker_mute = 0; +const static float speaker_maximum_volume_system_dB = 14; +static float speaker_maximum_volume_user_dB = speaker_maximum_volume_system_dB; +static float speaker_minimum_volume_user_dB = speaker_maximum_volume_system_dB - 60; + +uint8_t audio_headset_is_connected(){ return headset_connected; } +uint8_t audio_headphones_are_connected(){ return headphones_connected || headphones_detection_override; } +float audio_headphones_get_volume_dB(){ return headphones_volume_dB; } +float audio_speaker_get_volume_dB(){ return speaker_volume_dB; } +float audio_headphones_get_minimum_volume_dB(){ return headphones_minimum_volume_user_dB; } +float audio_speaker_get_minimum_volume_dB(){ return speaker_minimum_volume_user_dB; } +float audio_headphones_get_maximum_volume_dB(){ return headphones_maximum_volume_user_dB; } +float audio_speaker_get_maximum_volume_dB(){ return speaker_maximum_volume_user_dB; } +uint8_t audio_headphones_get_mute(){ return headphones_mute ? 1 : 0; } +uint8_t audio_speaker_get_mute(){ return speaker_mute ? 1 : 0; } + #if defined(CONFIG_BADGE23_HW_GEN_P3) || defined(CONFIG_BADGE23_HW_GEN_P4) + static uint8_t max98091_i2c_read(const uint8_t reg) { const uint8_t tx[] = {reg}; uint8_t rx[1]; + xSemaphoreTake(mutex_i2c, portMAX_DELAY); esp_err_t ret = i2c_master_write_read_device(I2C_MASTER_NUM, 0x10, tx, sizeof(tx), rx, sizeof(rx), TIMEOUT_MS / portTICK_PERIOD_MS); + xSemaphoreGive(mutex_i2c); return rx[0]; } static esp_err_t max98091_i2c_write(const uint8_t reg, const uint8_t data) { const uint8_t tx[] = {reg, data}; + xSemaphoreTake(mutex_i2c, portMAX_DELAY); esp_err_t ret = i2c_master_write_to_device(I2C_MASTER_NUM, 0x10, tx, sizeof(tx), TIMEOUT_MS / portTICK_PERIOD_MS); - if(max98091_i2c_read(reg) != data) printf("readback of %04X to %02X write failed\n", data, reg); + xSemaphoreGive(mutex_i2c); return ret; } - +static esp_err_t max98091_i2c_write_readback(const uint8_t reg, const uint8_t data) +{ + const uint8_t tx[] = {reg, data}; + xSemaphoreTake(mutex_i2c, portMAX_DELAY); + esp_err_t ret = i2c_master_write_to_device(I2C_MASTER_NUM, 0x10, tx, sizeof(tx), TIMEOUT_MS / portTICK_PERIOD_MS); + xSemaphoreGive(mutex_i2c); + if(max98091_i2c_read(reg) != data) printf("readback of %04X to %02X write failed\n", data, reg); + return ret; +} static void init_codec() { // Enable CODEC vTaskDelay(10 / portTICK_PERIOD_MS); - ESP_ERROR_CHECK(max98091_i2c_write(0x00, 0x80)); // shutdown + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x00, 0x80)); // shutdown vTaskDelay(10 / portTICK_PERIOD_MS); - ESP_ERROR_CHECK(max98091_i2c_write(0x45, 0)); // shutdown + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x45, 0)); // shutdown - ESP_ERROR_CHECK(max98091_i2c_write(0x1b, 1 << 4)); // pclk = mclk / 1 + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x1b, 1 << 4)); // pclk = mclk / 1 - ESP_ERROR_CHECK(max98091_i2c_write(0x26, (1 << 7) | (1 << 6))); // music, dc filter in record + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x26, (1 << 7) | (1 << 6))); // music, dc filter in record - ESP_ERROR_CHECK(max98091_i2c_write(0x06, 1 << 2)); // Sets up DAI for left-justified slave mode operation. - ESP_ERROR_CHECK(max98091_i2c_write(0x07, 1 << 5)); // Sets up the DAC to speaker path + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x06, 1 << 2)); // Sets up DAI for left-justified slave mode operation. + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x07, 1 << 5)); // Sets up the DAC to speaker path // Somehow this was needed to get an input signal to the ADC, even though // all other registers should be taken care of later. Don't know why. - ESP_ERROR_CHECK(max98091_i2c_write(0x09, 1 << 6)); // Sets up the line in to adc path + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x09, 1 << 6)); // Sets up the line in to adc path - ESP_ERROR_CHECK(max98091_i2c_write(0x25, (1 << 1) | (1 << 0))); // SDOUT, SDIN enabled - ESP_ERROR_CHECK(max98091_i2c_write(0x42, 1 << 0)); // bandgap bias - ESP_ERROR_CHECK(max98091_i2c_write(0x43, 1 << 0)); // high performane mode + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x25, (1 << 1) | (1 << 0))); // SDOUT, SDIN enabled + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x42, 1 << 0)); // bandgap bias + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x43, 1 << 0)); // high performane mode // Table 51. Digital Audio Interface (DAI) Format Configuration Register - ESP_ERROR_CHECK(max98091_i2c_write(0x2E, 1)); // Left DAC -> Left Speaker - ESP_ERROR_CHECK(max98091_i2c_write(0x2F, 2)); // Right DAC -> Right Speaker + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x2E, 1)); // Left DAC -> Left Speaker + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x2F, 2)); // Right DAC -> Right Speaker - //max98091_i2c_write(0x2E, (1<<2) | (1<<1)); // Line A -> Left Speaker - //max98091_i2c_write(0x2F, (1<<3) | (1<<0)); // LIne B -> Right Speaker + //max98091_i2c_write_readback(0x2E, (1<<2) | (1<<1)); // Line A -> Left Speaker + //max98091_i2c_write_readback(0x2F, (1<<3) | (1<<0)); // LIne B -> Right Speaker - ESP_ERROR_CHECK(max98091_i2c_write(0x29, 1)); // Left DAC -> Left HP - ESP_ERROR_CHECK(max98091_i2c_write(0x2A, 2)); // Right DAC -> Right HP + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x29, 1)); // Left DAC -> Left HP + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x2A, 2)); // Right DAC -> Right HP // Mic bias is off - ESP_ERROR_CHECK(max98091_i2c_write(0x3E, (1<<4) |(1<<3) | (1<<2) | (1<<1) | (1<<0))); // enable micbias, line input amps, ADCs - ESP_ERROR_CHECK(max98091_i2c_write(0x0D, (1<<3) | (1<<2))); // IN3 SE -> Line A, IN4 SE -> Line B - ESP_ERROR_CHECK(max98091_i2c_write(0x15, (1<<4) )); // line B -> left ADC - ESP_ERROR_CHECK(max98091_i2c_write(0x16, (1<<3) )); // line A -> right ADC - ESP_ERROR_CHECK(max98091_i2c_write(0x44, (1<<2) | (1<<1) | (1<<0) )); // 128x oversampling, dithering, high performance ADC + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x3E, (1<<4) |(1<<3) | (1<<2) | (1<<1) | (1<<0))); // enable micbias, line input amps, ADCs + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x0D, (1<<3) | (1<<2))); // IN3 SE -> Line A, IN4 SE -> Line B + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x15, (1<<4) )); // line B -> left ADC + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x16, (1<<3) )); // line A -> right ADC + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x44, (1<<2) | (1<<1) | (1<<0) )); // 128x oversampling, dithering, high performance ADC - max98091_i2c_write(0x13, (1<<4) | (1<<5) | (1<<1) | (1<<0) ); // enable digital mic + max98091_i2c_write_readback(0x13, (1<<4) | (1<<5) | (1<<1) | (1<<0) ); // enable digital mic // Enable headset mic #if 0 - max98091_i2c_write(0x13, 0); - ESP_ERROR_CHECK(max98091_i2c_write(0x0F, (0<<1) | (1<<0) )); // IN5/IN6 to MIC1 - ESP_ERROR_CHECK(max98091_i2c_write(0x10, (1<<6) | (1<<4) | (1<<2) )); // 20 dB gain on MIC1 - ESP_ERROR_CHECK(max98091_i2c_write(0x15, (1<<5) )); // MIC1 -> left ADC - ESP_ERROR_CHECK(max98091_i2c_write(0x16, (1<<5) )); // MIC1 -> right ADC + max98091_i2c_write_readback(0x13, 0); + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x0F, (0<<1) | (1<<0) )); // IN5/IN6 to MIC1 + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x10, (1<<6) | (1<<4) | (1<<2) )); // 20 dB gain on MIC1 + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x15, (1<<5) )); // MIC1 -> left ADC + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x16, (1<<5) )); // MIC1 -> right ADC #endif - ESP_ERROR_CHECK(max98091_i2c_write(0x3F, (1<<1) | (1<<0))); // output enable: enable dacs + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x3F, (1<<1) | (1<<0))); // output enable: enable dacs - ESP_ERROR_CHECK(max98091_i2c_write(0x45, 1<<7)); // power up - //max98091_i2c_write(0x31, 0x2c); // 0db, no mute - //max98091_i2c_write(0x32, 0x2c); // 0db, no mute - ESP_ERROR_CHECK(max98091_i2c_write(0x3F, (1<<7) | (1<<6) | (1<<5) | (1<<4) | (1<<1) | (1<<0))); // enable outputs, dacs + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x45, 1<<7)); // power up + //max98091_i2c_write_readback(0x31, 0x2c); // 0db, no mute + //max98091_i2c_write_readback(0x32, 0x2c); // 0db, no mute + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x3F, (1<<7) | (1<<6) | (1<<5) | (1<<4) | (1<<1) | (1<<0))); // enable outputs, dacs - //max98091_i2c_write(0x27, (1<<4) | (1<<5)); // full playback gain + //max98091_i2c_write_readback(0x27, (1<<4) | (1<<5)); // full playback gain - //max98091_i2c_write(0x31, 0x3f); // +14 db speaker - //max98091_i2c_write(0x32, 0x3f); // +14 db speaker - ESP_ERROR_CHECK(max98091_i2c_write(0x41, 0x0)); + //max98091_i2c_write_readback(0x31, 0x3f); // +14 db speaker + //max98091_i2c_write_readback(0x32, 0x3f); // +14 db speaker + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x41, 0x0)); - ESP_ERROR_CHECK(max98091_i2c_write(0x3D, 1<<7)); // jack detect enable + ESP_ERROR_CHECK(max98091_i2c_write_readback(0x3D, 1<<7)); // jack detect enable printf("4 readbacks failing here is normal dw ^w^"); } @@ -144,8 +192,78 @@ static void i2s_init(void){ } +// irregular register value -> vol mapping taken from codec datasheet +typedef struct { + uint8_t register_value; + float volume_dB; +} vol_map_t; + +const uint8_t speaker_map_len = 40; +const vol_map_t speaker_map[] = {{0x3F, +14}, {0x3E, +13.5}, {0x3D, +13}, {0x3C, +12.5}, {0x3B, +12}, {0x3A, +11.5}, {0x39, +11}, {0x38, +10.5}, {0x37, +10}, {0x36, +9.5}, {0x35, +9}, {0x34, +8}, {0x33, +7}, {0x32, +6}, {0x31, +5}, {0x30, +4}, {0x2F, +3}, {0x2E, +2}, {0x2D, +1}, {0x2C, +0}, {0x2B, -1}, {0x2A, -2}, {0x29, -3}, {0x28, -4}, {0x27, -5}, {0x26, -6}, {0x25, -8}, {0x24, -10}, {0x23, -12}, {0x22, -14}, {0x21, -17}, {0x20, -20}, {0x1F, -23}, {0x1E, -26}, {0x1D, -29}, {0x1C, -32}, {0x1B, -36}, {0x1A, -40}, {0x19, -44}, {0x18, -48}}; + +const uint8_t headphones_map_len = 32; +const vol_map_t headphones_map[] = {{0x1F, +3}, {0x1E, +2.5}, {0x1D, +2}, {0x1C, +1.5}, {0x1B, +1}, {0x1A, +0}, {0x19, -1}, {0x18, -2}, {0x17, -3}, {0x16, -4}, {0x15, -5}, {0x14, -7}, {0x13, -9}, {0x12, -11}, {0x11, -13}, {0x10, -15}, {0x0F, -17}, {0x0E, -19}, {0x0D, -22}, {0x0C, -25}, {0x0B, -28}, {0x0A, -31}, {0x09, -34}, {0x08, -37}, {0x07, -40}, {0x06, -43}, {0x06, -47}, {0x04, -51}, {0x03, -55}, {0x02, -59}, {0x01, -63}, {0x00, -67}}; + +void _audio_headphones_set_volume_dB(float vol_dB, bool mute){ + uint8_t map_index = headphones_map_len - 1; + for(; map_index; map_index--){ + if(headphones_map[map_index].volume_dB >= vol_dB) break; + } + uint8_t reg = headphones_map[map_index].register_value; + reg = (mute ? (1 << 7) : 0) | reg; + max98091_i2c_write(0x2C, reg); //left chan + max98091_i2c_write(0x2D, reg); //right chan + // note: didn't check if chan physically mapped to l/r or flipped. + + // do the fine steps in software + // note: synchronizing both hw and software volume changes is somewhat tricky + float hardware_volume_dB = headphones_map[map_index].volume_dB; + float software_volume_dB = vol_dB - hardware_volume_dB; + if(software_volume_dB > 0) software_volume_dB = 0; + //if(!software_volume_enabled) software_volume_dB = 0; // breaks p1, might add option once it is retired + software_volume = (int32_t) (32768 * exp(software_volume_dB * NAT_LOG_DB)); + headphones_volume_dB = hardware_volume_dB + software_volume_dB; +} + +void _audio_speaker_set_volume_dB(float vol_dB, bool mute){ + uint8_t map_index = speaker_map_len - 1; + for(; map_index; map_index--){ + if(speaker_map[map_index].volume_dB >= vol_dB) break; + } + + uint8_t reg = speaker_map[map_index].register_value; + reg = (mute ? (1 << 7) : 0) | reg; + max98091_i2c_write(0x31, reg); //left chan + max98091_i2c_write(0x32, reg); //right chan + //note: didn't check if chan physically mapped to l/r or flipped. + + // do the fine steps in software + // note: synchronizing both hw and software volume changes is somewhat tricky + float hardware_volume_dB = speaker_map[map_index].volume_dB; + float software_volume_dB = vol_dB - hardware_volume_dB; + if(software_volume_dB > 0) software_volume_dB = 0; + //if(!software_volume_enabled) software_volume_dB = 0; // breaks p1, might add option once it is retired + software_volume = (int32_t) (32768. * exp(software_volume_dB * NAT_LOG_DB)); + speaker_volume_dB = hardware_volume_dB + software_volume_dB; +} + +void audio_headphones_set_mute(uint8_t mute){ + headphones_mute = mute; + audio_headphones_set_volume_dB(headphones_volume_dB); +} + +void audio_speaker_set_mute(uint8_t mute){ + speaker_mute = mute; + audio_speaker_set_volume_dB(speaker_volume_dB); +} + #elif defined(CONFIG_BADGE23_HW_GEN_P1) +#define MAX_VOLUME_DB 10 +#define MIN_VOLUME_DB (-80) + +int32_t software_volume_premute; // ugly but this is an old prototype that will be phased out soon + static void i2s_init(void){ static const i2s_config_t i2s_config = { @@ -172,10 +290,219 @@ static void i2s_init(void){ i2s_set_pin(I2S_PORT, &pin_config); } + +void _audio_speaker_set_volume_dB(float vol_dB, bool mute){ + int32_t buf = 32767 * exp(vol_dB * NAT_LOG_DB); + software_volume_premute = buf; + if(mute){ + software_volume = 0; + } else { + software_volume = software_volume_premute; + } + speaker_volume_dB = vol_dB; +} + +void _audio_headphones_set_volume_dB(float vol_dB, bool mute){ +} + +void audio_headphones_set_mute(uint8_t mute){ + headphones_mute = 1; +}; + +void audio_speaker_set_mute(uint8_t mute){ + speaker_mute = mute; + if(speaker_mute){ + software_volume = 0; + } else { + software_volume = software_volume_premute; + } +} + #else #error "audio not implemented for this badge generation" #endif +float audio_speaker_set_volume_dB(float vol_dB){ + bool mute = speaker_mute || audio_headphones_are_connected(); + if(vol_dB > speaker_maximum_volume_user_dB) vol_dB = speaker_maximum_volume_user_dB; + if(vol_dB < speaker_minimum_volume_user_dB){ + vol_dB = SILLY_LOW_VOLUME_DB; // fake mute + mute = 1; + } + _audio_speaker_set_volume_dB(vol_dB, mute); + return speaker_volume_dB; +} + +float audio_headphones_set_volume_dB(float vol_dB){ + bool mute = headphones_mute || (!audio_headphones_are_connected()); + if(vol_dB > headphones_maximum_volume_user_dB) vol_dB = headphones_maximum_volume_user_dB; + if(vol_dB < headphones_minimum_volume_user_dB){ + vol_dB = SILLY_LOW_VOLUME_DB; // fake mute + mute = 1; + } + _audio_headphones_set_volume_dB(vol_dB, mute); + return headphones_volume_dB; +} + +void audio_headphones_detection_override(uint8_t enable){ + headphones_detection_override = enable; + audio_headphones_set_volume_dB(headphones_volume_dB); + audio_speaker_set_volume_dB(speaker_volume_dB); +} + +float audio_headphones_adjust_volume_dB(float vol_dB){ + if(audio_headphones_get_volume_dB() < headphones_minimum_volume_user_dB){ //fake mute + if(vol_dB > 0){ + return audio_headphones_set_volume_dB(headphones_minimum_volume_user_dB); + } else { + return audio_headphones_get_volume_dB(); + } + } else { + return audio_headphones_set_volume_dB(headphones_volume_dB + vol_dB); + } +} + +float audio_speaker_adjust_volume_dB(float vol_dB){ + if(audio_speaker_get_volume_dB() < speaker_minimum_volume_user_dB){ //fake mute + if(vol_dB > 0){ + return audio_speaker_set_volume_dB(speaker_minimum_volume_user_dB); + printf("hi"); + } else { + return audio_speaker_get_volume_dB(); + } + } else { + return audio_speaker_set_volume_dB(speaker_volume_dB + vol_dB); + } +} + +float audio_adjust_volume_dB(float vol_dB){ + if(audio_headphones_are_connected()){ + return audio_headphones_adjust_volume_dB(vol_dB); + } else { + return audio_speaker_adjust_volume_dB(vol_dB); + } +} + +float audio_set_volume_dB(float vol_dB){ + if(audio_headphones_are_connected()){ + return audio_headphones_set_volume_dB(vol_dB); + } else { + return audio_speaker_set_volume_dB(vol_dB); + } +} + +float audio_get_volume_dB(){ + if(audio_headphones_are_connected()){ + return audio_headphones_get_volume_dB(); + } else { + return audio_speaker_get_volume_dB(); + } +} + +void audio_set_mute(uint8_t mute){ + if(audio_headphones_are_connected()){ + audio_headphones_set_mute(mute); + } else { + audio_speaker_set_mute(mute); + } +} + +uint8_t audio_get_mute(){ + if(audio_headphones_are_connected()){ + return audio_headphones_get_mute(); + } else { + return audio_speaker_get_mute(); + } +} + +float audio_headphones_set_maximum_volume_dB(float vol_dB){ + if(vol_dB > headphones_maximum_volume_system_dB) vol_dB = headphones_maximum_volume_system_dB; + if(vol_dB < headphones_minimum_volume_user_dB) vol_dB = headphones_minimum_volume_user_dB; + headphones_maximum_volume_user_dB = vol_dB; + return headphones_maximum_volume_user_dB; +} + +float audio_headphones_set_minimum_volume_dB(float vol_dB){ + if(vol_dB > headphones_maximum_volume_user_dB) vol_dB = headphones_maximum_volume_user_dB; + if((vol_dB + 1) < SILLY_LOW_VOLUME_DB) vol_dB = SILLY_LOW_VOLUME_DB + 1.; + headphones_minimum_volume_user_dB = vol_dB; + return headphones_minimum_volume_user_dB; +} + +float audio_speaker_set_maximum_volume_dB(float vol_dB){ + if(vol_dB > speaker_maximum_volume_system_dB) vol_dB = speaker_maximum_volume_system_dB; + if(vol_dB < speaker_minimum_volume_user_dB) vol_dB = speaker_minimum_volume_user_dB; + speaker_maximum_volume_user_dB = vol_dB; + return speaker_maximum_volume_user_dB; +} + +float audio_speaker_set_minimum_volume_dB(float vol_dB){ + if(vol_dB > speaker_maximum_volume_user_dB) vol_dB = speaker_maximum_volume_user_dB; + if((vol_dB + 1) < SILLY_LOW_VOLUME_DB) vol_dB = SILLY_LOW_VOLUME_DB + 1.; + speaker_minimum_volume_user_dB = vol_dB; + return speaker_minimum_volume_user_dB; +} + +float audio_speaker_get_volume_relative(){ + float ret = audio_speaker_get_volume_dB(); + if(ret < speaker_minimum_volume_user_dB) return 0; // fake mute + float vol_range = speaker_maximum_volume_user_dB - speaker_minimum_volume_user_dB; + ret -= speaker_minimum_volume_user_dB; // shift to above zero + ret /= vol_range; // restrict to 0..1 range + return (ret*0.99) + 0.01; // shift to 0.01 to 0.99 range to distinguish from fake mute +} + +float audio_headphones_get_volume_relative(){ + float ret = audio_headphones_get_volume_dB(); + if(ret < headphones_minimum_volume_user_dB) return 0; // fake mute + float vol_range = headphones_maximum_volume_user_dB - headphones_minimum_volume_user_dB; + ret -= headphones_minimum_volume_user_dB; // shift to above zero + ret /= vol_range; // restrict to 0..1 range + return (ret*0.99) + 0.01; // shift to 0.01 to 0.99 range to distinguish from fake mute +} + +float audio_get_volume_relative(){ + if(audio_headphones_are_connected()){ + return audio_headphones_get_volume_relative(); + } else { + return audio_speaker_get_volume_relative(); + } +} + +void audio_update_jacksense(){ +#if defined(CONFIG_BADGE23_HW_GEN_P1) + line_in_connected = 0; +#elif defined(CONFIG_BADGE23_HW_GEN_P3) || defined(CONFIG_BADGE23_HW_GEN_P4) + line_in_connected = 1; +#elif defined(CONFIG_BADGE23_HW_GEN_P6) + line_in_connected = 0; //TODO: read port expander +#endif + +#if defined(CONFIG_BADGE23_HW_GEN_P1) + headphones_connected = 0; + headset_connected = 0; +#elif defined(CONFIG_BADGE23_HW_GEN_P3) || defined(CONFIG_BADGE23_HW_GEN_P4) || defined(CONFIG_BADGE23_HW_GEN_P6) + static uint8_t jck_prev = 255; // unreachable value -> initial comparision always untrue + uint8_t jck = max98091_i2c_read(0x02); + if(jck == 6){ + headphones_connected = 0; + headset_connected = 0; + } else if(jck == 0){ + headphones_connected = 1; + headset_connected = 0; + } else if(jck == 2){ + headphones_connected = 1; + headset_connected = 1; + } + + if(jck != jck_prev){ // update volume to trigger mutes if needed + audio_speaker_set_volume_dB(speaker_volume_dB); + audio_headphones_set_volume_dB(headphones_volume_dB); + } + jck_prev = jck; +#endif +} + typedef struct _audio_source_t{ void * render_data; float (* render_function)(void *); @@ -258,29 +585,15 @@ uint16_t count_audio_sources(){ } static void _audio_init(void) { + // TODO: this assumes I2C is already initialized init_scope(241); i2s_init(); + audio_update_jacksense(); //ESP_ERROR_CHECK(i2s_channel_enable(tx_chan)); TaskHandle_t handle; xTaskCreate(&audio_player_task, "Audio player", 3000, NULL, configMAX_PRIORITIES - 1, &handle); } -#define LR_PHASE 1 -#define NAT_LOG_DB 0.1151292546497023 - -static uint16_t _global_vol = 3000; - -void set_global_vol_dB(int8_t vol_dB){ - if(vol_dB < (BADGE_MIN_VOLUME_DB)){ - _global_vol = 0; - } else { - if(vol_dB > (BADGE_MAX_VOLUME_DB)) vol_dB = (BADGE_MAX_VOLUME_DB); - uint16_t buf = 3000 * exp(vol_dB * NAT_LOG_DB); - if(buf > (BADGE_VOLUME_LIMIT)) buf = (BADGE_VOLUME_LIMIT); - _global_vol = buf; - } -} - static void audio_player_task(void* arg) { int16_t buffer[DMA_BUFFER_SIZE * 2]; memset(buffer, 0, sizeof(buffer)); @@ -295,11 +608,11 @@ static void audio_player_task(void* arg) { audio_source = audio_source->next; } write_to_scope((int16_t) (1600. * sample)); - sample = _global_vol * sample; + sample = software_volume * (sample/10); if(sample > 32767) sample = 32767; if(sample < -32767) sample = -32767; buffer[i] = (int16_t) sample; - buffer[i+1] = LR_PHASE * buffer[i]; + buffer[i+1] = buffer[i]; } size_t count = 0; @@ -312,37 +625,3 @@ static void audio_player_task(void* arg) { } void audio_init() { _audio_init(); } - - -/* -#define NAT_LOG_SEMITONE 0.05776226504666215 - -void synth_set_bend(int i, float bend){ - if(bend != bend) return; - if((bend > -0.0001) && (bend < 0.0001)){ - synths[i].bend = 1; - } else { - synths[i].bend = exp(bend * NAT_LOG_SEMITONE); - } -} -*/ - -/* -void synth_stop(int i){ - if(synths[i].env_phase){ - synths[i].env_phase = 3; - } -} - -void synth_fullstop(int i){ - synths[i].env_phase = 0; -} - -void synth_start(int i){ - synths[i].env_phase = 1; //put into attack phase; -} - -float synth_get_env(int i){ - return synths[i].env; -} -*/ diff --git a/components/badge23/captouch.c b/components/badge23/captouch.c index 96082e60ac..8916c9269b 100644 --- a/components/badge23/captouch.c +++ b/components/badge23/captouch.c @@ -6,6 +6,7 @@ #include <stdint.h> #include <freertos/FreeRTOS.h> #include <freertos/atomic.h> +#include "badge23/lock.h" #define PETAL_PAD_TIP 0 #define PETAL_PAD_CCW 1 @@ -102,7 +103,9 @@ static struct ad714x_chip chip_bot_rev5 = {.addr = AD7147_ADDR_BOT, .gpio = 15, static esp_err_t ad714x_i2c_write(const struct ad714x_chip *chip, const uint16_t reg, const uint16_t data) { const uint8_t tx[] = {reg >> 8, reg & 0xFF, data >> 8, data & 0xFF}; + xSemaphoreTake(mutex_i2c, portMAX_DELAY); ESP_LOGI(TAG, "AD7147 write reg %X-> %X", reg, data); + xSemaphoreGive(mutex_i2c); return i2c_master_write_to_device(I2C_MASTER_NUM, chip->addr, tx, sizeof(tx), TIMEOUT_MS / portTICK_PERIOD_MS); } @@ -110,7 +113,9 @@ static esp_err_t ad714x_i2c_read(const struct ad714x_chip *chip, const uint16_t { const uint8_t tx[] = {reg >> 8, reg & 0xFF}; uint8_t rx[len * 2]; + xSemaphoreTake(mutex_i2c, portMAX_DELAY); esp_err_t ret = i2c_master_write_read_device(I2C_MASTER_NUM, chip->addr, tx, sizeof(tx), rx, sizeof(rx), TIMEOUT_MS / portTICK_PERIOD_MS); + xSemaphoreGive(mutex_i2c); for(int i = 0; i < len; i++) { data[i] = (rx[i * 2] << 8) | rx[i * 2 + 1]; } diff --git a/components/badge23/espan.c b/components/badge23/espan.c index 4df670e72a..0375ab5b45 100644 --- a/components/badge23/espan.c +++ b/components/badge23/espan.c @@ -4,6 +4,7 @@ #include "badge23/display.h" #include "badge23/spio.h" #include "badge23_hwconfig.h" +#include "badge23/lock.h" #include "esp_log.h" #include "driver/i2c.h" @@ -35,6 +36,7 @@ static const char *TAG = "espan"; #endif static QueueHandle_t i2c_queue = NULL; +static QueueHandle_t slow_system_status_queue = NULL; static uint8_t dummy_data; static esp_err_t i2c_master_init(void) @@ -56,6 +58,7 @@ static esp_err_t i2c_master_init(void) } #define CAPTOUCH_POLLING_PERIOD 10 +#define SLOW_SYSTEM_STATUS_PERIOD 200 static uint8_t hw_init_done = 0; void i2c_timer(TimerHandle_t data){ @@ -70,20 +73,35 @@ void i2c_task(void * data){ } } +void slow_system_status_timer(TimerHandle_t data){ + xQueueSend(slow_system_status_queue, &dummy_data, 0); +} + +void slow_system_status_task(void * data){ + while(1){ + xQueueReceive(slow_system_status_queue, &dummy_data, portMAX_DELAY); + //read out stuff like jack detection, battery status, usb connection etc. + audio_update_jacksense(); + } +} + +void locks_init(){ + mutex_i2c = xSemaphoreCreateMutex(); +} + void os_app_main(void) { + locks_init(); ESP_LOGI(TAG, "Starting on %s...", badge23_hw_name); ESP_ERROR_CHECK(i2c_master_init()); ESP_LOGI(TAG, "I2C initialized successfully"); - set_global_vol_dB(-90); audio_init(); leds_init(); init_buttons(); captouch_init(); //vTaskDelay(2000 / portTICK_PERIOD_MS); - set_global_vol_dB(0); captouch_force_calibration(); display_init(); @@ -91,12 +109,19 @@ void os_app_main(void) i2c_queue = xQueueCreate(1,1); TaskHandle_t i2c_task_handle; - //xTaskCreate(&i2c_task, "I2C task", 4096, NULL, configMAX_PRIORITIES , &i2c_task_handle); xTaskCreatePinnedToCore(&i2c_task, "I2C task", 4096, NULL, configMAX_PRIORITIES-1, &i2c_task_handle, 0); - TimerHandle_t i2c_timer_handle = xTimerCreate("I2C timer", pdMS_TO_TICKS(CAPTOUCH_POLLING_PERIOD), pdTRUE, (void *) 0, *i2c_timer); if( xTimerStart(i2c_timer_handle, 0 ) != pdPASS) ESP_LOGI(TAG, "I2C timer initialization failed"); + + slow_system_status_queue = xQueueCreate(1,1); + + TaskHandle_t slow_system_status_task_handle; + xTaskCreatePinnedToCore(&slow_system_status_task, "slow system status task", 4096, NULL, configMAX_PRIORITIES-2, &slow_system_status_task_handle, 0); + + TimerHandle_t slow_system_status_timer_handle = xTimerCreate("slow system status timer", pdMS_TO_TICKS(SLOW_SYSTEM_STATUS_PERIOD), pdTRUE, (void *) 0, *slow_system_status_timer); + if( xTimerStart(slow_system_status_timer_handle, 0 ) != pdPASS) ESP_LOGI(TAG, "I2C task initialization failed"); + hw_init_done = 1; } diff --git a/components/badge23/include/badge23/audio.h b/components/badge23/include/badge23/audio.h index 8de1ee6c5a..813a782bea 100644 --- a/components/badge23/include/badge23/audio.h +++ b/components/badge23/include/badge23/audio.h @@ -2,14 +2,160 @@ #include <stdint.h> #define SAMPLE_RATE 16000 -#define BADGE_MAX_VOLUME_DB 20 -#define BADGE_MIN_VOLUME_DB (-80) -#define BADGE_VOLUME_LIMIT 30000 +/* Initializes I2S bus, the audio task and required data structures. + * Expects an initialized I2C bus, will fail ungracefully otherwise (TODO). + * Requires the I2C lock. + */ void audio_init(); -void set_global_vol_dB(int8_t vol_dB); uint16_t count_audio_sources(); - uint16_t add_audio_source(void * render_data, void * render_function); void remove_audio_source(uint16_t index); + +/* Polls hardware to check if headphones, headset or line in are plugged + * into the 3.5mm jacks. If it detects a plug in the headphone jack, speakers + * are automatically muted. There is no override for this at this moment. + * + * Should be called periodically (100ms ish?) by a low priority task. Requires + * the I2C lock. + */ +void audio_update_jacksense(void); + +/* Returns 1 if headphones with or without microphone were connected to the + * headphone jack at the last call of audio_update_jacksense. + */ +uint8_t audio_headphones_are_connected(); + +/* Returns 1 if headphones with microphone were connected to the headphone jack + * at the last call of audio_update_jacksense. + */ +uint8_t audio_headset_is_connected(); + +/* If a sleeve contact mic doesn't pull the detection pin low enough the + * codec's built in headphone detection might fail. Calling this function + * with 'enable = 1' overrides the detection and assumes there's headphones + * plugged in. Call with 'enable = 0' to revert to automatic detection. + */ +void audio_headphones_detection_override(uint8_t enable); + +/* Attempts to set target volume for the headphone output/onboard speakers + * respectively, clamps/rounds if necessary and returns the actual volume. + * Absolute reference arbitrary. + * Does not unmute, use audio_{headphones_/speaker_/}set_mute as needed. + * Enters fake mute if requested volume is below the value set by + * audio_{headphones/speaker}_set_minimum_volume_user. + * + * Note: This function uses a hardware PGA for the coarse value and software + * for the fine value. These two methods are as of yet not synced so that there + * may be a transient volume "hiccup". "p1" badges only use software volume. + * The unspecified variant automatically chooses the adequate channel (**). + */ +float audio_headphones_set_volume_dB(float vol_dB); +float audio_speaker_set_volume_dB(float vol_dB); +float audio_set_volume_dB(float vol_dB); + +/* Like the audio_{headphones_/speaker_/}set_volume family but changes relative + * to last volume value. + */ +float audio_headphones_adjust_volume_dB(float vol_dB); +float audio_speaker_adjust_volume_dB(float vol_dB); +float audio_adjust_volume_dB(float vol_dB); + +/* Returns volume as set with audio_{headphones/speaker}_set_volume_dB. + * The unspecified variant automatically chooses the adequate channel (**). + */ +float audio_headphones_get_volume_dB(); +float audio_speaker_get_volume_dB(); +float audio_get_volume_dB(); + +/* Mutes (mute = 1) or unmutes (mute = 0) the specified channel. + * The unspecified variant automatically chooses the adequate channel (**). + * + * Note: Even if a channel is unmuted it might not play sound depending on + * the return value of audio_headphone_are_connected. There is no override for + * this (see HEADPHONE PORT POLICY below). + */ +void audio_headphones_set_mute(uint8_t mute); +void audio_speaker_set_mute(uint8_t mute); +void audio_set_mute(uint8_t mute); + +/* Returns 1 if channel is muted, 0 if channel is unmuted. + * The unspecified variant automatically chooses the adequate channel (**). + */ +uint8_t audio_headphones_get_mute(); +uint8_t audio_speaker_get_mute(); +uint8_t audio_get_mute(); + +/* Set the minimum and maximum allowed volume levels for speakers and headphones + * respectively. Clamps with hardware limitations. Maximum clamps below the minimum + * value, minimum clamps above the maximum. Returns clamped value. + */ +float audio_headphones_set_minimum_volume_dB(float vol_dB); +float audio_headphones_set_maximum_volume_dB(float vol_dB); +float audio_speaker_set_minimum_volume_dB(float vol_dB); +float audio_speaker_set_maximum_volume_dB(float vol_dB); + +/* Returns the minimum and maximum allowed volume levels for speakers and headphones + * respectively. Change with audio_{headphones/speaker}_set_{minimum/maximum}_volume_dB. + */ +float audio_headphones_get_minimum_volume_dB(); +float audio_headphones_get_maximum_volume_dB(); +float audio_speaker_get_minimum_volume_dB(); +float audio_speaker_get_maximum_volume_dB(); + +/* Syntactic sugar for drawing UI: Returns channel volume in a 0..1 range, + * scaled into a 0.01..1 range according to the values set with + * audio_{headphones_/speaker_/}set_{maximum/minimum}_volume_ and 0 if in a + * fake mute condition. + * + * The unspecified variant automatically chooses the adequate channel (**). + */ +float audio_headphones_get_volume_relative(); +float audio_speaker_get_volume_relative(); +float audio_get_volume_relative(); + +/* (**) if audio_headphones_are_connected returns 1 the "headphone" variant + * is chosen, else the "speaker" variant is chosen. + */ + +/* +HEADPHONE PORT POLICY + +Under normal circumstances it is an important feature to have a reliable speaker +mute when plugging in headphones. However, since the headphone port on the badge +can also be used for badge link, there are legimate cases where it is desirable to +have the speakers unmuted while a cable is plugged into the jack. + +As a person who plugs in the headphones on the tram, doesn't put them on, turns on +music to check if it's not accidentially playing on speakers and then finally puts +on headphones (temporarily, of course, intermittent checks if the speakers didn't +magically turn on are scheduled according to our general anxiety level) we wish to +make it difficult to accidentially have sound coming from the speakers. + +Our proposed logic is as follows (excluding boot conditions): + +1) Badge link TX cannot be enabled for any of the headphone jack pins without a +cable detected in the jack. This is to protect users from plugging in headphones +while badge link is active and receiving a short but potentially very loud burst +of digital data before the software can react to the state change. + +2) If the software detects that the headphone jack has changed from unplugged to +plugged it *always* turns off speakers, no exceptions. + +3) If a user wishes to TX on headphone badge link, they must confirm a warning that +having headphones plugged in may potentially cause hearing damage *every time*. + +4) If a user wishes to RX or TX on headphone badge link while playing sound on the +onboard speakers, they must confirm a warning *every time*. + +We understand that these means seem extreme, but we find them to be a sensible +default configuration to make sure people can safely operate the device without +needing to refer to a manual. + +(TX here means any state that is not constantly ~GND with whatever impedance. +While there are current limiting resistors (value TBD at the time of writing, but +presumably 100R-470R) in series with the GPIOs, they still can generate quite some +volume with standard 40Ohm-ish headphones. Ideally the analog switch will never +switch to the GPIOs without a cable plugged in.) +*/ diff --git a/components/badge23/include/badge23/lock.h b/components/badge23/include/badge23/lock.h new file mode 100644 index 0000000000..eb52077c7b --- /dev/null +++ b/components/badge23/include/badge23/lock.h @@ -0,0 +1,6 @@ +#pragma once + +#include <freertos/FreeRTOS.h> +#include <freertos/semphr.h> + +SemaphoreHandle_t mutex_i2c; diff --git a/components/badge23/spio.c b/components/badge23/spio.c index c93ca9d2eb..e93bc90456 100644 --- a/components/badge23/spio.c +++ b/components/badge23/spio.c @@ -3,6 +3,7 @@ #include "badge23_hwconfig.h" #include "stdint.h" #include "badge23/spio.h" +#include "badge23/lock.h" static int8_t leftbutton = 0; static int8_t rightbutton = 0; @@ -94,7 +95,11 @@ static void _init_buttons(){ void update_button_state(){ uint8_t port; + + xSemaphoreTake(mutex_i2c, portMAX_DELAY); esp_err_t ret = i2c_master_read_from_device(I2C_MASTER_NUM, 0b1101101, &port, sizeof(port), TIMEOUT_MS / portTICK_PERIOD_MS); + xSemaphoreGive(mutex_i2c); + uint8_t rr = port & (1ULL << RIGHT_BUTTON_RIGHT); uint8_t rm = port & (1ULL << RIGHT_BUTTON_MID); uint8_t rl = port & (1ULL << RIGHT_BUTTON_LEFT); diff --git a/python_payload/demo_menu.py b/python_payload/demo_menu.py index 25c024a719..b1de86295a 100644 --- a/python_payload/demo_menu.py +++ b/python_payload/demo_menu.py @@ -2,6 +2,7 @@ import menu import event import hardware import control +import audio import application import demo_worms,demo_sparabo,cap_touch_demo, melodic_demo, harmonic_demo @@ -10,7 +11,7 @@ import menu_settings,menu_tinysynth import time hardware.captouch_autocalib() -hardware.set_global_volume_dB(0) +audio.set_volume_dB(0) menu_demo = menu.Menu("demo") diff --git a/python_payload/demo_sparabo.py b/python_payload/demo_sparabo.py index 36ead3a4aa..f22a12a6aa 100644 --- a/python_payload/demo_sparabo.py +++ b/python_payload/demo_sparabo.py @@ -8,6 +8,7 @@ from synth import tinysynth import application import ui +import audio popcorn = [9,7,9,5,0,5,-3,999] @@ -37,7 +38,7 @@ def on_step(data): class AppSparabo(application.Application): def on_init(self): - hardware.set_global_volume_dB(0) + audio.set_volume_dB(0) self.synth = tinysynth(440,1) self.synth.decay(25) @@ -55,4 +56,4 @@ class AppSparabo(application.Application): self.sequencer.stop() -app = AppSparabo("sequencer") \ No newline at end of file +app = AppSparabo("sequencer") diff --git a/python_payload/menu_settings.py b/python_payload/menu_settings.py index 860c7e1a75..6e4b985724 100644 --- a/python_payload/menu_settings.py +++ b/python_payload/menu_settings.py @@ -1,5 +1,6 @@ import menu import event +import audio import control import ui import hardware @@ -28,7 +29,7 @@ def set_controls_overlay(value): def set_volume(value): db = int(value*60-40) print("DB",db) - hardware.set_global_volume_dB(db) + audio.set_volume_dB(db) def get_menu(): @@ -48,4 +49,4 @@ def get_menu(): return m -m = get_menu() \ No newline at end of file +m = get_menu() diff --git a/python_payload/menu_tinysynth.py b/python_payload/menu_tinysynth.py index 0d012b4442..a77d138896 100644 --- a/python_payload/menu_tinysynth.py +++ b/python_payload/menu_tinysynth.py @@ -5,6 +5,7 @@ import event import control import ui import hardware +import audio ui_input = ui.Icon("") @@ -20,7 +21,7 @@ def set_play(value): def set_volume(value): db = int(value*60-40) print("DB",db) - hardware.set_global_volume_dB(db) + audio.set_volume_dB(db) def set_frequency(value): f = 440+value*440 @@ -42,4 +43,4 @@ def get_menu(): synth = tinysynth(440,0) -m = get_menu() \ No newline at end of file +m = get_menu() diff --git a/python_payload/utils.py b/python_payload/utils.py index 2341cf9a0c..060d754b65 100644 --- a/python_payload/utils.py +++ b/python_payload/utils.py @@ -1,5 +1,6 @@ import time from hardware import * +import audio RED = 0b1111100000000000 GREEN = 0b0000011111100000 @@ -24,12 +25,7 @@ def long_bottom_petal_captouch_blocking(num, ms): return False def draw_volume_slider(ctx, volume): - length = 96 + ((volume - 20) * 1.6) - if length > 96: - length = 96 - if length < 0: - length = 0 - length = int(length) + length = int(96*volume) ctx.rgb(0,0,0)#dummy ctx.round_rectangle(-49,41,98,8,3).fill()#dummy idk diff --git a/usermodule/micropython.cmake b/usermodule/micropython.cmake index 0be5f99f67..94c79a75f7 100644 --- a/usermodule/micropython.cmake +++ b/usermodule/micropython.cmake @@ -6,6 +6,7 @@ add_library(usermod_badge23 INTERFACE) target_sources(usermod_badge23 INTERFACE ${CMAKE_CURRENT_LIST_DIR}/mp_hardware.c + ${CMAKE_CURRENT_LIST_DIR}/mp_audio.c ${CMAKE_CURRENT_LIST_DIR}/mp_synth.c ${CMAKE_CURRENT_LIST_DIR}/mp_kernel.c ) diff --git a/usermodule/mp_audio.c b/usermodule/mp_audio.c new file mode 100644 index 0000000000..b2300b88d5 --- /dev/null +++ b/usermodule/mp_audio.c @@ -0,0 +1,234 @@ +// probably doesn't need all of these idk +#include <stdio.h> +#include <string.h> + +#include "py/runtime.h" +#include "py/mphal.h" +#include "mphalport.h" +#include "modmachine.h" +#include "extmod/virtpin.h" +#include "machine_rtc.h" +#include "py/builtin.h" +#include "py/runtime.h" + +#include "badge23/audio.h" +#include "badge23_hwconfig.h" + +// documentation: these are all super thin wrappers for the c api in components/badge23/include/badge23/audio.h + +STATIC mp_obj_t mp_headset_is_connected() { + return mp_obj_new_int(audio_headset_is_connected()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_headset_is_connected_obj, mp_headset_is_connected); + +STATIC mp_obj_t mp_headphones_are_connected() { + return mp_obj_new_int(audio_headphones_are_connected()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_headphones_are_connected_obj, mp_headphones_are_connected); + +STATIC mp_obj_t mp_headphones_detection_override(mp_obj_t enable) { + audio_headphones_detection_override(mp_obj_get_int(enable)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_headphones_detection_override_obj, mp_headphones_detection_override); + + + +STATIC mp_obj_t mp_headphones_set_volume_dB(mp_obj_t vol_dB) { + return mp_obj_new_float(audio_headphones_set_volume_dB(mp_obj_get_float(vol_dB))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_headphones_set_volume_dB_obj, mp_headphones_set_volume_dB); + +STATIC mp_obj_t mp_speaker_set_volume_dB(mp_obj_t vol_dB) { + return mp_obj_new_float(audio_speaker_set_volume_dB(mp_obj_get_float(vol_dB))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_speaker_set_volume_dB_obj, mp_speaker_set_volume_dB); + +STATIC mp_obj_t mp_set_volume_dB(mp_obj_t vol_dB) { + return mp_obj_new_float(audio_set_volume_dB(mp_obj_get_float(vol_dB))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_set_volume_dB_obj, mp_set_volume_dB); + + + +STATIC mp_obj_t mp_headphones_adjust_volume_dB(mp_obj_t vol_dB) { + return mp_obj_new_float(audio_headphones_adjust_volume_dB(mp_obj_get_float(vol_dB))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_headphones_adjust_volume_dB_obj, mp_headphones_adjust_volume_dB); + +STATIC mp_obj_t mp_speaker_adjust_volume_dB(mp_obj_t vol_dB) { + return mp_obj_new_float(audio_speaker_adjust_volume_dB(mp_obj_get_float(vol_dB))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_speaker_adjust_volume_dB_obj, mp_speaker_adjust_volume_dB); + +STATIC mp_obj_t mp_adjust_volume_dB(mp_obj_t vol_dB) { + return mp_obj_new_float(audio_adjust_volume_dB(mp_obj_get_float(vol_dB))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_adjust_volume_dB_obj, mp_adjust_volume_dB); + + + +STATIC mp_obj_t mp_headphones_get_volume_dB() { + return mp_obj_new_float(audio_headphones_get_volume_dB()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_headphones_get_volume_dB_obj, mp_headphones_get_volume_dB); + +STATIC mp_obj_t mp_speaker_get_volume_dB() { + return mp_obj_new_float(audio_speaker_get_volume_dB()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_speaker_get_volume_dB_obj, mp_speaker_get_volume_dB); + +STATIC mp_obj_t mp_get_volume_dB() { + return mp_obj_new_float(audio_get_volume_dB()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_get_volume_dB_obj, mp_get_volume_dB); + + + +STATIC mp_obj_t mp_headphones_get_mute() { + return mp_obj_new_int(audio_headphones_get_mute()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_headphones_get_mute_obj, mp_headphones_get_mute); + +STATIC mp_obj_t mp_speaker_get_mute() { + return mp_obj_new_int(audio_speaker_get_mute()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_speaker_get_mute_obj, mp_speaker_get_mute); + +STATIC mp_obj_t mp_get_mute() { + return mp_obj_new_int(audio_get_mute()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_get_mute_obj, mp_get_mute); + + + +STATIC mp_obj_t mp_headphones_set_mute(mp_obj_t mute) { + audio_headphones_set_mute(mp_obj_get_int(mute)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_headphones_set_mute_obj, mp_headphones_set_mute); + +STATIC mp_obj_t mp_speaker_set_mute(mp_obj_t mute) { + audio_speaker_set_mute(mp_obj_get_int(mute)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_speaker_set_mute_obj, mp_speaker_set_mute); + +STATIC mp_obj_t mp_set_mute(mp_obj_t mute) { + audio_set_mute(mp_obj_get_int(mute)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_set_mute_obj, mp_set_mute); + + + +STATIC mp_obj_t mp_headphones_set_minimum_volume_dB(mp_obj_t vol_dB) { + return mp_obj_new_float(audio_headphones_set_minimum_volume_dB(mp_obj_get_float(vol_dB))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_headphones_set_minimum_volume_dB_obj, mp_headphones_set_minimum_volume_dB); + +STATIC mp_obj_t mp_speaker_set_minimum_volume_dB(mp_obj_t vol_dB) { + return mp_obj_new_float(audio_speaker_set_minimum_volume_dB(mp_obj_get_float(vol_dB))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_speaker_set_minimum_volume_dB_obj, mp_speaker_set_minimum_volume_dB); + +STATIC mp_obj_t mp_headphones_set_maximum_volume_dB(mp_obj_t vol_dB) { + return mp_obj_new_float(audio_headphones_set_maximum_volume_dB(mp_obj_get_float(vol_dB))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_headphones_set_maximum_volume_dB_obj, mp_headphones_set_maximum_volume_dB); + +STATIC mp_obj_t mp_speaker_set_maximum_volume_dB(mp_obj_t vol_dB) { + return mp_obj_new_float(audio_speaker_set_maximum_volume_dB(mp_obj_get_float(vol_dB))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_speaker_set_maximum_volume_dB_obj, mp_speaker_set_maximum_volume_dB); + + + +STATIC mp_obj_t mp_headphones_get_minimum_volume_dB() { + return mp_obj_new_float(audio_headphones_get_minimum_volume_dB()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_headphones_get_minimum_volume_dB_obj, mp_headphones_get_minimum_volume_dB); + +STATIC mp_obj_t mp_speaker_get_minimum_volume_dB() { + return mp_obj_new_float(audio_speaker_get_minimum_volume_dB()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_speaker_get_minimum_volume_dB_obj, mp_speaker_get_minimum_volume_dB); + +STATIC mp_obj_t mp_headphones_get_maximum_volume_dB() { + return mp_obj_new_float(audio_headphones_get_maximum_volume_dB()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_headphones_get_maximum_volume_dB_obj, mp_headphones_get_maximum_volume_dB); + +STATIC mp_obj_t mp_speaker_get_maximum_volume_dB() { + return mp_obj_new_float(audio_speaker_get_maximum_volume_dB()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_speaker_get_maximum_volume_dB_obj, mp_speaker_get_maximum_volume_dB); + + + +STATIC mp_obj_t mp_headphones_get_volume_relative() { + return mp_obj_new_float(audio_headphones_get_volume_relative()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_headphones_get_volume_relative_obj, mp_headphones_get_volume_relative); + +STATIC mp_obj_t mp_speaker_get_volume_relative() { + return mp_obj_new_float(audio_speaker_get_volume_relative()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_speaker_get_volume_relative_obj, mp_speaker_get_volume_relative); + +STATIC mp_obj_t mp_get_volume_relative() { + return mp_obj_new_float(audio_get_volume_relative()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_get_volume_relative_obj, mp_get_volume_relative); + + +STATIC const mp_rom_map_elem_t mp_module_audio_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audio) }, + { MP_ROM_QSTR(MP_QSTR_headset_is_connected), MP_ROM_PTR(&mp_headset_is_connected_obj) }, + { MP_ROM_QSTR(MP_QSTR_headphones_are_connected), MP_ROM_PTR(&mp_headphones_are_connected_obj) }, + { MP_ROM_QSTR(MP_QSTR_headphones_detection_override), MP_ROM_PTR(&mp_headphones_detection_override_obj) }, + + { MP_ROM_QSTR(MP_QSTR_headphones_set_volume_dB), MP_ROM_PTR(&mp_headphones_set_volume_dB_obj) }, + { MP_ROM_QSTR(MP_QSTR_speaker_set_volume_dB), MP_ROM_PTR(&mp_speaker_set_volume_dB_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_volume_dB), MP_ROM_PTR(&mp_set_volume_dB_obj) }, + + { MP_ROM_QSTR(MP_QSTR_headphones_adjust_volume_dB), MP_ROM_PTR(&mp_headphones_adjust_volume_dB_obj) }, + { MP_ROM_QSTR(MP_QSTR_speaker_adjust_volume_dB), MP_ROM_PTR(&mp_speaker_adjust_volume_dB_obj) }, + { MP_ROM_QSTR(MP_QSTR_adjust_volume_dB), MP_ROM_PTR(&mp_adjust_volume_dB_obj) }, + + { MP_ROM_QSTR(MP_QSTR_headphones_get_volume_dB), MP_ROM_PTR(&mp_headphones_get_volume_dB_obj) }, + { MP_ROM_QSTR(MP_QSTR_speaker_get_volume_dB), MP_ROM_PTR(&mp_speaker_get_volume_dB_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_volume_dB), MP_ROM_PTR(&mp_get_volume_dB_obj) }, + + { MP_ROM_QSTR(MP_QSTR_headphones_get_mute), MP_ROM_PTR(&mp_headphones_get_mute_obj) }, + { MP_ROM_QSTR(MP_QSTR_speaker_get_mute), MP_ROM_PTR(&mp_speaker_get_mute_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_mute), MP_ROM_PTR(&mp_get_mute_obj) }, + + { MP_ROM_QSTR(MP_QSTR_headphones_set_mute), MP_ROM_PTR(&mp_headphones_set_mute_obj) }, + { MP_ROM_QSTR(MP_QSTR_speaker_set_mute), MP_ROM_PTR(&mp_speaker_set_mute_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_mute), MP_ROM_PTR(&mp_set_mute_obj) }, + + { MP_ROM_QSTR(MP_QSTR_headphones_set_minimum_volume_dB), MP_ROM_PTR(&mp_headphones_set_minimum_volume_dB_obj) }, + { MP_ROM_QSTR(MP_QSTR_speaker_set_minimum_volume_dB), MP_ROM_PTR(&mp_speaker_set_minimum_volume_dB_obj) }, + { MP_ROM_QSTR(MP_QSTR_headphones_set_maximum_volume_dB), MP_ROM_PTR(&mp_headphones_set_maximum_volume_dB_obj) }, + { MP_ROM_QSTR(MP_QSTR_speaker_set_maximum_volume_dB), MP_ROM_PTR(&mp_speaker_set_maximum_volume_dB_obj) }, + + { MP_ROM_QSTR(MP_QSTR_headphones_get_minimum_volume_dB), MP_ROM_PTR(&mp_headphones_get_minimum_volume_dB_obj) }, + { MP_ROM_QSTR(MP_QSTR_speaker_get_minimum_volume_dB), MP_ROM_PTR(&mp_speaker_get_minimum_volume_dB_obj) }, + { MP_ROM_QSTR(MP_QSTR_headphones_get_maximum_volume_dB), MP_ROM_PTR(&mp_headphones_get_maximum_volume_dB_obj) }, + { MP_ROM_QSTR(MP_QSTR_speaker_get_maximum_volume_dB), MP_ROM_PTR(&mp_speaker_get_maximum_volume_dB_obj) }, + + { MP_ROM_QSTR(MP_QSTR_headphones_get_volume_relative), MP_ROM_PTR(&mp_headphones_get_volume_relative_obj) }, + { MP_ROM_QSTR(MP_QSTR_speaker_get_volume_relative), MP_ROM_PTR(&mp_speaker_get_volume_relative_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_volume_relative), MP_ROM_PTR(&mp_get_volume_relative_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_audio_globals, mp_module_audio_globals_table); + +const mp_obj_module_t mp_module_audio = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_audio_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_audio, mp_module_audio); + diff --git a/usermodule/mp_hardware.c b/usermodule/mp_hardware.c index f0fc7fe79b..a692e884ec 100644 --- a/usermodule/mp_hardware.c +++ b/usermodule/mp_hardware.c @@ -136,10 +136,9 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_right_button_get_obj, mp_right_button_get); STATIC mp_obj_t mp_set_global_volume_dB(size_t n_args, const mp_obj_t *args) { - mp_float_t x = mp_obj_get_float(args[0]); - int8_t d = x; - set_global_vol_dB(d); - mp_float_t l = x; + //TODO: DEPRECATE + mp_float_t d = mp_obj_get_float(args[0]); + audio_set_volume_dB(d); return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_set_global_volume_dB_obj, 1, 2, mp_set_global_volume_dB); -- GitLab