From d6cfcd3596eeae840d5e2fcdfdc4e77a3d2c8fbb Mon Sep 17 00:00:00 2001
From: moon2 <moon2protonmail@protonmail.com>
Date: Sun, 11 Jun 2023 21:20:26 +0200
Subject: [PATCH] audio: line in api

---
 components/badge23/audio.c                 | 203 ++++++++++++++++-----
 components/badge23/include/badge23/audio.h |  43 ++++-
 components/badge23/include/badge23/synth.h |  17 +-
 components/badge23/spio.c                  |  22 +++
 components/badge23/synth.c                 |  20 +-
 usermodule/mp_audio.c                      |  90 +++++++++
 6 files changed, 315 insertions(+), 80 deletions(-)

diff --git a/components/badge23/audio.c b/components/badge23/audio.c
index 9e0852547d..46aa8c8b6b 100644
--- a/components/badge23/audio.c
+++ b/components/badge23/audio.c
@@ -20,8 +20,8 @@
 
 static void audio_player_task(void* arg);
 
-#define DMA_BUFFER_SIZE 64
-#define DMA_BUFFER_COUNT 2
+#define DMA_BUFFER_SIZE 64*3
+#define DMA_BUFFER_COUNT 4
 #define I2S_PORT 0
 
 // used for exp(vol_dB * NAT_LOG_DB)
@@ -49,6 +49,13 @@ 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;
 
+static uint8_t input_source = AUDIO_INPUT_SOURCE_NONE;
+static uint8_t headset_gain = 0;
+
+static int32_t input_thru_vol;
+static int32_t input_thru_vol_int;
+static int32_t input_thru_mute = 1;
+
 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; }
@@ -59,6 +66,8 @@ float audio_headphones_get_maximum_volume_dB(){ return headphones_maximum_volume
 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; }
+uint8_t audio_input_get_source(){ return input_source; }
+uint8_t audio_headset_get_gain_dB(){ return headset_gain; }
 
 #if defined(CONFIG_BADGE23_HW_GEN_P3) || defined(CONFIG_BADGE23_HW_GEN_P4) || defined(CONFIG_BADGE23_HW_GEN_P6)
 
@@ -91,6 +100,67 @@ static esp_err_t max98091_i2c_write_readback(const uint8_t reg, const uint8_t da
     return ret;
 }
 
