#include #include #include #include #include #include #include #include #include #include #include "codec.h" #include "wavestream.h" #define HAPTIC 1 #define AURAL 1 #define UI_SAMPLERATE 22050 #define BUFFERSIZE 64 #define NSTREAMS 8 int16_t buffer[BUFFERSIZE]; WaveStream stream[NSTREAMS]; bool streams_loaded = false; I2S i2s(INPUT_PULLUP); TLV320AIC3204 codec; TCA9555 TCA(0x20, &Wire1); AS5600 ENC(&Wire1); DAC8552 dac(9, &SPI1); PWMAudio ui_snd(8); enum BUTTON {CVINL, CVINR, INL, INR, OUTR, OUTL, CVOUTR, CVOUTL, RIGHT, LEFT, SELECT, DEBUG1, DEBUG2, DEBUG3}; int lut_ring_cw[48] = {39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,50,49,48,47,46,45,44,43,42,41,40}; int lut_ring_ccw[48] = {40,41,42,43,44,45,46,47,48,49,50,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39}; int lut_matrix[13] = {56,55,57,58,59,62,61,60,63,64,65,67,66}; int active = 0; int active_led_ring = 0; bool speakerToggle = false; uint32_t lastTime = 0; int32_t position = 0; CRGB ui_leds[74]; CRGB edge_leds[11]; volatile bool buttonChanged = false; volatile bool ui_click = false; volatile bool ui_beep = false; volatile bool amp = false; int counter = 0; bool buttons[16] = {false}; int16_t ui_click_snd[UI_SAMPLERATE]; uint16_t click_length = 0; int16_t ui_beep_snd[UI_SAMPLERATE]; uint16_t beep_length = 0; bool sd_card_detected = false; struct Color { int r; int g; int b; int r_active; int g_active; int b_active; }; struct Config { Color edge_color = {0,0,50,0,50,0}; Color ring_color = {0,50,50,0,80,50}; }; Config config; void loadConfiguration(Config& config) { File file = SD.open("/config.txt"); JsonDocument doc; DeserializationError error = deserializeJson(doc, file); if(error) Serial.println(F("Failed to read file, using default configuration")); config.edge_color.r = doc["edge"]["color"]["idle"]["r"] | 0; config.edge_color.g = doc["edge"]["color"]["idle"]["g"] | 0; config.edge_color.b = doc["edge"]["color"]["idle"]["b"] | 50; config.edge_color.r_active = doc["edge"]["color"]["active"]["r"] | 0; config.edge_color.g_active = doc["edge"]["color"]["active"]["g"] | 50; config.edge_color.b_active = doc["edge"]["color"]["active"]["b"] | 0; config.ring_color.r = doc["ring"]["color"]["idle"]["r"] | 0; config.ring_color.g = doc["ring"]["color"]["idle"]["g"] | 50; config.ring_color.b = doc["ring"]["color"]["idle"]["b"] | 50; config.ring_color.r_active = doc["ring"]["color"]["active"]["r"] | 0; config.ring_color.g_active = doc["ring"]["color"]["active"]["g"] | 80; config.ring_color.b_active = doc["ring"]["color"]["active"]["b"] | 50; //strlcpy(config.hostname, doc["hostname"] | "example.com", sizeof(config.hostname)); file.close(); } size_t count; size_t tape_write = 0; void codec_transmit() { for(int i = 0; i < count; i++){ int j = active % NSTREAMS; for(int k = 0; k < NSTREAMS; k++){ int16_t sample = 0; sample = stream[k].get(); buffer[i] += sample >> 2; } } i2s.write((const uint8_t *)&buffer, count * sizeof(int16_t)); } void codec_receive(){ count = i2s.read((uint8_t *)&buffer, BUFFERSIZE * sizeof(int16_t)) * sizeof(uint32_t) / sizeof(int16_t); for(int i = 0; i < count; i++){ buffer[i] *= -1; } } void pwm_audio_callback() { while (ui_snd.availableForWrite()) { if(ui_click || ui_beep) { if(ui_beep) { ui_snd.write(ui_beep_snd[counter]); counter++; if(counter == beep_length) { counter = 0; ui_beep = false; } continue; } if(ui_click) { ui_snd.write(ui_click_snd[counter]); counter++; if(counter == click_length) { counter = 0; ui_click = false; } } } else { digitalWrite(7, LOW); ui_snd.write(0); counter = 0; } } } bool load_ui_sounds(const char* file, int16_t *buffer, uint16_t &length){ if(SD.exists(file)) { File f_click = SD.open(file); f_click.seek(44, SeekSet); int click_bytes = 0; while (f_click.available() && click_bytes < UI_SAMPLERATE) { uint8_t samplebyte[2]; f_click.read(samplebyte, 2); int16_t sample = (samplebyte[1] << 8) + samplebyte[0]; buffer[click_bytes] = sample; click_bytes++; } f_click.close(); length = click_bytes; return true; } else { for(int i = 0; i < UI_SAMPLERATE; i++){ float sine_pos = (2.0f * M_PI * 3000.0f * (float)i) / (float)UI_SAMPLERATE; buffer[i] = (int16_t)(sin(sine_pos) * 8000.0f); } Serial.println("Synthesized 8kHz sine into beep array."); length = UI_SAMPLERATE; } return false; } void tca_irq() { buttonChanged = true; } void speaker(bool state){ digitalWrite(12, state ? HIGH : LOW); digitalWrite(13, state ? HIGH : LOW); } void setup() { Serial.begin(); delay(1000); i2s.setFrequency(48000); SPI1.setSCK(10); SPI1.setTX(11); SPI1.begin(); dac.begin(); FastLED.addLeds(edge_leds, 11); FastLED.addLeds(ui_leds, 74); pinMode(12, OUTPUT); pinMode(13, OUTPUT); speaker(false); pinMode(21, INPUT_PULLUP); sd_card_detected = !digitalRead(21); delay(500); bool sdInitialized = SD.begin(22, 23, 24); delay(100); if(!sdInitialized) sdInitialized = SD.begin(22, 23, 24); // hack to prevent SD card from not initializing after soft reset if(sdInitialized) loadConfiguration(config); if(sdInitialized){ load_ui_sounds("/ui/click.wav", ui_click_snd, click_length); load_ui_sounds("/ui/beep.wav", ui_beep_snd, beep_length); } Serial.println("INIT WIRE"); Wire1.setSDA(2); Wire1.setSCL(3); Wire1.begin(); Serial.println("SUCCESS"); Serial.println("INIT TCA"); TCA.begin(); TCA.pinMode16(0xFFFF); TCA.setPolarity16(0x0000); Serial.println("SUCCESS"); Serial.println("INIT INTERRUPT"); pinMode(1, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(1), tca_irq, FALLING); Serial.println("SUCCESS"); // Rotary Encoder ENC.begin(); // set direction pin. ENC.setDirection(AS5600_COUNTERCLOCK_WISE); pinMode(6, OUTPUT); // Vibration Motor pinMode(7, OUTPUT); // UI Amp Enable ui_snd.onTransmit(pwm_audio_callback); ui_snd.begin(UI_SAMPLERATE); digitalWrite(7, LOW); // UI amp off pinMode(19, OUTPUT); // MCLK enable digitalWrite(19, HIGH); // enable MCLK pinMode(20, OUTPUT); // CODEC reset digitalWrite(20, HIGH); codec.begin(&Wire1); i2s.onTransmit(codec_transmit); i2s.onReceive(codec_receive); i2s.setDOUT(15); i2s.setDIN(14); i2s.setBCLK(16); // Note: LRCLK = BCLK + 1 i2s.setMCLK(18); i2s.setMCLKmult(512); // 256 = 12.288.000Hz 512 = 25Mhz i2s.swapClocks(); i2s.setBitsPerSample(16); i2s.setBuffers(6, BUFFERSIZE * sizeof(int16_t) / sizeof(uint32_t)); if(!i2s.begin(48000)){ Serial.println("I2S error!"); while(100); } if(sdInitialized) { for(int i = 0; i < NSTREAMS; i++){ stream[i].begin(); char filename[40]; sprintf(filename, "/sound/%d.wav", i+1); if(SD.exists(filename)){ bool loaded = stream[i].load(SD.open(filename)); if(loaded) { Serial.print("file read: "); Serial.print(stream[i].wavefile.length); Serial.print(" bytes | "); Serial.print(stream[i].wavefile.samplerate); Serial.print(" kHz | "); Serial.print(stream[i].wavefile.bitspersample); Serial.print(" bits | "); Serial.print(stream[i].wavefile.channels); Serial.print(" channels | "); Serial.print(stream[i].wavefile.blockalign); Serial.println(" bytes"); //stream[i].play(); stream[i].wavefile.loop = true; } else { Serial.println("file loading error"); } } else { for(int k = 0; k < 3; k++){ digitalWrite(6, HIGH); delay(100); digitalWrite(6, LOW); delay(50); } } } streams_loaded = true; } digitalWrite(6, HIGH); delay(25); digitalWrite(6, LOW); delay(50); digitalWrite(6, HIGH); delay(25); digitalWrite(6, LOW); } bool dactest = false; void loop() { position = ENC.getCumulativePosition(); sd_card_detected = !digitalRead(21); if(sd_card_detected) edge_leds[8] = CRGB(0,10,0); if(!sd_card_detected) edge_leds[8] = CRGB(10,0,0); // EDGE LEDs for (int i = 0; i < 8; i++) { edge_leds[i] = CRGB(config.edge_color.r, config.edge_color.g, config.edge_color.b); } // LED Ring leeren for (int i = 0; i < 48; i++) { ui_leds[i + 3] = CRGB(0, 0, 0); } // Rotary button LEDs ui_leds[0] = CRGB(0, 0, 0); ui_leds[1] = CRGB(0, 0, 0); ui_leds[2] = CRGB(0, 0, 0); // buttonChanged = true when a button is pushed if (buttonChanged) { int buttonValues = TCA.read16(); bool buttonsNew[16] = {false}; int buttonsDir[16] = {0}; bool buttonUp = false; bool buttonDown = false; for(int i = 0; i < 16; i++){ buttonsNew[i] = ~(buttonValues >> i) & 0x01; if(buttonsNew[i] == true && buttons[i] == false) { buttonsDir[i] = 1; buttonDown = true; } if(buttonsNew[i] == false && buttons[i] == true) { buttonsDir[i] = -1; buttonUp = true; } buttons[i] = buttonsNew[i]; } // Make vibration if (HAPTIC && buttonDown) { digitalWrite(6, HIGH); delay(50); digitalWrite(6, LOW); } // Make beep if (AURAL && buttonDown) { digitalWrite(7, HIGH); ui_click = true; } // Flash encoder leds ui_leds[0] = CRGB(0, 100, 50); ui_leds[1] = CRGB(0, 100, 50); ui_leds[2] = CRGB(0, 100, 50); // Switch through LED matrix if(buttons[RIGHT]) active++; if(buttons[LEFT]) active--; if(active == 13) active = 0; if(active == -1) active = 12; if(buttons[CVINL]) { Serial.println("vol down"); codec.volumeDown(); } if(buttons[CVOUTL]) { Serial.println("vol up"); codec.volumeUp(); } if(buttons[DEBUG3]) { speakerToggle = !speakerToggle; speaker(speakerToggle); } if(buttons[DEBUG2]) { dactest = !dactest; dac.setValue(0, dactest ? 0 : 32768); dac.setValue(1, !dactest ? 0 : 65535); } if(buttons[SELECT]){ stream[active % NSTREAMS].toggle(); ui_beep = true; } Serial.println(codec.getVolumeL()); //for(int i = 0; i < NSTREAMS; i++){ // stream[i].pause(); // } //stream[active % NSTREAMS].play(); buttonChanged = false; } //dac.setValue(0, dactest ? 0 : sin((float)millis() / 100.0f) * 32768 + 32768); // empty LED matrix for (int i = 0; i < 13; i++) { ui_leds[lut_matrix[i]] = CRGB(0, 0, 0); if(stream[i % NSTREAMS].isPlaying()) ui_leds[lut_matrix[i % NSTREAMS]] = CRGB(0, 50, 0); } // set active LED matrix LED ui_leds[lut_matrix[active]] = CRGB(100, 100, 100); if(position < 0) position += 4096; active_led_ring = (position / 32) % 48; // set active LED ring LED for(int i = 0; i < active_led_ring; i++){ ui_leds[lut_ring_ccw[i]] = CRGB(config.ring_color.r, config.ring_color.g, config.ring_color.b); } FastLED.show(); if(streams_loaded) { for(int i = 0; i < NSTREAMS; i++){ stream[i].stream(); } } if(i2s.getOverflow()) Serial.println("overflow"); if(i2s.getUnderflow()) Serial.println("underflow"); //delay(20); // wait 1ms }