+void audio_headphones_line_in_set_hardware_thru(bool enable){
+    max98091_i2c_write_readback(0x2B, (1<<5) | (1<<4)); // Enable Headphone Mixer
+    uint8_t trust_issue = enable ? 1 : 0;
+    max98091_i2c_write_readback(0x29, (trust_issue<<2) | (1<<1)); // Line A + DAC_L -> Left HP
+    max98091_i2c_write_readback(0x2A, (trust_issue<<3) | (1<<0)); // LIne B + DAC_R -> Right HP
+}
+
+
+void audio_speaker_line_in_set_hardware_thru(bool enable){
+    uint8_t trust_issue = enable ? 1 : 0;
+    max98091_i2c_write_readback(0x2E, (trust_issue<<2) | (1<<1)); // Line A -> Left Speaker
+    max98091_i2c_write_readback(0x2F, (trust_issue<<3) | (1<<0)); // LIne B -> Right Speaker
+}
+
+
+void audio_line_in_set_hardware_thru(bool enable){
+    audio_speaker_line_in_set_hardware_thru(enable);
+    audio_headphones_line_in_set_hardware_thru(enable);
+}
+
+static void onboard_mic_set_power(bool enable){
+    uint8_t trust_issue = enable ? 1 : 0;
+    max98091_i2c_write_readback(0x13, (1<<4) | (1<<5) | (trust_issue<<1) | (trust_issue<<0) );
+}
+
+#define ADC_MIXER_MIC_1     5
+#define ADC_MIXER_LINE_IN_B    4
+#define ADC_MIXER_LINE_IN_A    3
+
+static void adc_left_set_mixer(uint8_t mask){
+    max98091_i2c_write_readback(0x15, mask);
+}
+
+static void adc_right_set_mixer(uint8_t mask){
+    max98091_i2c_write_readback(0x16, mask);
+}
+
+void audio_input_set_source(uint8_t source){
+    if(source == AUDIO_INPUT_SOURCE_NONE){
+        onboard_mic_set_power(0);
+        adc_left_set_mixer(0);
+        adc_right_set_mixer(0);
+        input_source = source;
+    } else if(source == AUDIO_INPUT_SOURCE_LINE_IN){
+        onboard_mic_set_power(0);
+        adc_left_set_mixer(1<<ADC_MIXER_LINE_IN_A);
+        adc_right_set_mixer(1<<ADC_MIXER_LINE_IN_B);
+        input_source = source;
+    } else if(source == AUDIO_INPUT_SOURCE_HEADSET_MIC){
+        onboard_mic_set_power(0);
+        adc_left_set_mixer(1<<ADC_MIXER_MIC_1);
+        adc_right_set_mixer(1<<ADC_MIXER_MIC_1);
+        input_source = source;
+    } else if(source == AUDIO_INPUT_SOURCE_ONBOARD_MIC){
+        onboard_mic_set_power(1);
+        adc_left_set_mixer(0);
+        adc_right_set_mixer(0);
+        input_source = source;
+    }
+}
+
 static void init_codec()
 {
     // Enable CODEC
@@ -103,7 +173,7 @@ static void init_codec()
 
     ESP_ERROR_CHECK(max98091_i2c_write_readback(0x1b, 1 << 4)); // pclk = mclk / 1
 
-    ESP_ERROR_CHECK(max98091_i2c_write_readback(0x26,  (1 << 7) | (1 << 6))); // music, dc filter in record
+    ESP_ERROR_CHECK(max98091_i2c_write_readback(0x26,  (1 << 7) | (1 << 6) | (1<<5))); // music, dc filter in record and playback
 
     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
@@ -116,47 +186,28 @@ static void init_codec()
     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_readback(0x2E, 1)); // Left DAC -> Left Speaker
-    ESP_ERROR_CHECK(max98091_i2c_write_readback(0x2F, 2)); // Right DAC -> 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_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_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
+    //ESP_ERROR_CHECK(max98091_i2c_write_readback(0x44, (1<<2) | (1<<1) | (1<<0) )); // 128x oversampling, dithering, high performance ADC
+    ESP_ERROR_CHECK(max98091_i2c_write_readback(0x44, (1<<1) | (1<<0) )); // 64x oversampling, dithering, high performance ADC
 
-    max98091_i2c_write_readback(0x13, (1<<4) | (1<<5) | (1<<1) | (1<<0) ); // enable digital mic
-
-    // Enable headset mic
-#if 0
+    // Enable headset mic preamp
     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_readback(0x0F, (1<<0) )); // IN5/IN6 to MIC1
+
+    audio_line_in_set_hardware_thru(0);
+    audio_headset_set_gain_dB(0);
+    audio_input_set_source(AUDIO_INPUT_SOURCE_NONE);
+    audio_input_thru_set_volume_dB(-20); //mute is on by default
 
     ESP_ERROR_CHECK(max98091_i2c_write_readback(0x3F, (1<<1) | (1<<0))); // output enable: enable 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_readback(0x27, (1<<4) | (1<<5)); // full playback gain
 
-    //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_readback(0x41, 0x0)); // disable all digital filters except for dc blocking
 
     ESP_ERROR_CHECK(max98091_i2c_write_readback(0x3D, 1<<7)); // jack detect enable
     printf("4 readbacks failing here is normal dw ^w^\n");
@@ -167,13 +218,11 @@ static void i2s_init(void){
     vTaskDelay(100 / portTICK_PERIOD_MS); // dunno if necessary
     
     static const i2s_config_t i2s_config = {
-        .mode = I2S_MODE_MASTER | I2S_MODE_TX,
+        .mode = I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_RX,
         .sample_rate = SAMPLE_RATE,
         .bits_per_sample = 16,
         .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
-        //.communication_format = I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB,
         .communication_format = I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_LSB,
-        //^...technically wrong but works...? in idf v5 it's msb but don't try that late at night
         .intr_alloc_flags = 0, // default interrupt priority
         .dma_buf_count = DMA_BUFFER_COUNT,
         .dma_buf_len = DMA_BUFFER_SIZE,
@@ -184,9 +233,9 @@ static void i2s_init(void){
         .mck_io_num = 18,
         .ws_io_num = 11,
         .data_out_num = 12,
-        .data_in_num = I2S_PIN_NO_CHANGE
+        .data_in_num = 13,
     };
-    i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
+    ESP_ERROR_CHECK(i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL));
 
     i2s_set_pin(I2S_PORT, &pin_config);
 
@@ -257,6 +306,24 @@ void audio_speaker_set_mute(uint8_t mute){
     audio_speaker_set_volume_dB(speaker_volume_dB);
 }
 
+uint8_t audio_headset_set_gain_dB(uint8_t gain_dB){
+    if(gain_dB < 0) gain_dB = 0;
+    if(gain_dB > 50) gain_dB = 50;
+
+    uint8_t hi_bits = 0b01;
+    if(gain_dB > 30){
+        gain_dB -= 30;
+        hi_bits = 0b11;
+    } else if(gain_dB > 20){
+        gain_dB -= 20;
+        hi_bits = 0b10;
+    }
+    uint8_t reg = (hi_bits<<5) | (0x14-gain_dB);
+    max98091_i2c_write(0x10, reg);
+    headset_gain = gain_dB;
+    return headset_gain;
+}
+
 #elif defined(CONFIG_BADGE23_HW_GEN_P1)
 
 #define MAX_VOLUME_DB 10
@@ -318,6 +385,12 @@ void audio_speaker_set_mute(uint8_t mute){
     }
 }
 
+void audio_headphones_line_in_set_hardware_thru(bool enable){}
+void audio_speaker_line_in_set_hardware_thru(bool enable){}
+void audio_line_in_set_hardware_thru(bool enable){}
+void audio_input_set_source(uint8_t source){}
+uint8_t audio_headset_set_gain_dB(uint8_t gain_dB){}
+
 #else
 #error "audio not implemented for this badge generation"
 #endif
@@ -590,38 +663,68 @@ static void _audio_init(void) {
     // TODO: this assumes I2C is already initialized
     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);
 }
 
+float audio_input_thru_set_volume_dB(float vol_dB){
+    if(vol_dB > 0) vol_dB = 0;
+    input_thru_vol_int = (int32_t) (32768. * exp(vol_dB * NAT_LOG_DB));
+    input_thru_vol = vol_dB;
+    return input_thru_vol;
+}
+
+float audio_input_thru_get_volume_dB(){ return input_thru_vol; }
+void audio_input_thru_set_mute(bool mute){ input_thru_mute = mute; }
+bool audio_input_thru_get_mute(){ return input_thru_mute; }
+
 static void audio_player_task(void* arg) {
-    int16_t buffer[DMA_BUFFER_SIZE * 2];
-    memset(buffer, 0, sizeof(buffer));
+    int16_t buffer_tx[DMA_BUFFER_SIZE * 2];
+    int16_t buffer_rx[DMA_BUFFER_SIZE * 2];
+    memset(buffer_tx, 0, sizeof(buffer_tx));
+    memset(buffer_rx, 0, sizeof(buffer_rx));
+    size_t count;
 
     while(true) {
 
         for(int i = 0; i < (DMA_BUFFER_SIZE * 2); i += 2){
-            float sample = 0;
+            float acc = 0;
+            int32_t sample = 0;
             audio_source_t * audio_source = _audio_sources;
             while(audio_source != NULL){
-                sample += (*(audio_source->render_function))(audio_source->render_data);
+                acc += (*(audio_source->render_function))(audio_source->render_data);
                 audio_source = audio_source->next;
             }
-            st3m_scope_write((int16_t) (1600. * sample));
-            sample = software_volume * (sample/10);
+            st3m_scope_write((int16_t) (1600. * acc));
+
+            sample += 32767 * acc;
+            sample = (sample * software_volume) >> 15;
+
             if(sample > 32767) sample = 32767;
             if(sample < -32767) sample = -32767;
-            buffer[i] = (int16_t) sample;
-            buffer[i+1] = buffer[i];
+            buffer_tx[i] = sample;
+            buffer_tx[i+1] = sample;
+            if(!input_thru_mute){
+                buffer_tx[i] += (((int32_t) buffer_rx[i]) * input_thru_vol_int) >> 15;
+                buffer_tx[i+1] += (((int32_t) buffer_rx[i+1]) * input_thru_vol_int) >> 15;
+            }
         }
 
-        size_t count = 0;
-        i2s_write(I2S_PORT, buffer, sizeof(buffer), &count, 1000);
-        if (count != sizeof(buffer)) {
-            printf("i2s_write_bytes: count (%d) != length (%d)\n", count, sizeof(buffer));
+        count = 0;
+        i2s_write(I2S_PORT, buffer_tx, sizeof(buffer_tx), &count, 1000);
+        if (count != sizeof(buffer_tx)) {
+            printf("i2s_write_bytes: count (%d) != length (%d)\n", count, sizeof(buffer_tx));
             abort();
         }
+
+#if defined(CONFIG_BADGE23_HW_GEN_P3) || defined(CONFIG_BADGE23_HW_GEN_P4) || defined(CONFIG_BADGE23_HW_GEN_P6)
+        count = 0;
+        i2s_read(I2S_PORT, buffer_rx, sizeof(buffer_rx), &count, 1000);
+        if (count != sizeof(buffer_rx)) {
+            printf("i2s_read_bytes: count (%d) != length (%d)\n", count, sizeof(buffer_rx));
+            abort();
+        }
+#endif
     }
 }
 
diff --git a/components/badge23/include/badge23/audio.h b/components/badge23/include/badge23/audio.h
index 813a782bea..93ee3edf81 100644
--- a/components/badge23/include/badge23/audio.h
+++ b/components/badge23/include/badge23/audio.h
@@ -1,7 +1,13 @@
 #pragma once
 #include <stdint.h>
+#include <stdbool.h>
 
-#define SAMPLE_RATE 16000
+#define SAMPLE_RATE 48000
+
+#define AUDIO_INPUT_SOURCE_NONE 0
+#define AUDIO_INPUT_SOURCE_LINE_IN 1
+#define AUDIO_INPUT_SOURCE_HEADSET_MIC 2
+#define AUDIO_INPUT_SOURCE_ONBOARD_MIC 3
 
 /* Initializes I2S bus, the audio task and required data structures.
  * Expects an initialized I2C bus, will fail ungracefully otherwise (TODO).
@@ -119,6 +125,41 @@ float audio_get_volume_relative();
  *      is chosen, else the "speaker" variant is chosen.
  */
 
+/* These route whatever is on the line in port directly to the headphones or
+ * speaker respectively (enable = 1), or don't (enable = 0). Is affected by mute
+ * and coarse hardware volume settings, however software fine volume is not applied.
+ *
+ * Good for testing, might deprecate later, idk~
+ */
+void audio_headphones_line_in_set_hardware_thru(bool enable);
+void audio_speaker_line_in_set_hardware_thru(bool enable);
+void audio_line_in_set_hardware_thru(bool enable);
+
+/* The codec can transmit audio data from different sources. This function enables
+ * one or no source as provided by the AUDIO_INPUT_SOURCE_* constants.
+ *
+ * Note: The onboard digital mic turns on an LED on the top board if it receives
+ * a clock signal which is considered a good proxy for its capability of reading data.
+ *
+ * TODO: check if sources are available
+ */
+void audio_input_set_source(uint8_t source);
+
+/* Returns the currently selected input source.
+ */
+uint8_t audio_input_get_source();
+
+/* Hardware preamp gain
+ */
+uint8_t audio_headset_set_gain_dB(uint8_t gain_dB);
+uint8_t audio_headset_get_gain_dB();
+
+float audio_input_thru_set_volume_dB(float vol_dB);
+float audio_input_thru_get_volume_dB();
+void audio_input_thru_set_mute(bool mute);
+bool audio_input_thru_get_mute();
+
+
 /*
 HEADPHONE PORT POLICY
 
diff --git a/components/badge23/include/badge23/synth.h b/components/badge23/include/badge23/synth.h
index 50f52fcd5a..cd52e28bd8 100644
--- a/components/badge23/include/badge23/synth.h
+++ b/components/badge23/include/badge23/synth.h
@@ -27,26 +27,11 @@ typedef struct {
     int8_t      overflow_event; //set to -1 when counter underflows (below -1),
                                 //set to +1 when counter overflows (above 1)
                                 //not reset or used by anything so far
+    uint8_t     undersampling_counter;
     uint16_t    noise_reg;
 } trad_osc_t;
 
-//#define KS_BUFFER_SIZE (SAMPLE_RATE)/20
-#define KS_BUFFER_SIZE 800
-
-typedef struct {
-    //user variables
-    float freq;                 //frequency in hertz, negative frequencies are rectified,
-                                //minimum freq determined by KS_BUFFER_SIZE
-    float feedback;             //feedback value, will be compensated with frequency
-                                //for equal decay across spectrum, [-1..1] without
-    
-    //internal data storage, not for user access
-    float tape[KS_BUFFER_SIZE]; //the delay chain
-    float real_feedback;        //compensated feedback value
-} ks_osc_t; //karplus strong
-
 float run_trad_osc(trad_osc_t * osc);
-
 void trad_osc_set_freq_semitone(trad_osc_t * osc, float bend);
 void trad_osc_set_freq_Hz(trad_osc_t * osc, float freq);
 void trad_osc_set_waveform(trad_osc_t * osc, uint8_t waveform);
diff --git a/components/badge23/spio.c b/components/badge23/spio.c
index c405026cc7..f57c80c865 100644
--- a/components/badge23/spio.c
+++ b/components/badge23/spio.c
@@ -52,11 +52,17 @@
 #define LEFT_BUTTON_LEFT (7+8)
 #define RIGHT_BUTTON_RIGHT (5+8)
 
+#define LINE_IN_JACKSENSE (6+8)
+#define CHARGER_STATE (2+8)
+
 #endif
 
 static int8_t leftbutton = 0;
 static int8_t rightbutton = 0;
 
+static bool line_in_jacksense = 1;
+static bool charger_state;
+
 static bool menu_button_left = 0;
 
 static uint8_t badge_link_enabled = 0;
@@ -295,6 +301,9 @@ void update_button_state(){
     uint8_t rm = gpio_get_level(RIGHT_BUTTON_MID);
     uint8_t lm = gpio_get_level(LEFT_BUTTON_MID);
 
+    line_in_jacksense = max7321s_get_pin(LINE_IN_JACKSENSE);
+    charger_state = max7321s_get_pin(CHARGER_STATE);
+
     int8_t new_rightbutton = process_button_state(rr, rm, rl);
     int8_t new_leftbutton = process_button_state(lr, lm, ll);
     if(new_rightbutton != rightbutton){
@@ -325,6 +334,19 @@ int8_t get_button_state(bool left){
     return rightbutton;
 }
 
+bool spio_charger_state_get(){
+#ifdef ALWAYS_UPDATE_BUTTON
+    update_button_state();
+#endif
+    return charger_state;
+}
+
+bool spio_line_in_jacksense_get(){
+#ifdef ALWAYS_UPDATE_BUTTON
+    update_button_state();
+#endif
+    return line_in_jacksense;
+}
 
 void spio_menu_button_set_left(bool left){
     menu_button_left = 1;
diff --git a/components/badge23/synth.c b/components/badge23/synth.c
index 52ce89fb55..fe0d81ade6 100644
--- a/components/badge23/synth.c
+++ b/components/badge23/synth.c
@@ -2,17 +2,8 @@
 #include "badge23/audio.h"
 #include <math.h>
 
-float ks_osc(ks_osc_t * ks, float input){
-    //TODO: FIX THIS
-    ks->real_feedback = ks->feedback;
-
-    float delay_time = ((float) (SAMPLE_RATE))/ks->freq;
-    if(delay_time >= (KS_BUFFER_SIZE)) delay_time = (KS_BUFFER_SIZE) - 1;
-
-
-    //ks->tape[0] = input + real_feedback * ks->tape[delay_time];    
-    return ks->tape[0];
-}
+#define SYNTH_UNDERSAMPLING 3
+#define SYNTH_SAMPLE_RATE ((SAMPLE_RATE)/(SYNTH_UNDERSAMPLING))
 
 float waveshaper(uint8_t shape, float in);
 float nes_noise(uint16_t * reg, uint8_t mode, uint8_t run);
@@ -64,16 +55,19 @@ void run_trad_env(trad_osc_t * osc){
 }
 
 float run_trad_osc(trad_osc_t * osc){
+    static float ret = 0;
+    osc->undersampling_counter = (osc->undersampling_counter+1) % SYNTH_UNDERSAMPLING;
+    if(osc->undersampling_counter) return ret;
+
     run_trad_env(osc);
     if(!osc->env_phase) return 0;
-    float ret = 0;
 
     //run core sawtooth
     float freq = osc->freq * osc->bend;
     if(freq > 10000) freq = 10000;
     if(freq < -10000) freq = -10000;
     if(freq != freq) freq = 0;
-    osc->counter += 2. * freq / ((float)(SAMPLE_RATE));
+    osc->counter += 2. * freq / ((float)(SYNTH_SAMPLE_RATE));
     if(osc->counter != osc->counter){
         printf("trad_osc counter is NaN");
         abort();
diff --git a/usermodule/mp_audio.c b/usermodule/mp_audio.c
index 2634c1a831..145216b211 100644
--- a/usermodule/mp_audio.c
+++ b/usermodule/mp_audio.c
@@ -181,6 +181,76 @@ STATIC mp_obj_t mp_get_volume_relative() {
 STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_get_volume_relative_obj, mp_get_volume_relative);
 
 
+
+STATIC mp_obj_t mp_headphones_line_in_set_hardware_thru(mp_obj_t enable) {
+    audio_headphones_line_in_set_hardware_thru(mp_obj_get_int(enable));
+    return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_headphones_line_in_set_hardware_thru_obj, mp_headphones_line_in_set_hardware_thru);
+
+STATIC mp_obj_t mp_speaker_line_in_set_hardware_thru(mp_obj_t enable) {
+    audio_speaker_line_in_set_hardware_thru(mp_obj_get_int(enable));
+    return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_speaker_line_in_set_hardware_thru_obj, mp_speaker_line_in_set_hardware_thru);
+
+STATIC mp_obj_t mp_line_in_set_hardware_thru(mp_obj_t enable) {
+    audio_line_in_set_hardware_thru(mp_obj_get_int(enable));
+    return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_line_in_set_hardware_thru_obj, mp_line_in_set_hardware_thru);
+
+
+
+STATIC mp_obj_t mp_input_set_source(mp_obj_t enable) {
+    audio_input_set_source(mp_obj_get_int(enable));
+    return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_input_set_source_obj, mp_input_set_source);
+
+STATIC mp_obj_t mp_input_get_source(mp_obj_t enable) {
+    return mp_obj_new_int(audio_input_get_source());
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_input_get_source_obj, mp_input_get_source);
+
+
+
+STATIC mp_obj_t mp_headset_set_gain_dB(mp_obj_t gain_dB) {
+    audio_headset_set_gain_dB(mp_obj_get_int(gain_dB));
+    return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_headset_set_gain_dB_obj, mp_headset_set_gain_dB);
+
+STATIC mp_obj_t mp_headset_get_gain_dB(mp_obj_t enable) {
+    return mp_obj_new_int(audio_headset_get_gain_dB());
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_headset_get_gain_dB_obj, mp_headset_get_gain_dB);
+
+
+
+STATIC mp_obj_t mp_input_thru_set_volume_dB(mp_obj_t vol_dB) {
+    return mp_obj_new_float(audio_input_thru_set_volume_dB(mp_obj_get_float(vol_dB)));
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_input_thru_set_volume_dB_obj, mp_input_thru_set_volume_dB);
+
+STATIC mp_obj_t mp_input_thru_get_volume_dB() {
+    return mp_obj_new_float(audio_input_thru_get_volume_dB());
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_input_thru_get_volume_dB_obj, mp_input_thru_get_volume_dB);
+
+STATIC mp_obj_t mp_input_thru_set_mute(mp_obj_t mute) {
+    audio_input_thru_set_mute(mp_obj_get_int(mute));
+    return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_input_thru_set_mute_obj, mp_input_thru_set_mute);
+
+STATIC mp_obj_t mp_input_thru_get_mute() {
+    return mp_obj_new_int(audio_input_thru_get_mute());
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_input_thru_get_mute_obj, mp_input_thru_get_mute);
+
+
+
 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) },
@@ -220,6 +290,26 @@ STATIC const mp_rom_map_elem_t mp_module_audio_globals_table[] = {
     { 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) },
+
+    { MP_ROM_QSTR(MP_QSTR_headphones_line_in_set_hardware_thru), MP_ROM_PTR(&mp_headphones_line_in_set_hardware_thru_obj) },
+    { MP_ROM_QSTR(MP_QSTR_speaker_line_in_set_hardware_thru), MP_ROM_PTR(&mp_speaker_line_in_set_hardware_thru_obj) },
+    { MP_ROM_QSTR(MP_QSTR_line_in_set_hardware_thru), MP_ROM_PTR(&mp_line_in_set_hardware_thru_obj) },
+
+    { MP_ROM_QSTR(MP_QSTR_input_set_source), MP_ROM_PTR(&mp_input_set_source_obj) },
+    { MP_ROM_QSTR(MP_QSTR_input_get_source), MP_ROM_PTR(&mp_input_get_source_obj) },
+
+    { MP_ROM_QSTR(MP_QSTR_headset_set_gain_dB), MP_ROM_PTR(&mp_headset_set_gain_dB_obj) },
+    { MP_ROM_QSTR(MP_QSTR_headset_get_gain_dB), MP_ROM_PTR(&mp_headset_get_gain_dB_obj) },
+
+    { MP_ROM_QSTR(MP_QSTR_input_thru_set_volume_dB), MP_ROM_PTR(&mp_input_thru_set_volume_dB_obj) },
+    { MP_ROM_QSTR(MP_QSTR_input_thru_get_volume_dB), MP_ROM_PTR(&mp_input_thru_get_volume_dB_obj) },
+    { MP_ROM_QSTR(MP_QSTR_input_thru_set_mute), MP_ROM_PTR(&mp_input_thru_set_mute_obj) },
+    { MP_ROM_QSTR(MP_QSTR_input_thru_get_mute), MP_ROM_PTR(&mp_input_thru_get_mute_obj) },
+
+    { MP_ROM_QSTR(MP_QSTR_INPUT_SOURCE_NONE), MP_ROM_INT(AUDIO_INPUT_SOURCE_NONE) },
+    { MP_ROM_QSTR(MP_QSTR_INPUT_SOURCE_LINE_IN), MP_ROM_INT(AUDIO_INPUT_SOURCE_LINE_IN) },
+    { MP_ROM_QSTR(MP_QSTR_INPUT_SOURCE_HEADSET_MIC), MP_ROM_INT(AUDIO_INPUT_SOURCE_HEADSET_MIC) },
+    { MP_ROM_QSTR(MP_QSTR_INPUT_SOURCE_ONBOARD_MIC), MP_ROM_INT(AUDIO_INPUT_SOURCE_ONBOARD_MIC) },
 };
 
 STATIC MP_DEFINE_CONST_DICT(mp_module_audio_globals, mp_module_audio_globals_table);
-- 
GitLab