├── .gitattributes ├── examples ├── I2CBusScan │ └── I2CBusScan.ino ├── MP3_NetRadio │ ├── MP3_NetRadio.ino │ ├── graphics.cpp │ ├── graphics.h │ ├── messaging.cpp │ ├── messaging.h │ └── metadata.h ├── MP3_SDCard │ └── MP3_SDCard.ino ├── PCF8574_Test │ └── PCF8574_Test.ino ├── SD_Test │ └── SD_Test.ino ├── Sound_Plotter │ └── Sound_Plotter.ino ├── Sound_Recorder │ ├── Sound_Recorder.ino │ ├── Wav.cpp │ └── Wav.h ├── Vocoder │ └── Vocoder.ino └── Waveform │ └── Waveform.ino ├── keywords.txt ├── library.properties └── src ├── Audio.cpp ├── Audio.h ├── GPIOExpander.cpp ├── GPIOExpander.h ├── LinkedList.h ├── PicoAudio.cpp ├── PicoAudio.h ├── aac_decoder.cpp ├── aac_decoder.h ├── ak74_logo.h ├── arduinoFFT.cpp ├── arduinoFFT.h ├── defs.h ├── lcd.cpp ├── lcd.h ├── mp3_decoder.cpp ├── mp3_decoder.h └── types.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /examples/I2CBusScan/I2CBusScan.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void setup() 5 | { 6 | Wire.begin(32, 33); 7 | 8 | Serial.begin(9600); 9 | while (!Serial); // Leonardo: wait for serial monitor 10 | Serial.println("\nI2C Scanner"); 11 | } 12 | 13 | void loop() 14 | { 15 | byte error, address; 16 | int nDevices; 17 | 18 | Serial.begin(115200); 19 | Serial.println("Scanning..."); 20 | 21 | nDevices = 0; 22 | for(address = 1; address < 127; address++ ) 23 | { 24 | // The i2c_scanner uses the return value of 25 | // the Write.endTransmisstion to see if 26 | // a device did acknowledge to the address. 27 | Wire.beginTransmission(address); 28 | error = Wire.endTransmission(); 29 | 30 | if (error == 0) 31 | { 32 | Serial.print("I2C device found at address 0x"); 33 | if (address<16) 34 | Serial.print("0"); 35 | Serial.print(address,HEX); 36 | Serial.println(" !"); 37 | 38 | nDevices++; 39 | } 40 | else if (error==4) 41 | { 42 | Serial.print("Unknown error at address 0x"); 43 | if (address<16) 44 | Serial.print("0"); 45 | Serial.println(address,HEX); 46 | } 47 | } 48 | if (nDevices == 0) 49 | Serial.println("No I2C devices found\n"); 50 | else 51 | Serial.println("done\n"); 52 | 53 | delay(5000); // wait 5 seconds for next scan 54 | } -------------------------------------------------------------------------------- /examples/MP3_NetRadio/MP3_NetRadio.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 AKsevenFour / Adam Keher (adam@keher.com.au) 3 | http://www.aksevenfour.com / https://github.com/AdamKeher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 7 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 13 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 14 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 15 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include "TinyPICO.h" 22 | #include "PicoAudio.h" 23 | #include "messaging.h" 24 | #include "lcd.h" 25 | #include "metadata.h" 26 | #include "graphics.h" 27 | #include 28 | 29 | #define LCD_ADDR 0x27 // I2C Address 30 | #define LCD_COL 16 31 | #define LCD_ROW 2 32 | 33 | // Enter your WiFi setup here: 34 | const char *SSID = "GoAway"; 35 | const char *PASSWORD = "w00tw00tw00t"; 36 | 37 | int urlPos = 0; 38 | const char *URLS[] = { 39 | "http://live-radio01.mediahubaustralia.com/2TJW/mp3/", 40 | "http://dg-ais-eco-http-fra-eco-cdn.cast.addradio.de/hellwegradio/west/mp3/low", 41 | "http://jazz.streamr.ru/jazz-64.mp3" }; 42 | 43 | TinyPICO tinyPICO = TinyPICO(); 44 | PicoAudio picoAudio = PicoAudio(); 45 | 46 | LCD *lcd; 47 | 48 | TaskHandle_t messageProcessorTask; 49 | Messaging messaging = Messaging(); 50 | struct message *newMessage = NULL; 51 | 52 | StreamMetadata streamMetadata; 53 | double buckets[256]; 54 | double peak; 55 | int16_t rVu; 56 | int16_t lVu; 57 | 58 | void GPIOChangeCallback(void *cbData, uint8_t ports, uint8_t button, bool state) { 59 | // Note: XOR ports to flip the bits for menu button (active low) 60 | Serial.printf("BUTTONS: Function %02d | %s\r\n", button, state ? "DOWN" : "UP" ); 61 | char buffer[64]; 62 | 63 | switch (button) { 64 | case 3: 65 | if (state) { 66 | Serial.printf("BUTTONS: Pause | %s\r\n", picoAudio.isPlaying() ? "Resume" : "Pause"); 67 | picoAudio.pauseResume(); 68 | sprintf(buffer, "%s", picoAudio.isPlaying() ? "Resume" : "Pause"); 69 | messaging.display(0, 2500, true, buffer); 70 | messaging.sendMessage(messaging.messageQueue, updateMetadata, &streamMetadata); 71 | } 72 | break; 73 | case 4: 74 | if (state) { 75 | Serial.printf("BUTTONS: Prev Track |\r\n"); 76 | messaging.sendMessage(messaging.mainQueue, prevTrack, NULL); 77 | messaging.display(0, 2500, true, "<< Prev Track"); 78 | } 79 | break; 80 | case 5: 81 | if (state) { 82 | Serial.printf("BUTTONS: Next Track |\r\n"); 83 | messaging.sendMessage(messaging.mainQueue, nextTrack); 84 | messaging.display(0, 2500, true, "Next Track >>"); 85 | } 86 | break; 87 | case 6: 88 | if (state) { 89 | picoAudio.volumeDown(); 90 | Serial.printf("BUTTONS: Volume Down | volume %d/10\r\n", picoAudio.getVolume() + 1); 91 | sprintf(buffer, "Volume: %d/10", picoAudio.getVolume() + 1); 92 | messaging.display(0, 2500, true, buffer); 93 | messaging.sendMessage(messaging.messageQueue, updateMetadata, &streamMetadata); 94 | } 95 | break; 96 | case 7: 97 | if (state) { 98 | picoAudio.volumeUp(); 99 | Serial.printf("BUTTONS: Volume Up | volume %d/10\r\n", picoAudio.getVolume() + 1); 100 | sprintf(buffer, "Volume: %d/10", picoAudio.getVolume() + 1); 101 | messaging.display(0, 2500, true, buffer); 102 | messaging.sendMessage(messaging.messageQueue, updateMetadata, &streamMetadata); 103 | } 104 | break; 105 | default: break; 106 | } 107 | } 108 | 109 | void FFTCallback(void *cbData, int16_t _lVu, int16_t _rVu, double _peak, double *values) { 110 | // for (int i= 0; i < out->getSampleRate() / 2; i++) { 111 | // buckets[i] = values[i]; 112 | // } 113 | // peak = _peak; 114 | // Serial.printf("DEBUG::VU::%d:%d\r\n", _lVu >> 8, _rVu >> 8); 115 | lVu = _lVu; 116 | rVu = _rVu; 117 | } 118 | 119 | void MetadataCallback(void *cbData, StreamMetadata *metadata) { 120 | lcd->print(0, 0, true, metadata->siteName); 121 | lcd->print(0, 1, true, metadata->streamTitle); 122 | Serial.printf("DEBUG::MSGMDCB: metadata: '%s'\r\n", metadata->streamTitle); 123 | } 124 | 125 | void DisplayCallback(void *cbData, int line, char *text) { 126 | Serial.printf("DEBUG::DISPLAYCALLBACK - '%s'\r\n", text); 127 | lcd->print(0, line, true, text); 128 | } 129 | 130 | void setupWiFi() { 131 | Serial.printf("MAC Address: %s\r\n", WiFi.macAddress().c_str()); 132 | messaging.display(0, 0, false, "WiFi: "); 133 | messaging.display(1, 0, false, ""); 134 | WiFi.disconnect(); 135 | WiFi.softAPdisconnect(true); 136 | WiFi.mode(WIFI_STA); 137 | WiFi.begin(SSID, PASSWORD); 138 | while (WiFi.status() != WL_CONNECTED) { 139 | delay(1000); 140 | } 141 | long rssi = WiFi.RSSI(); 142 | messaging.display(0, 0, false, "WiFi: Connected"); 143 | char buffer[64]; 144 | sprintf(buffer, "RSSI: %d dB", rssi); 145 | messaging.display(1, 2000, false, buffer); 146 | Serial.printf("MAC Address: %s\r\n", WiFi.macAddress().c_str()); 147 | } 148 | 149 | void setup() { 150 | Serial.begin(115200); 151 | 152 | picoAudio.begin(); 153 | picoAudio.mute(); 154 | 155 | messaging.RegisterMetadataCB(MetadataCallback, (void*)"metadata"); 156 | messaging.RegisterDisplayCB(DisplayCallback, (void *)"display"); 157 | 158 | 159 | // tinyPICO led yellow 160 | tinyPICO.DotStar_SetBrightness(255); 161 | tinyPICO.DotStar_SetPixelColor(0, 255, 255); 162 | 163 | // create task to process messages, LCD and TFT 164 | xTaskCreatePinnedToCore( 165 | messageProcessor, /* Task function. */ 166 | "messageProcessorTask", /* name of task. */ 167 | 10000, /* Stack size of task */ 168 | NULL, /* parameter of the task */ 169 | 1, /* priority of the task */ 170 | &messageProcessorTask, /* Task handle to keep track of created task */ 171 | 0); /* pin task to core 0 */ 172 | 173 | // connect to wifi and display RSSI 174 | setupWiFi(); 175 | 176 | // tinyPICO led green 177 | tinyPICO.DotStar_SetPixelColor(0, 255, 0); 178 | tinyPICO.DotStar_Show(); 179 | 180 | // connect to stream 181 | picoAudio.connecttoHost(URLS[0]); 182 | // picoAudio.RegisterFFTCB(FFTCallback, NULL); 183 | 184 | messaging.display(0, 0, false, "Pico Audio"); 185 | 186 | picoAudio.setVolumeMax(); 187 | } 188 | 189 | void loop() { 190 | picoAudio.process(true, false); 191 | 192 | if (uxQueueMessagesWaiting(messaging.mainQueue) > 0) 193 | { 194 | xQueueReceive(messaging.mainQueue, &newMessage, 0); 195 | Serial.printf("MESSAGE: Received (Core %d) | ID %04d | function %d\r\n", xPortGetCoreID(), newMessage->messageId, newMessage->function); 196 | if (newMessage->function == nextTrack) { 197 | // clear metadata 198 | strcpy(streamMetadata.streamTitle, ""); 199 | strcpy(streamMetadata.siteName, ""); 200 | strcpy(streamMetadata.genre, ""); 201 | strcpy(streamMetadata.bitRate, ""); 202 | 203 | urlPos++; 204 | if (urlPos >= sizeof(URLS) / sizeof(URLS[0])) 205 | urlPos = 0; 206 | picoAudio.connecttoHost(URLS[urlPos]); 207 | Serial.printf("BUTTONS: Next URL | urlPos (%d) | Url %s\r\n", urlPos, URLS[urlPos]); 208 | } else 209 | if (newMessage->function == prevTrack) { 210 | // clear metadata 211 | strcpy(streamMetadata.streamTitle, ""); 212 | strcpy(streamMetadata.siteName, ""); 213 | strcpy(streamMetadata.genre, ""); 214 | strcpy(streamMetadata.bitRate, ""); 215 | 216 | urlPos--; 217 | if (urlPos < 0) 218 | urlPos = (sizeof(URLS)/sizeof(URLS[0]))-1; 219 | picoAudio.connecttoHost(URLS[urlPos]); 220 | Serial.printf("BUTTONS: Prev URL | urlPos (%d) | Url %s\r\n", urlPos, URLS[urlPos]); 221 | } 222 | 223 | vPortFree(newMessage); 224 | } 225 | } 226 | 227 | void messageProcessor(void * pvParameters) { 228 | // GPIO Extender / Buttons 229 | picoAudio.RegisterButtonCB(GPIOChangeCallback, (void*)"messageProcessor"); 230 | 231 | // 16x2 LCD Screen 232 | lcd = new LCD(LCD_ADDR, LCD_COL, LCD_ROW, PA_SDA, PA_SCL); 233 | 234 | // 320x240 ILI9341 Screen 235 | Graphics graphics = Graphics(); 236 | 237 | for (;;) 238 | { 239 | // draw checkerboard 240 | graphics.drawCheckerBoard(lVu); 241 | 242 | // draw logo 243 | graphics.drawLogo(); 244 | 245 | // draw VU meter 246 | //TODO: Clean up / simplify variables passed in 247 | int vuBarWidth = 20; 248 | int vuBorder = 5; 249 | graphics.drawVu(graphics.getBuffer().width() - vuBarWidth - vuBorder, vuBarWidth, lVu); 250 | graphics.drawVu(graphics.getBuffer().width() - (vuBarWidth * 2) - (vuBorder * 2), vuBarWidth, rVu); 251 | 252 | // draw FFT equaliser 253 | // graphics.drawFFT(out->getSampleRate(), buckets); 254 | 255 | // draw Metadata 256 | graphics.drawMetadata(streamMetadata, TFT_RED); 257 | 258 | // draw FPS 259 | graphics.getBuffer().drawString("FPS: " + String(graphics.getFPS()), 5, 0); 260 | 261 | // send screen buffer to TFT 262 | graphics.drawFrame(); 263 | 264 | messaging.loop(); 265 | 266 | // scroll long metadata 267 | lcd->loop(); 268 | 269 | // Process GPIO extender buttons 270 | picoAudio.process(false, true); 271 | 272 | // vTaskDelay(1000); 273 | } 274 | } 275 | 276 | // Audio metadata 277 | 278 | void audio_showstation(const char *info){ 279 | Serial.printf("AUDMETA: Station | \033[32m%s\033[37m\r\n", info); 280 | strcpy(streamMetadata.siteName, info); 281 | messaging.sendMessage(messaging.messageQueue, updateMetadata, &streamMetadata); 282 | } 283 | void audio_showstreaminfo(const char *info){ 284 | Serial.printf("AUDMETA: Streaminfo | \033[32m%s\033[37m\r\n", info); 285 | } 286 | void audio_showstreamtitle(const char *info){ 287 | Serial.printf("AUDMETA: Streamtitle | \033[32m%s\033[37m\r\n", info); 288 | strcpy(streamMetadata.streamTitle, info); 289 | messaging.sendMessage(messaging.messageQueue, updateMetadata, &streamMetadata); 290 | } -------------------------------------------------------------------------------- /examples/MP3_NetRadio/graphics.cpp: -------------------------------------------------------------------------------- 1 | #include "graphics.h" 2 | #include 3 | 4 | Graphics::Graphics() { 5 | tft.init(); 6 | tft.setRotation(1); 7 | 8 | buffer.setColorDepth(8); 9 | buffer.createSprite(tft.width(), tft.height()); 10 | buffer.setRotation(1); 11 | buffer.setTextFont(2); 12 | buffer.setTextSize(2); 13 | buffer.setTextColor(TFT_RED); 14 | buffer.fillScreen(TFT_BLUE); 15 | } 16 | 17 | void Graphics::drawMetadata(StreamMetadata metadata, uint32_t color) { 18 | // NOTE: append a space to streamTitle as tft.drawString clips last letter when it partially draws out of right bounds 19 | char title[65]; 20 | strcpy(title, metadata.streamTitle); 21 | strcat(title, " "); 22 | int16_t correction = buffer.textWidth(title) - buffer.width(); 23 | // Serial.printf("correction: %d | title: '%s' | titlewidth: %d | bufwidth: %d\r\n", correction, title, buffer.textWidth(title), buffer.width() ); 24 | if (correction < 0) { 25 | pos = 0; 26 | } else { 27 | 28 | if (forward) { 29 | pos = pos + 1; 30 | if (pos > 0) 31 | forward = false; 32 | } 33 | else { 34 | pos = pos - 1; 35 | if (pos < 0 - correction) 36 | forward = true; 37 | } 38 | } 39 | 40 | buffer.setTextColor(color); 41 | buffer.drawString(title, pos + 5, 40); 42 | buffer.drawString(metadata.siteName, 5, 70); 43 | buffer.drawString(metadata.genre, 5, 100); 44 | buffer.drawString(metadata.bitRate, 5, 130); 45 | } 46 | 47 | void Graphics::drawVu(int x, int width, int16_t vuLevel) { 48 | // vuLevel -32k..0..32k 49 | uint8_t vu = (vuLevel & 0x7FFF) >> 8; 50 | 51 | int vuBorder = 25; 52 | int vuBarHeight = 20; 53 | 54 | for (int i = 0; i < 10; i++) { 55 | uint32_t color = TFT_DARKGREY; 56 | 57 | if (vu * 2 > i * 25) 58 | switch (i) 59 | { 60 | case 0: 61 | case 1: 62 | color = TFT_GREEN; 63 | break; 64 | case 2: 65 | case 3: 66 | case 4: 67 | case 5: 68 | case 6: 69 | case 7: 70 | color = TFT_YELLOW; 71 | break; 72 | case 8: 73 | color = TFT_ORANGE; 74 | break; 75 | case 9: 76 | color = TFT_RED; 77 | break; 78 | default: 79 | color = TFT_DARKGREY; 80 | break; 81 | } 82 | 83 | buffer.fillRect(x, buffer.height() - (i * vuBarHeight) - vuBarHeight - vuBorder, width, vuBarHeight-1, color); 84 | } 85 | } 86 | 87 | void Graphics::drawLogo() { 88 | // TODO: Clean up calcs & convert to sprite for transparency 89 | uint16_t x = (sin(counter) * ((320 - LOGOWIDTH) / 2)) + (160 - (LOGOWIDTH/2)); 90 | uint16_t y = (sin(counter/2) * ((240 - LOGOHEIGHT) / 2)) + (120 - (LOGOHEIGHT/2)); 91 | buffer.pushImage(x, y, LOGOWIDTH, LOGOHEIGHT, logo); 92 | counter = counter + 0.1f; 93 | } 94 | 95 | void Graphics::drawCheckerBoard(uint16_t vu) { 96 | 97 | long color = tft.color565(0, 0, vu); 98 | for (int y = 0; y < 6; y++) { 99 | for (int x = 0; x < 9; x++) 100 | { 101 | if (box % 2 == 0) 102 | buffer.fillRect((x * 40) + box_offset, y * 40, 40, 40, color); 103 | else 104 | buffer.fillRect((x * 40) + box_offset, y * 40, 40, 40, TFT_WHITE); 105 | box++; 106 | } 107 | } 108 | box_offset = box_offset + 2; 109 | 110 | if (box_offset == 0) { 111 | box_offset = -40; 112 | box++; 113 | } 114 | } 115 | 116 | void Graphics::drawFFT(uint16_t sampleRate, double *buckets) { 117 | int barWidth = buffer.width() / (sampleRate / 2); 118 | int tftHeight = buffer.height(); 119 | 120 | for (int i = 0; i < sampleRate / 2; i++) 121 | { 122 | int height = buckets[i] / 1024; 123 | buffer.fillRect(i * barWidth, tftHeight - height, barWidth, height, TFT_GREEN); 124 | } 125 | } 126 | 127 | void Graphics::drawDebug(int16_t bufferSize, uint16_t fillLevel) { 128 | // draw buffer fillrate 129 | int bfrWidth = buffer.width() - 60; 130 | int bfrBorder = 5; 131 | int bfrY = 160; 132 | int bfrHeight = 30; 133 | double bsize = bufferSize; 134 | buffer.drawRect(bfrBorder, bfrY, bfrWidth, bfrHeight, TFT_RED); 135 | buffer.fillRect(bfrBorder + 1, bfrY + 1, fillLevel == 0 ? 0 : bfrWidth * (fillLevel / bsize) - 2, bfrHeight - 2, TFT_ORANGE); 136 | buffer.drawString("BUFFER", 10, 160); 137 | 138 | // draw wireless strength 139 | bfrWidth = buffer.width() - 60; 140 | double rssi = abs(WiFi.RSSI()); 141 | double fill = 0.0f; 142 | if (rssi <= 35) 143 | fill = 100.0f; 144 | else if (rssi > 35 && rssi <= 70) 145 | fill = (70-(rssi-35))/70; 146 | else if (rssi > 70) 147 | fill = 0.0f; 148 | buffer.drawRect(bfrBorder, bfrY + 35, bfrWidth, bfrHeight, TFT_RED); 149 | buffer.fillRect(bfrBorder + 1, bfrY + 35 + 1, (bfrWidth * fill) - 2, bfrHeight - 2, TFT_ORANGE); 150 | buffer.drawString(String(WiFi.RSSI()) + "db", 10, 195); 151 | } 152 | 153 | void Graphics::drawFrame() { 154 | buffer.pushSprite(0, 0); 155 | 156 | frameCount++; 157 | if (millis() - lastFPS > 1000) { 158 | lastFPS = millis(); 159 | FPS = frameCount; 160 | frameCount = 0; 161 | } 162 | } 163 | 164 | // void drawAlpha(uint16_t _x, uint16_t _y, uint16_t _w, uint16_t _h, const uint16_t *data, TFT_eSprite *backBuffer) { 165 | // // uint16_t tx = _x; 166 | // // _x = _y; 167 | // // _y = _dheight - tx - 1; 168 | 169 | // for (int h = 0; h < _h; h++) { 170 | // for (int w = 0; w < _w; w++) { 171 | // backBuffer->drawPixel(_x + h, _y + w, logo[(h * w)+ w]); 172 | // } 173 | // } 174 | // } -------------------------------------------------------------------------------- /examples/MP3_NetRadio/graphics.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 AKsevenFour / Adam Keher (adam@keher.com.au) 3 | http://www.aksevenfour.com / https://github.com/AdamKeher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 7 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 13 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 14 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 15 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | #ifndef GRAPHICS_H 18 | #define GRAPHICS_H 19 | 20 | // TFT 21 | // #define TFT_DC 27 22 | // #define TFT_CS 21 23 | // #define TFT_MOSI 23 24 | // #define TFT_CLK 18 25 | // #define TFT_RST -1 // tie RST to 3.3v 26 | // #define TFT_MISO 19 27 | 28 | #include 29 | #include "metadata.h" 30 | #include "ak74_logo.h" 31 | 32 | class Graphics 33 | { 34 | public: 35 | Graphics(); 36 | 37 | void drawMetadata(StreamMetadata metadata, uint32_t color); 38 | void drawVu(int x, int width, int16_t vuLevel); 39 | void drawLogo(); 40 | void drawCheckerBoard(uint16_t vu); 41 | void drawFFT(uint16_t sampleRate, double *buckets); 42 | void drawDebug(int16_t bufferSize, uint16_t fillLevel); 43 | 44 | void drawFrame(); 45 | 46 | TFT_eSPI getTFT() { return tft; } 47 | TFT_eSprite getBuffer() { return buffer; } 48 | int getFPS() { return FPS; } 49 | 50 | private: 51 | TFT_eSPI tft = TFT_eSPI(); 52 | TFT_eSprite buffer = TFT_eSprite(&tft); 53 | uint16_t box; 54 | int box_offset = -40; 55 | double counter = 0; 56 | int frameCount = 0; 57 | int FPS = 0; 58 | uint32_t lastFPS = 0; 59 | int32_t pos = 0; 60 | bool forward = false; 61 | }; 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /examples/MP3_NetRadio/messaging.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 AKsevenFour / Adam Keher (adam@keher.com.au) 3 | http://www.aksevenfour.com / https://github.com/AdamKeher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 7 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 13 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 14 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 15 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | #include "messaging.h" 19 | 20 | Messaging::Messaging() { 21 | messageQueue = xQueueCreate( QUEUESIZE, sizeof( struct message * ) ); 22 | if(messageQueue == NULL){ 23 | Serial.println("Error creating the messageQueue"); 24 | } 25 | 26 | mainQueue = xQueueCreate( QUEUESIZE, sizeof( struct message * ) ); 27 | if(mainQueue == NULL){ 28 | Serial.println("Error creating the mainQueue"); 29 | } 30 | } 31 | 32 | void Messaging::sendMessage(QueueHandle_t queue, metaFunction function, void* object) { 33 | struct message *newMessage; 34 | struct message *tempMessage; 35 | 36 | 37 | if (function == displayMessage && ((DisplayMessage *)object)->immediate) { 38 | // delete existing queue items 39 | while (xQueueReceive(messageQueue, &tempMessage, 0)) 40 | { 41 | vPortFree(tempMessage); 42 | } 43 | messageDelay = 0; 44 | } 45 | 46 | newMessage = (struct message*) pvPortMalloc(sizeof(struct message)); 47 | newMessage->messageId = messageIdCount++; 48 | newMessage->function = function; 49 | newMessage->object = object; 50 | Serial.printf("MESSAGE: Send (Core %d) | ID %04d | function %d\r\n", xPortGetCoreID(), newMessage->messageId, newMessage->function); 51 | 52 | xQueueSend(queue, &newMessage, 0); 53 | } 54 | 55 | void Messaging::display(int line, int time, bool immediate, char* messageText) { 56 | // TODO: Free this object! 57 | DisplayMessage *dmsg = (struct DisplayMessage*) pvPortMalloc(sizeof(struct DisplayMessage)); 58 | dmsg->line = line; 59 | dmsg->time = time; 60 | dmsg->immediate = immediate; 61 | strcpy(dmsg->messageText, messageText); 62 | sendMessage(messageQueue, displayMessage, dmsg); 63 | } 64 | 65 | void Messaging::loop() { 66 | // Process Queue 67 | if (uxQueueMessagesWaiting(messageQueue) > 0) 68 | { 69 | // if delay expired then process message 70 | if (millis() - lastMessage > messageDelay) 71 | { 72 | xQueueReceive(messageQueue, &newMessage, 0); 73 | Serial.printf("MESSAGE: Received (Core %d) | ID %04d | function %d\r\n", xPortGetCoreID(), newMessage->messageId, newMessage->function); 74 | messageDelay = 0; 75 | } 76 | } 77 | 78 | // Handle message 79 | if (newMessage) 80 | { 81 | Serial.printf("MESSAGE: Handle (Core %d) | ID %04d | function %d\r\n", xPortGetCoreID(), newMessage->messageId, newMessage->function); 82 | if (newMessage->function == updateMetadata) { 83 | if (newMessage->object != NULL) 84 | cb.metadata((StreamMetadata*)newMessage->object); 85 | Serial.printf("MESSAGE: UpdMeta (Core %d) | ID %04d | function %d\r\n", xPortGetCoreID(), newMessage->messageId, newMessage->function); 86 | } 87 | else if (newMessage->function == displayMessage) { 88 | // lcd.print(0, newMessage->line, true, newMessage->messageText); 89 | DisplayMessage *displayMsg = (DisplayMessage *)newMessage->object; 90 | cb.dispdata(displayMsg->line, displayMsg->messageText); 91 | lastMessage = millis(); 92 | messageDelay = displayMsg->time; 93 | Serial.printf("MESSAGE: disMsge (Core %d) | ID %04d | function %d\r\n", xPortGetCoreID(), newMessage->messageId, newMessage->function); 94 | 95 | vPortFree(newMessage->object); 96 | newMessage->object = NULL; 97 | } 98 | else 99 | { 100 | Serial.printf("MESSAGE: Unhandled (Core: %d) | ID %04d | function %d\r\n", xPortGetCoreID(), newMessage->messageId, newMessage->function); 101 | } 102 | 103 | vPortFree(newMessage); 104 | newMessage = NULL; 105 | } 106 | } -------------------------------------------------------------------------------- /examples/MP3_NetRadio/messaging.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 AKsevenFour / Adam Keher (adam@keher.com.au) 3 | http://www.aksevenfour.com / https://github.com/AdamKeher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 7 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 13 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 14 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 15 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | #ifndef MESSAGING_H 18 | #define MESSAGING_H 19 | 20 | #include 21 | #include "metadata.h" 22 | 23 | #define QUEUESIZE 10 24 | 25 | enum metaFunction 26 | { 27 | updateMetadata, 28 | volumeUp, 29 | volumeDown, 30 | displayMessage, 31 | nextTrack, 32 | prevTrack 33 | }; 34 | 35 | struct DisplayMessage { 36 | int line; 37 | int time; 38 | bool immediate = false; 39 | char messageText[64]; 40 | }; 41 | 42 | struct message { 43 | uint32_t messageId; 44 | metaFunction function; 45 | void *object; 46 | }; 47 | 48 | class MessagingEvents 49 | { 50 | public: 51 | MessagingEvents() { ClearCBs(); } 52 | void ClearCBs() { metaFn = NULL; }; 53 | 54 | typedef void (*metaCBFn)(void *cbData, StreamMetadata *metadata); 55 | bool RegisterMetadataCB(metaCBFn f, void *cbData) { metaFn = f; metaData = cbData; return true; } 56 | inline void metadata(StreamMetadata *metadata) { if (metaFn) metaFn(metaData, metadata); } 57 | 58 | typedef void (*dispCBFn)(void *cbData, int line, char *text); 59 | bool RegisterDisplayCB(dispCBFn f, void *cbData) { dispFn = f; dispData = cbData; return true; } 60 | inline void dispdata(int line, char *text) { if (dispFn) dispFn(dispData, line, text); } 61 | private: 62 | metaCBFn metaFn; 63 | void *metaData; 64 | 65 | dispCBFn dispFn; 66 | void *dispData; 67 | }; 68 | 69 | class Messaging 70 | { 71 | public: 72 | Messaging(); 73 | // void sendMessage(QueueHandle_t queue, metaFunction function, int line, int time, char *messageText, bool immediate = false, uint16_t data = 0, void* object = NULL); 74 | void sendMessage(QueueHandle_t queue, metaFunction function, void* object = NULL); 75 | void display(int line, int time, bool immediate, char *messageText); 76 | 77 | bool RegisterMetadataCB(MessagingEvents::metaCBFn fn, void *data) { return cb.RegisterMetadataCB(fn, data); } 78 | bool RegisterDisplayCB(MessagingEvents::dispCBFn fn, void *data) { return cb.RegisterDisplayCB(fn, data); } 79 | 80 | QueueHandle_t messageQueue; 81 | QueueHandle_t mainQueue; 82 | int messageDelay; 83 | 84 | void loop(); 85 | 86 | private: 87 | MessagingEvents cb; 88 | int messageIdCount; 89 | struct message *newMessage = NULL; 90 | uint32_t lastMessage = 0; 91 | }; 92 | 93 | #endif // MESSAGING_H -------------------------------------------------------------------------------- /examples/MP3_NetRadio/metadata.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 AKsevenFour / Adam Keher (adam@keher.com.au) 3 | http://www.aksevenfour.com / https://github.com/AdamKeher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 7 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 13 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 14 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 15 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | #ifndef METADATA_H 19 | #define METADATA_H 20 | 21 | struct StreamMetadata { 22 | char siteName[64] = "PICOAudio"; 23 | char streamTitle[64] = "No Metadata Set"; 24 | char genre[64]; 25 | char bitRate[4]; 26 | 27 | bool operator!=(const StreamMetadata& a) const 28 | { 29 | return ( 30 | strcmp(siteName, a.siteName) || 31 | strcmp(streamTitle, a.streamTitle) || 32 | strcmp(genre, a.genre) || 33 | strcmp(bitRate, a.bitRate) ); 34 | } 35 | }; 36 | 37 | #endif -------------------------------------------------------------------------------- /examples/MP3_SDCard/MP3_SDCard.ino: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | #include "WiFi.h" 3 | #include "lcd.h" 4 | #include "PicoAudio.h" 5 | 6 | #define LCD_ADDR 0x27 // I2C Address 7 | #define LCD_COL 16 8 | #define LCD_ROW 2 9 | 10 | PicoAudio *picoAudio; 11 | LCD *lcd; 12 | 13 | struct ID3Data { 14 | char Artist[128]; 15 | char Album[128]; 16 | char Title[128]; 17 | } id3data; 18 | 19 | void playNext() { 20 | Serial.println("[Play Next]================================================================"); 21 | strcpy(id3data.Album, "??"); 22 | strcpy(id3data.Artist, "??"); 23 | strcpy(id3data.Title, "??"); 24 | picoAudio->playNext(); 25 | updateLCD(); 26 | } 27 | 28 | void playPrev() { 29 | Serial.println("[Play Prev]================================================================"); 30 | strcpy(id3data.Album, "??"); 31 | strcpy(id3data.Artist, "??"); 32 | strcpy(id3data.Title, "??"); 33 | picoAudio->playPrev(); 34 | updateLCD(); 35 | } 36 | 37 | void GPIOChangeCallback(void *cbData, uint8_t ports, uint8_t button, bool state) { 38 | switch (button) { 39 | case 3: { 40 | if (state == true) { 41 | picoAudio->pauseResume(); 42 | Serial.printf("button %s\r\n", picoAudio->isPlaying() ? "Play" : "Pause" ); 43 | } 44 | break; 45 | } 46 | case 4: { 47 | if (state == true) { 48 | playPrev(); 49 | } 50 | break; 51 | } 52 | case 5: { 53 | if (state == true) { 54 | playNext(); 55 | } 56 | break; 57 | } 58 | case 6: { 59 | if (state == true) { 60 | Serial.printf("Volume: %d/10\r\n", picoAudio->volumeDown() + 1); 61 | } 62 | break; 63 | } 64 | case 7: { 65 | if (state == true) { 66 | Serial.printf("Volume: %d/10\r\n", picoAudio->volumeUp() + 1); 67 | } 68 | break; 69 | } 70 | default: break; 71 | } 72 | } 73 | 74 | void updateLCD() { 75 | char line1[256]; 76 | strcpy(line1, id3data.Artist); 77 | strcat(line1, " - "); 78 | strcat(line1, id3data.Album); 79 | 80 | char line2[156]; 81 | sprintf(line2, "%d/%d ", picoAudio->getFileListPosition() + 1, picoAudio->getFileListSize()); 82 | strcat(line2, id3data.Title); 83 | lcd->print(0, 0, true, line1); 84 | lcd->print(0, 1, true, line2); 85 | } 86 | 87 | void setup() { 88 | Serial.begin(115200); 89 | 90 | picoAudio = new PicoAudio(); 91 | picoAudio->begin(); 92 | picoAudio->mute(); 93 | picoAudio->RegisterButtonCB(GPIOChangeCallback, (void*)"messageProcessor"); 94 | 95 | lcd = new LCD(LCD_ADDR, LCD_COL, LCD_ROW, PA_SDA, PA_SCL); 96 | lcd->print(0, 0, true, "picoAUDIO Player"); 97 | lcd->print(0, 1, true, "2019 AKsevenFOUR"); 98 | 99 | if (!picoAudio->scanSD()) { 100 | Serial.println("Error mounting SD Card"); 101 | lcd->print(0, 0, true, "SD Card Error"); 102 | for(;;) {} 103 | } else 104 | Serial.println("SD Card mounted"); 105 | 106 | for (int i = 0; i < picoAudio->FileList()->size(); i++) { 107 | Serial.printf("file %d: %s\r\n", i, picoAudio->FileList()->get(i)); 108 | } 109 | 110 | picoAudio->setVolumeMax(); 111 | picoAudio->playFile(); 112 | } 113 | 114 | void loop() 115 | { 116 | picoAudio->process(); 117 | } 118 | 119 | // optional functions called by audio library 120 | 121 | void audio_info(const char *info){ 122 | Serial.print("info "); 123 | Serial.println(info); 124 | } 125 | 126 | void audio_id3data(const char *info) { //id3 metadata 127 | Serial.print("id3data "); 128 | Serial.println(info); 129 | 130 | char *token; 131 | const char delim[2] = ":"; 132 | token = strtok((char *)info, delim); 133 | for (int i = 0; i < strlen(token); i++) 134 | token[i] = tolower(token[i]); 135 | 136 | // artist 137 | if (strcmp(token, "artist") == 0) 138 | { 139 | strcpy(id3data.Artist, token + strlen(token) + 2); 140 | updateLCD(); 141 | } 142 | 143 | // Album 144 | if (strcmp(token, "album") == 0) 145 | { 146 | strcpy(id3data.Album, token + strlen(token) + 2); 147 | updateLCD(); 148 | } 149 | 150 | // title 151 | if (strcmp(token, "title") == 0) 152 | { 153 | strcpy(id3data.Title, token + strlen(token) + 2); 154 | updateLCD(); 155 | } 156 | } 157 | 158 | void audio_eof_mp3(const char *info) { //end of file 159 | Serial.print("eof_mp3 "); 160 | Serial.println(info); 161 | playNext(); 162 | } 163 | 164 | void audio_showstation(const char *info) { 165 | Serial.print("station "); 166 | Serial.println(info); 167 | } 168 | 169 | void audio_showstreaminfo(const char *info) { 170 | Serial.print("streaminfo "); 171 | Serial.println(info); 172 | } 173 | 174 | void audio_showstreamtitle(const char *info) { 175 | Serial.print("streamtitle "); 176 | Serial.println(info); 177 | } 178 | 179 | void audio_bitrate(const char *info) { 180 | Serial.print("bitrate "); 181 | Serial.println(info); 182 | } 183 | 184 | void audio_commercial(const char *info) { //duration in sec 185 | Serial.print("commercial "); 186 | Serial.println(info); 187 | } 188 | 189 | void audio_icyurl(const char *info) { //homepage 190 | Serial.print("icyurl "); 191 | Serial.println(info); 192 | } 193 | 194 | void audio_lasthost(const char *info) { //stream URL played 195 | Serial.print("lasthost "); 196 | Serial.println(info); 197 | } 198 | 199 | void audio_eof_speech(const char *info){ 200 | Serial.print("eof_speech "); 201 | Serial.println(info); 202 | } 203 | -------------------------------------------------------------------------------- /examples/PCF8574_Test/PCF8574_Test.ino: -------------------------------------------------------------------------------- 1 | #include "PicoAudio.h" 2 | 3 | // uncomment when using AK74 GPIO Expander Board module. It will cycle the LED's for testing 4 | // #define EXPANDERBOARD 1 5 | 6 | PicoAudio picoAudio = PicoAudio(); 7 | 8 | int outpin = 0; 9 | 10 | void printGPIO(uint8_t ports) { 11 | // echos the lines 12 | Serial.printf("\033[2J\033[H"); 13 | Serial.printf("Value: %d | Bin: ", ports); 14 | Serial.print(ports, BIN); 15 | Serial.printf(" | P0: %-5s | P1: %-5s | P2: %-5s | P3: %-5s | P4: %-5s | P5: %-5s | P6: %-5s | P7: %-5s |\r\n", 16 | (ports & P0) == 0 ? "\033[32mTRUE \033[37m" : "\033[31mFALSE\033[37m", 17 | (ports & P1) == 0 ? "\033[32mTRUE \033[37m" : "\033[31mFALSE\033[37m", 18 | (ports & P2) == 0 ? "\033[32mTRUE \033[37m" : "\033[31mFALSE\033[37m", 19 | (ports & P3) == 0 ? "\033[32mTRUE \033[37m" : "\033[31mFALSE\033[37m", 20 | (ports & P4) == 0 ? "\033[32mTRUE \033[37m" : "\033[31mFALSE\033[37m", 21 | (ports & P5) == 0 ? "\033[32mTRUE \033[37m" : "\033[31mFALSE\033[37m", 22 | (ports & P6) == 0 ? "\033[32mTRUE \033[37m" : "\033[31mFALSE\033[37m", 23 | (ports & P7) == 0 ? "\033[32mTRUE \033[37m" : "\033[31mFALSE\033[37m"); 24 | } 25 | 26 | void GPIOChangeCallback(void *cbData, uint8_t ports, uint8_t button, bool state) { 27 | printGPIO(ports); 28 | } 29 | 30 | void setup() 31 | { 32 | Serial.begin(115200); 33 | Serial.println("\nTEST PCF8574\n"); 34 | 35 | picoAudio.begin(); 36 | picoAudio.RegisterButtonCB(GPIOChangeCallback, (void*)"messageProcessor"); 37 | 38 | // print intial screen with all buttons as false 39 | printGPIO(0xFF); 40 | } 41 | 42 | unsigned long lastBlink = 0; 43 | 44 | void loop() 45 | { 46 | picoAudio.process(); 47 | 48 | #ifdef EXPANDERBOARD 49 | if (millis() - lastBlink > 50) { 50 | picoAudio.writeIO(outpin, LOW); 51 | int prevpin = outpin - 1; 52 | if (prevpin < 0) 53 | prevpin = 7; 54 | picoAudio.writeIO(prevpin, HIGH); 55 | outpin++; 56 | if (outpin > 7) 57 | outpin = 0; 58 | lastBlink = millis(); 59 | } 60 | #endif 61 | } -------------------------------------------------------------------------------- /examples/SD_Test/SD_Test.ino: -------------------------------------------------------------------------------- 1 | #include "FS.h" 2 | #include "SD.h" 3 | #include "SPI.h" 4 | 5 | void listDir(fs::FS &fs, const char * dirname, uint8_t levels){ 6 | Serial.printf("Listing directory: %s\n", dirname); 7 | 8 | File root = fs.open(dirname); 9 | if(!root){ 10 | Serial.println("Failed to open directory"); 11 | return; 12 | } 13 | if(!root.isDirectory()){ 14 | Serial.println("Not a directory"); 15 | return; 16 | } 17 | 18 | File file = root.openNextFile(); 19 | while(file){ 20 | if(file.isDirectory()){ 21 | Serial.print(" DIR : "); 22 | Serial.println(file.name()); 23 | if(levels){ 24 | listDir(fs, file.name(), levels -1); 25 | } 26 | } else { 27 | Serial.print(" FILE: "); 28 | Serial.print(file.name()); 29 | Serial.print(" SIZE: "); 30 | Serial.println(file.size()); 31 | } 32 | file = root.openNextFile(); 33 | } 34 | } 35 | 36 | void createDir(fs::FS &fs, const char * path){ 37 | Serial.printf("Creating Dir: %s\n", path); 38 | if(fs.mkdir(path)){ 39 | Serial.println("Dir created"); 40 | } else { 41 | Serial.println("mkdir failed"); 42 | } 43 | } 44 | 45 | void removeDir(fs::FS &fs, const char * path){ 46 | Serial.printf("Removing Dir: %s\n", path); 47 | if(fs.rmdir(path)){ 48 | Serial.println("Dir removed"); 49 | } else { 50 | Serial.println("rmdir failed"); 51 | } 52 | } 53 | 54 | void readFile(fs::FS &fs, const char * path){ 55 | Serial.printf("Reading file: %s\n", path); 56 | 57 | File file = fs.open(path); 58 | if(!file){ 59 | Serial.println("Failed to open file for reading"); 60 | return; 61 | } 62 | 63 | Serial.print("Read from file: "); 64 | while(file.available()){ 65 | Serial.write(file.read()); 66 | } 67 | file.close(); 68 | } 69 | 70 | void writeFile(fs::FS &fs, const char * path, const char * message){ 71 | Serial.printf("Writing file: %s\n", path); 72 | 73 | File file = fs.open(path, FILE_WRITE); 74 | if(!file){ 75 | Serial.println("Failed to open file for writing"); 76 | return; 77 | } 78 | if(file.print(message)){ 79 | Serial.println("File written"); 80 | } else { 81 | Serial.println("Write failed"); 82 | } 83 | file.close(); 84 | } 85 | 86 | void appendFile(fs::FS &fs, const char * path, const char * message){ 87 | Serial.printf("Appending to file: %s\n", path); 88 | 89 | File file = fs.open(path, FILE_APPEND); 90 | if(!file){ 91 | Serial.println("Failed to open file for appending"); 92 | return; 93 | } 94 | if(file.print(message)){ 95 | Serial.println("Message appended"); 96 | } else { 97 | Serial.println("Append failed"); 98 | } 99 | file.close(); 100 | } 101 | 102 | void renameFile(fs::FS &fs, const char * path1, const char * path2){ 103 | Serial.printf("Renaming file %s to %s\n", path1, path2); 104 | if (fs.rename(path1, path2)) { 105 | Serial.println("File renamed"); 106 | } else { 107 | Serial.println("Rename failed"); 108 | } 109 | } 110 | 111 | void deleteFile(fs::FS &fs, const char * path){ 112 | Serial.printf("Deleting file: %s\n", path); 113 | if(fs.remove(path)){ 114 | Serial.println("File deleted"); 115 | } else { 116 | Serial.println("Delete failed"); 117 | } 118 | } 119 | 120 | void testFileIO(fs::FS &fs, const char * path){ 121 | File file = fs.open(path); 122 | static uint8_t buf[512]; 123 | size_t len = 0; 124 | uint32_t start = millis(); 125 | uint32_t end = start; 126 | if(file){ 127 | len = file.size(); 128 | size_t flen = len; 129 | start = millis(); 130 | while(len){ 131 | size_t toRead = len; 132 | if(toRead > 512){ 133 | toRead = 512; 134 | } 135 | file.read(buf, toRead); 136 | len -= toRead; 137 | } 138 | end = millis() - start; 139 | Serial.printf("%u bytes read for %u ms\n", flen, end); 140 | file.close(); 141 | } else { 142 | Serial.println("Failed to open file for reading"); 143 | } 144 | 145 | 146 | file = fs.open(path, FILE_WRITE); 147 | if(!file){ 148 | Serial.println("Failed to open file for writing"); 149 | return; 150 | } 151 | 152 | size_t i; 153 | start = millis(); 154 | for(i=0; i<2048; i++){ 155 | file.write(buf, 512); 156 | } 157 | end = millis() - start; 158 | Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end); 159 | file.close(); 160 | } 161 | 162 | void setup(){ 163 | Serial.begin(115200); 164 | 165 | if(!SD.begin()){ 166 | Serial.println("Card Mount Failed"); 167 | return; 168 | } 169 | uint8_t cardType = SD.cardType(); 170 | 171 | if(cardType == CARD_NONE){ 172 | Serial.println("No SD card attached"); 173 | return; 174 | } 175 | 176 | Serial.print("SD Card Type: "); 177 | if(cardType == CARD_MMC){ 178 | Serial.println("MMC"); 179 | } else if(cardType == CARD_SD){ 180 | Serial.println("SDSC"); 181 | } else if(cardType == CARD_SDHC){ 182 | Serial.println("SDHC"); 183 | } else { 184 | Serial.println("UNKNOWN"); 185 | } 186 | 187 | uint64_t cardSize = SD.cardSize() / (1024 * 1024); 188 | Serial.printf("SD Card Size: %lluMB\n", cardSize); 189 | 190 | listDir(SD, "/", 0); 191 | createDir(SD, "/mydir"); 192 | listDir(SD, "/", 0); 193 | removeDir(SD, "/mydir"); 194 | listDir(SD, "/", 2); 195 | writeFile(SD, "/hello.txt", "Hello "); 196 | appendFile(SD, "/hello.txt", "World!\n"); 197 | readFile(SD, "/hello.txt"); 198 | deleteFile(SD, "/foo.txt"); 199 | renameFile(SD, "/hello.txt", "/foo.txt"); 200 | readFile(SD, "/foo.txt"); 201 | testFileIO(SD, "/test.txt"); 202 | Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024)); 203 | Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024)); 204 | Serial.printf("Test 1 end."); 205 | } 206 | 207 | void loop(){ 208 | delay(5000); 209 | 210 | listDir(SD, "/", 0); 211 | Serial.printf("Test loop end."); 212 | } 213 | -------------------------------------------------------------------------------- /examples/Sound_Plotter/Sound_Plotter.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 AKsevenFour / Adam Keher (adam@keher.com.au) 3 | http://www.aksevenfour.com / https://github.com/AdamKeher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 7 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 13 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 14 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 15 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | #include 19 | 20 | PicoAudio picoAudio = PicoAudio(); 21 | 22 | void setup() { 23 | Serial.begin(115200); 24 | Serial.println("Configuring I2S..."); 25 | 26 | picoAudio.begin(); 27 | picoAudio.initMIC(); 28 | 29 | Serial.println("I2S driver installed."); 30 | } 31 | 32 | int count; 33 | 34 | void loop() { 35 | int32_t sample = 0; 36 | int bytes_read = picoAudio.readMIC((char *)&sample, 4); 37 | if (count % 2 == 0 && bytes_read == 4) { 38 | Serial.println(sample); 39 | } 40 | count++; 41 | } -------------------------------------------------------------------------------- /examples/Sound_Recorder/Sound_Recorder.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 AKsevenFour / Adam Keher (adam@keher.com.au) 3 | http://www.aksevenfour.com / https://github.com/AdamKeher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 7 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 13 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 14 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 15 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include "Wav.h" 22 | 23 | const int record_time = 10; // seconds 24 | const char filename[] = "/sound.wav"; 25 | 26 | const int headerSize = 44; // static size of a wave file header 27 | const int waveDataSize = record_time * 88200; // mono wav file = 44,100 x 16 bits = 88200 bytes 28 | const int micBufferSize = 8000; // nominal buffer size for i2s mic recording left/right channels in 32 bit 29 | // as mic is mono one channel is discarded. Buffer size is ~22.7 ms) 30 | // 4 bytes (32bit sample) x 44100 sample rate x 2 chanels = 352800 bytes / seconds 31 | // 352800 bytes per secon / 8000 byte buffer = 22.67573696 ms 32 | const int wavBufferSize = micBufferSize/4; // wav buffer size is 1/4 of mic as wav is mono (half) and 16 bit as apposed to the mic's 32 bit (half) 33 | 34 | // file size on SD card will be: 88,200 bytes per second * 10 seconds + 44 byte wav header = 861k / 882,044 bytes 35 | 36 | byte header[headerSize]; 37 | char micData[micBufferSize]; 38 | char wavData[wavBufferSize]; 39 | 40 | File file; 41 | 42 | TinyPICO tinyPICO = TinyPICO(); 43 | PicoAudio picoAudio = PicoAudio(); 44 | 45 | void setup() { 46 | Serial.begin(115200); 47 | picoAudio.begin(); 48 | 49 | if (!SD.begin()) 50 | { 51 | Serial.println("SD begin failed"); 52 | return; 53 | } 54 | 55 | // create wav file header, remove existing file and write header to file on SD card 56 | CreateWavHeader(header, waveDataSize); 57 | SD.remove(filename); 58 | file = SD.open(filename, FILE_WRITE); 59 | if (!file) return; 60 | file.write(header, headerSize); 61 | 62 | // initialise i2s microphone 63 | picoAudio.initMIC(); 64 | 65 | // set tinyPICO LED to green for 5 seconds before turning red and commencing recording 66 | Serial.println("get ready for recording in 5 seconds"); 67 | tinyPICO.DotStar_SetBrightness(255); 68 | tinyPICO.DotStar_SetPixelColor(0, 255, 0); 69 | delay(5000); 70 | tinyPICO.DotStar_SetPixelColor(255, 0, 0); 71 | Serial.println("start recording"); 72 | 73 | // record audio and write to file 74 | for (int j = 0; j < waveDataSize/wavBufferSize; ++j) { 75 | picoAudio.readMIC(micData, micBufferSize); 76 | 77 | // convert 2 channel 32 bit to mono 16 bit 78 | for (int i = 0; i < micBufferSize / 8; ++i) { 79 | // wavdata counts 2 bytes per iteration (16bit (2 bytes) x 1 mono channel ) 80 | // micData counts 8 bytes per iteration (32bit (4 bytes) / 2 channel = 8 bytes) 81 | wavData[2 * i] = micData[8 * i + 2]; 82 | wavData[2 * i + 1] = micData[8 * i + 3]; 83 | } 84 | 85 | // write converted data to wav file 86 | file.write((const byte*)wavData, wavBufferSize); 87 | } 88 | 89 | file.close(); 90 | Serial.println("finish"); 91 | } 92 | 93 | void loop() { 94 | // flash LED once recording finished 95 | tinyPICO.DotStar_SetPixelColor(0, 0, 255); 96 | delay(1000); 97 | tinyPICO.DotStar_SetPixelColor(0, 0, 0); 98 | delay(1000); 99 | } 100 | -------------------------------------------------------------------------------- /examples/Sound_Recorder/Wav.cpp: -------------------------------------------------------------------------------- 1 | #include "Wav.h" 2 | 3 | void CreateWavHeader(byte* header, int waveDataSize){ 4 | header[0] = 'R'; 5 | header[1] = 'I'; 6 | header[2] = 'F'; 7 | header[3] = 'F'; 8 | unsigned int fileSizeMinus8 = waveDataSize + 44 - 8; 9 | header[4] = (byte)(fileSizeMinus8 & 0xFF); 10 | header[5] = (byte)((fileSizeMinus8 >> 8) & 0xFF); 11 | header[6] = (byte)((fileSizeMinus8 >> 16) & 0xFF); 12 | header[7] = (byte)((fileSizeMinus8 >> 24) & 0xFF); 13 | header[8] = 'W'; 14 | header[9] = 'A'; 15 | header[10] = 'V'; 16 | header[11] = 'E'; 17 | header[12] = 'f'; 18 | header[13] = 'm'; 19 | header[14] = 't'; 20 | header[15] = ' '; 21 | header[16] = 0x10; // linear PCM 22 | header[17] = 0x00; 23 | header[18] = 0x00; 24 | header[19] = 0x00; 25 | header[20] = 0x01; // linear PCM 26 | header[21] = 0x00; 27 | header[22] = 0x01; // monoral 28 | header[23] = 0x00; 29 | header[24] = 0x44; // sampling rate 44100 30 | header[25] = 0xAC; 31 | header[26] = 0x00; 32 | header[27] = 0x00; 33 | header[28] = 0x88; // Byte/sec = 44100x2x1 = 88200 34 | header[29] = 0x58; 35 | header[30] = 0x01; 36 | header[31] = 0x00; 37 | header[32] = 0x02; // 16bit monoral 38 | header[33] = 0x00; 39 | header[34] = 0x10; // 16bit 40 | header[35] = 0x00; 41 | header[36] = 'd'; 42 | header[37] = 'a'; 43 | header[38] = 't'; 44 | header[39] = 'a'; 45 | header[40] = (byte)(waveDataSize & 0xFF); 46 | header[41] = (byte)((waveDataSize >> 8) & 0xFF); 47 | header[42] = (byte)((waveDataSize >> 16) & 0xFF); 48 | header[43] = (byte)((waveDataSize >> 24) & 0xFF); 49 | } 50 | 51 | -------------------------------------------------------------------------------- /examples/Sound_Recorder/Wav.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // 16bit, monoral, 44100Hz, linear PCM 4 | void CreateWavHeader(byte* header, int waveDataSize); // size of header is 44 5 | -------------------------------------------------------------------------------- /examples/Vocoder/Vocoder.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 AKsevenFour / Adam Keher (adam@keher.com.au) 3 | http://www.aksevenfour.com / https://github.com/AdamKeher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 7 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 13 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 14 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 15 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | #include "PicoAudio.h" 19 | #include "TinyPICO.h" 20 | 21 | const int bufferSize = 8000; 22 | char audioData[bufferSize]; 23 | int* audioDataPtr = (int*)&audioData[0]; 24 | bool applyVocoder = false; 25 | PicoAudio picoAudio = PicoAudio(false); 26 | TinyPICO tinyPICO = TinyPICO(); 27 | 28 | void GPIOChangeCallback(void *cbData, uint8_t ports, uint8_t button, bool state) { 29 | switch (button) { 30 | case 3: { 31 | applyVocoder = state ? true : false; 32 | tinyPICO.DotStar_SetPixelColor(applyVocoder ? 0 : 255, applyVocoder ? 255 : 0, 0); 33 | break; 34 | } 35 | default: 36 | break; 37 | } 38 | } 39 | 40 | void setup() { 41 | Serial.begin(115200); 42 | Serial.flush(); 43 | 44 | picoAudio.begin(); 45 | picoAudio.initDAC(); 46 | picoAudio.initMIC(); 47 | picoAudio.RegisterButtonCB(GPIOChangeCallback, (void*)"messageProcessor"); 48 | 49 | Serial.println("Hold push button to apply vocoder."); 50 | tinyPICO.DotStar_SetBrightness(255); 51 | tinyPICO.DotStar_SetPixelColor(255, 0, 0); 52 | } 53 | 54 | float counter; 55 | 56 | void loop() { 57 | counter = 0.0f; 58 | 59 | picoAudio.readMIC(audioData, bufferSize); 60 | 61 | if (applyVocoder) 62 | { 63 | for (int i = 0; i < bufferSize; i++) 64 | { 65 | audioData[i] = (audioData[i]) + (audioData[i] * abs(sin(counter))); 66 | counter += 0.01f; 67 | } 68 | } 69 | 70 | for (int i = 0; i < bufferSize / 4; i+=2) { 71 | audioDataPtr[i] = audioDataPtr[i] * 1.5f; // gain 72 | audioDataPtr[i + 1] = audioDataPtr[i]; // duplicate mono recording for stereo output 73 | } 74 | 75 | picoAudio.writeDAC(audioData, bufferSize); 76 | picoAudio.process(false, true); 77 | } 78 | -------------------------------------------------------------------------------- /examples/Waveform/Waveform.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 AKsevenFour / Adam Keher (adam@keher.com.au) 3 | http://www.aksevenfour.com / https://github.com/AdamKeher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 7 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 13 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 14 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 15 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | 17 | Ported from Arduino Zero / Feather M0 I2S audio tone generation example. 18 | Orginal Author: Tony DiCol 19 | Port: Adam Keher 20 | */ 21 | 22 | #include "PicoAudio.h" 23 | 24 | #define SAMPLERATE_HZ 44100 // The sample rate of the audio. Higher sample rates have better fidelity, 25 | // but these tones are so simple it won't make a difference. 44.1khz is 26 | // standard CD quality sound. 27 | 28 | #define AMPLITUDE ((1<<29)-1) // Set the amplitude of generated waveforms. This controls how loud 29 | // the signals are, and can be any value from 0 to 2**31 - 1. Start with 30 | // a low value to prevent damaging speakers! 31 | 32 | #define WAV_SIZE 256 // The size of each generated waveform. The larger the size the higher 33 | // quality the signal. A size of 256 is more than enough for these simple 34 | // waveforms. 35 | 36 | 37 | // Define the frequency of music notes (from http://www.phy.mtu.edu/~suits/notefreqs.html): 38 | #define C4_HZ 261.63 39 | #define D4_HZ 293.66 40 | #define E4_HZ 329.63 41 | #define F4_HZ 349.23 42 | #define G4_HZ 392.00 43 | #define A4_HZ 440.00 44 | #define B4_HZ 493.88 45 | 46 | // Define a C-major scale to play all the notes up and down. 47 | float scale[] = { C4_HZ, D4_HZ, E4_HZ, F4_HZ, G4_HZ, A4_HZ, B4_HZ, A4_HZ, G4_HZ, F4_HZ, E4_HZ, D4_HZ, C4_HZ }; 48 | 49 | // Store basic waveforms in memory. 50 | int32_t sine[WAV_SIZE] = {0}; 51 | int32_t sawtooth[WAV_SIZE] = {0}; 52 | int32_t triangle[WAV_SIZE] = {0}; 53 | int32_t square[WAV_SIZE] = {0}; 54 | 55 | PicoAudio *picoAudio; 56 | 57 | void generateSine(int32_t amplitude, int32_t* buffer, uint16_t length) { 58 | // Generate a sine wave signal with the provided amplitude and store it in 59 | // the provided buffer of size length. 60 | for (int i=0; iwriteDAC((char *)i2sBuffer, i2sBufferSize*sizeof(int32_t)); 119 | i2sBufferPos = 0; 120 | } 121 | } 122 | } 123 | 124 | void setup() { 125 | // Configure serial port. 126 | Serial.begin(115200); 127 | Serial.println("PicoAUDIO I2S Audio Tone Generator"); 128 | 129 | picoAudio = new PicoAudio(); 130 | picoAudio->begin(); 131 | picoAudio->initDAC(); 132 | 133 | // Generate waveforms. 134 | generateSine(AMPLITUDE, sine, WAV_SIZE); 135 | generateSawtooth(AMPLITUDE, sawtooth, WAV_SIZE); 136 | generateTriangle(AMPLITUDE, triangle, WAV_SIZE); 137 | generateSquare(AMPLITUDE, square, WAV_SIZE); 138 | } 139 | 140 | void loop() { 141 | Serial.println("Sine wave"); 142 | for (int i=0; iprocess(); 167 | delay(1000); 168 | } -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ################################### 2 | # Syntax Coloring Map For PicoAudio 3 | ################################### 4 | 5 | ################################### 6 | # Datatypes (KEYWORD1) 7 | ################################### 8 | 9 | PicoAudio KEYWORD1 10 | 11 | ################################### 12 | # Methods and Functions (KEYWORD2) 13 | ################################### 14 | 15 | 16 | ################################### 17 | # Constants (LITERAL1) 18 | ################################### 19 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=picoAUDIO Helper Library 2 | version=1.0.0 3 | author=AK74 4 | maintainer=Adam Keher 5 | sentence=A picoAUDIO Helper Library 6 | paragraph=A helper library for the picoAUDIO audio board made especially for the tinyPICO development board. 7 | category=Uncategorized 8 | url=https://github.com/AdamKeher/Pico-Audio 9 | architectures=* 10 | -------------------------------------------------------------------------------- /src/Audio.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Audio.h 3 | * 4 | * Created on: Oct 26,2018 5 | * Updated on: Sep 05,2019 6 | * Author: Wolle (schreibfaul1) 7 | */ 8 | 9 | #ifndef AUDIO_H_ 10 | #define AUDIO_H_ 11 | 12 | #include "Arduino.h" 13 | #include "SPI.h" 14 | #include "SD.h" 15 | #include "FS.h" 16 | #include "WiFiClientSecure.h" 17 | #include "driver/i2s.h" 18 | 19 | extern __attribute__((weak)) void audio_info(const char*); 20 | extern __attribute__((weak)) void audio_id3data(const char*); //ID3 metadata 21 | extern __attribute__((weak)) void audio_eof_mp3(const char*); //end of mp3 file 22 | extern __attribute__((weak)) void audio_showstreamtitle(const char*); 23 | extern __attribute__((weak)) void audio_showstation(const char*); 24 | extern __attribute__((weak)) void audio_showstreaminfo(const char*); 25 | extern __attribute__((weak)) void audio_bitrate(const char*); 26 | extern __attribute__((weak)) void audio_commercial(const char*); 27 | extern __attribute__((weak)) void audio_icyurl(const char*); 28 | extern __attribute__((weak)) void audio_lasthost(const char*); 29 | extern __attribute__((weak)) void audio_eof_speech(const char*); 30 | 31 | #define AUDIO_HEADER 2 //const for datamode 32 | #define AUDIO_DATA 4 33 | #define AUDIO_METADATA 8 34 | #define AUDIO_PLAYLISTINIT 16 35 | #define AUDIO_PLAYLISTHEADER 32 36 | #define AUDIO_PLAYLISTDATA 64 37 | #define AUDIO_SWM 128 38 | 39 | // 40 | 41 | 42 | class Audio { 43 | 44 | public: 45 | Audio(); 46 | ~Audio(); 47 | bool connecttoSD(String sdfile); 48 | bool connecttohost(String host); 49 | bool connecttospeech(String speech, String lang); 50 | void loop(); 51 | uint32_t getFileSize(); 52 | uint32_t getFilePos(); 53 | /** 54 | * @brief Get the audio file duration in seconds 55 | * 56 | * @return uint32_t file duration in seconds, 0 if no file active 57 | */ 58 | uint32_t getAudioFileDuration(); 59 | /** 60 | * @brief Get the current plying time in seconds 61 | * 62 | * @return uint32_t current second of audio file, 0 if no file active 63 | */ 64 | uint32_t getAudioCurrentTime(); 65 | bool setFilePos(uint32_t pos); 66 | /** 67 | * @brief audioFileSeek seeks the file in both directions 68 | * 69 | * @param[in] speed 70 | * speed > 0 : fast-forward 71 | * speed < 0 : fast-rewind 72 | * @return true if audio file active and speed is valid, otherwise false 73 | */ 74 | bool audioFileSeek(const int8_t speed); 75 | bool setPinout(uint8_t BCLK, uint8_t LRC, uint8_t DOUT); 76 | void stopSong(); 77 | /** 78 | * @brief pauseResume pauses current playback 79 | * 80 | * @return true if audio file or stream is active, false otherwise 81 | */ 82 | bool pauseResume(); 83 | bool isPlaying(); 84 | 85 | void setVolume(uint8_t vol); 86 | uint8_t getVolume(); 87 | inline uint8_t getDatamode(){return m_datamode;} 88 | inline void setDatamode(uint8_t dm){m_datamode=dm;} 89 | inline uint32_t streamavail() {if(m_f_ssl==false) return client.available(); else return clientsecure.available();} 90 | 91 | private: 92 | void processLocalFile(); 93 | void processWebStream(); 94 | int sendBytes(uint8_t *data, size_t len); 95 | void readID3Metadata(); 96 | bool setSampleRate(int hz); 97 | bool setBitsPerSample(int bits); 98 | bool setChannels(int channels); 99 | bool playChunk(); 100 | bool playSample(int16_t sample[2]) ; 101 | int16_t Gain(int16_t s); 102 | bool fill_InputBuf(); 103 | void showstreamtitle(const char *ml, bool full); 104 | bool chkhdrline(const char* str); 105 | void handlebyte(uint8_t b); 106 | esp_err_t I2Sstart(uint8_t i2s_num); 107 | esp_err_t I2Sstop(uint8_t i2s_num); 108 | char* lltoa(long long val, int base); 109 | long long int XL (long long int a, const char* b); 110 | String urlencode(String str); 111 | 112 | private: 113 | enum : int { APLL_AUTO = -1, APLL_ENABLE = 1, APLL_DISABLE = 0 }; 114 | enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 }; 115 | typedef enum { LEFTCHANNEL=0, RIGHTCHANNEL=1 } SampleIndex; 116 | 117 | const uint8_t volumetable[22]={ 0, 1, 2, 3, 4 , 6 , 8, 10, 12, 14, 17, 118 | 20, 23, 27, 30 ,34, 38, 43 ,48, 52, 58, 64}; //22 elements 119 | 120 | File mp3file; 121 | WiFiClient client; 122 | WiFiClientSecure clientsecure; 123 | char chbuf[256]; 124 | char path[256]; 125 | int m_id3Size=0; // length id3 tag 126 | int m_LFcount; // Detection of end of header 127 | int m_lastChannels; 128 | int m_bytesLeft=0; 129 | int m_writePtr=0; // ptr sampleBuffer 130 | int m_readPtr=0; // ptr sampleBuffer 131 | int m_bitrate=0; 132 | int m_readbytes=0; // bytes read 133 | int m_metacount=0; // Number of bytes in metadata 134 | int8_t m_playlist_num = 0 ; // Nonzero for selection from playlist 135 | uint8_t m_inBuff[1600]; // inputBuffer 136 | uint16_t m_inBuffwindex=0; // write index 137 | uint16_t m_inbuffrindex=0; // read index 138 | const uint16_t m_inBuffsize=sizeof(m_inBuff); // size of inputBuffer 139 | uint8_t m_rev=0; // revision 140 | uint8_t m_BCLK=0; // Bit Clock 141 | uint8_t m_LRC=0; // Left/Right Clock 142 | uint8_t m_DOUT=0; // Data Out 143 | uint8_t m_vol=64; // volume 144 | uint8_t m_bps; // bitsPerSample 145 | uint8_t m_channels; 146 | uint8_t m_i2s_num= I2S_NUM_0; // I2S_NUM_0 or I2S_NUM_1 147 | int16_t m_buffValid; 148 | int16_t m_lastFrameEnd; 149 | int16_t m_outBuff[2048*2]; //[1152 * 2]; // Interleaved L/R 150 | int16_t m_validSamples = 0; 151 | int16_t m_curSample; 152 | int16_t m_lastSample[2]; 153 | int16_t* m_leftSample; 154 | int16_t* m_rightSample; 155 | uint16_t m_datamode=0; // Statemaschine 156 | uint32_t m_metaint = 0; // Number of databytes between metadata 157 | uint32_t m_totalcount = 0; // Counter mp3 data 158 | uint32_t m_chunkcount = 0 ; // Counter for chunked transfer 159 | uint32_t m_t0; 160 | uint32_t m_count=0; // Bytecounter between metadata 161 | String m_mp3title=""; // the name of the file 162 | String m_playlist ; // The URL of the specified playlist 163 | String m_lastHost=""; // Store the last URL to a webstream 164 | String m_metaline ; // Readable line in metadata 165 | String m_icyname ; // Icecast station name 166 | String m_st_remember=""; // Save the last streamtitle 167 | String m_icyurl=""; // Store ie icy-url if received 168 | String m_plsURL; 169 | String m_plsStationName; 170 | String m_icystreamtitle ; // Streamtitle from metadata 171 | bool m_f_unsync = false; 172 | bool m_f_exthdr = false; // ID3 extended header 173 | bool m_f_localfile = false ; // Play from local mp3-file 174 | bool m_f_webstream = false ; // Play from URL 175 | bool m_f_ssl=false; 176 | bool m_f_running=false; 177 | bool m_f_firststream_ready=false; // Set after connecttohost and first streamdata are available 178 | bool m_f_ctseen=false; // First line of header seen or not 179 | bool m_f_chunked = false ; // Station provides chunked transfer 180 | bool m_f_filled; // outputBuffer 181 | bool m_f_swm=false; 182 | bool m_f_firstmetabyte=false; // True if first metabyte (counter) 183 | bool m_f_plsFile=false; // Set if URL is known 184 | bool m_f_plsTitle=false; // Set if StationName is known 185 | bool m_ctseen=false; // First line of header seen or not 186 | bool m_f_stream=false; // Set false if stream is lost 187 | bool m_f_mp3=false; // indicates mp3 188 | bool m_f_aac=false; // indicates aac 189 | bool m_f_playing = false; // valid mp3 stream recognized 190 | unsigned int m_lastRate; 191 | size_t m_bytesWritten=0; // set in i2s_write() but not used 192 | uint32_t m_audioFileDuration=0; 193 | }; 194 | 195 | #endif /* AUDIO_H_ */ 196 | -------------------------------------------------------------------------------- /src/GPIOExpander.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 AKsevenFour / Adam Keher (adam@keher.com.au) 3 | http://www.aksevenfour.com / https://github.com/AdamKeher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 7 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 13 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 14 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 15 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | #include "GPIOExpander.h" 19 | 20 | GPIOExpander::GPIOExpander(uint8_t address, uint8_t sda, uint8_t scl) { 21 | _address = address; 22 | _sda = sda; 23 | _scl = scl; 24 | 25 | _error = GPIOEXPANDER_OK; 26 | } 27 | 28 | void GPIOExpander::begin(uint8_t val) 29 | { 30 | Wire.begin(_sda, _scl); 31 | write8(val); 32 | } 33 | 34 | bool GPIOExpander::testInput(uint8_t inputs, uint8_t input) { 35 | return ((inputs >> input) ^ 0xFF) & 0x01; 36 | } 37 | 38 | uint8_t GPIOExpander::read8() 39 | { 40 | if (Wire.requestFrom(_address, (uint8_t)1) != 1) 41 | { 42 | _error = GPIOEXPANDER_I2C_ERROR; 43 | return _dataIn; // previous value 44 | } 45 | _dataIn = Wire.read(); 46 | return _dataIn; 47 | } 48 | 49 | uint8_t GPIOExpander::read(uint8_t pin) { 50 | if ((read8() & (1 << pin)) > 0) 51 | return HIGH; 52 | else 53 | return LOW; 54 | } 55 | 56 | void GPIOExpander::write8(const uint8_t value) 57 | { 58 | _dataOut = value; 59 | Wire.beginTransmission(_address); 60 | Wire.write(_dataOut); 61 | _error = Wire.endTransmission(); 62 | } 63 | 64 | void GPIOExpander::write(const uint8_t pin, const uint8_t value) 65 | { 66 | if (value == LOW) 67 | { 68 | _dataOut &= ~(1 << pin); 69 | } 70 | else 71 | { 72 | _dataOut |= (1 << pin); 73 | } 74 | write8(_dataOut); 75 | } 76 | 77 | void GPIOExpander::process() { 78 | uint8_t value = read8(); 79 | 80 | // Buttons start at P3 on GPIO Expander 81 | for (int i = 0; i < 8; i++) { 82 | // shift to button bit, flip it as it's a pull up to gnd and isolate the result 83 | bool curState = testInput(value, i); 84 | if (curState != buttonStates[i]) { 85 | cb.change(value, i, curState); 86 | buttonStates[i] = curState; 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /src/GPIOExpander.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 AKsevenFour / Adam Keher (adam@keher.com.au) 3 | http://www.aksevenfour.com / https://github.com/AdamKeher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 7 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 13 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 14 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 15 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | #ifndef GPIOEXPANDER_H 19 | #define GPIOEXPANDER_H 20 | 21 | #include 22 | #include 23 | 24 | #define BUTTON_UP 0x0 25 | #define BUTTON_DOWN 0x1 26 | 27 | #define P0 0x01 28 | #define P1 0x02 29 | #define P2 0x04 30 | #define P3 0x08 31 | #define P4 0x10 32 | #define P5 0x20 33 | #define P6 0x40 34 | #define P7 0x80 35 | 36 | #define GPIOEXPANDER_OK 0x00 37 | #define GPIOEXPANDER_I2C_ERROR 0x82 38 | 39 | class GPIOEvents 40 | { 41 | public: 42 | GPIOEvents() { ClearCBs(); } 43 | void ClearCBs() { chngFn = NULL; }; 44 | 45 | typedef void (*chngCBFn)(void *cbData, uint8_t ports, uint8_t button, bool state); 46 | bool RegisterChangeCB(chngCBFn f, void *cbData) { chngFn = f; chngData = cbData; return true; } 47 | inline void change(uint8_t ports, uint8_t button, bool state) { if (chngFn) chngFn(chngData, ports, button, state); } 48 | private: 49 | chngCBFn chngFn; 50 | void *chngData; 51 | }; 52 | 53 | class GPIOExpander 54 | { 55 | public: 56 | GPIOExpander(uint8_t address, uint8_t sda, uint8_t scl); 57 | 58 | void begin(uint8_t val=0xFF); 59 | 60 | static bool testInput(uint8_t inputs, uint8_t input); 61 | bool RegisterChangeCB(GPIOEvents::chngCBFn fn, void *data) { return cb.RegisterChangeCB(fn, data); } 62 | 63 | uint8_t read8(); 64 | uint8_t read(uint8_t pin); 65 | void write8(const uint8_t value); 66 | void write(const uint8_t pin, const uint8_t value); 67 | 68 | void process(); 69 | 70 | bool buttonStates[8] { false, false, false, false, false, false, false, false }; 71 | 72 | private: 73 | GPIOEvents cb; 74 | uint8_t _address; 75 | uint8_t _sda; 76 | uint8_t _scl; 77 | uint8_t _dataIn; 78 | uint8_t _dataOut; 79 | int _error; 80 | }; 81 | 82 | #endif // GPIOEXPANDER_H -------------------------------------------------------------------------------- /src/LinkedList.h: -------------------------------------------------------------------------------- 1 | /* 2 | LinkedList.h - V1.1 - Generic LinkedList implementation 3 | Works better with FIFO, because LIFO will need to 4 | search the entire List to find the last one; 5 | 6 | For instructions, go to https://github.com/ivanseidel/LinkedList 7 | 8 | Created by Ivan Seidel Gomes, March, 2013. 9 | Released into the public domain. 10 | */ 11 | 12 | 13 | #ifndef LinkedList_h 14 | #define LinkedList_h 15 | 16 | #include 17 | 18 | template 19 | struct ListNode 20 | { 21 | T data; 22 | ListNode *next; 23 | }; 24 | 25 | template 26 | class LinkedList{ 27 | 28 | protected: 29 | int _size; 30 | ListNode *root; 31 | ListNode *last; 32 | 33 | // Helps "get" method, by saving last position 34 | ListNode *lastNodeGot; 35 | int lastIndexGot; 36 | // isCached should be set to FALSE 37 | // everytime the list suffer changes 38 | bool isCached; 39 | 40 | ListNode* getNode(int index); 41 | 42 | ListNode* findEndOfSortedString(ListNode *p, int (*cmp)(T &, T &)); 43 | 44 | public: 45 | LinkedList(); 46 | LinkedList(int sizeIndex, T _t); //initiate list size and default value 47 | ~LinkedList(); 48 | 49 | /* 50 | Returns current size of LinkedList 51 | */ 52 | virtual int size(); 53 | /* 54 | Adds a T object in the specified index; 55 | Unlink and link the LinkedList correcly; 56 | Increment _size 57 | */ 58 | virtual bool add(int index, T); 59 | /* 60 | Adds a T object in the end of the LinkedList; 61 | Increment _size; 62 | */ 63 | virtual bool add(T); 64 | /* 65 | Adds a T object in the start of the LinkedList; 66 | Increment _size; 67 | */ 68 | virtual bool unshift(T); 69 | /* 70 | Set the object at index, with T; 71 | */ 72 | virtual bool set(int index, T); 73 | /* 74 | Remove object at index; 75 | If index is not reachable, returns false; 76 | else, decrement _size 77 | */ 78 | virtual T remove(int index); 79 | /* 80 | Remove last object; 81 | */ 82 | virtual T pop(); 83 | /* 84 | Remove first object; 85 | */ 86 | virtual T shift(); 87 | /* 88 | Get the index'th element on the list; 89 | Return Element if accessible, 90 | else, return false; 91 | */ 92 | virtual T get(int index); 93 | 94 | /* 95 | Clear the entire array 96 | */ 97 | virtual void clear(); 98 | 99 | /* 100 | Sort the list, given a comparison function 101 | */ 102 | virtual void sort(int (*cmp)(T &, T &)); 103 | 104 | // add support to array brakets [] operator 105 | inline T& operator[](int index); 106 | inline T& operator[](size_t& i) { return this->get(i); } 107 | inline const T& operator[](const size_t& i) const { return this->get(i); } 108 | 109 | }; 110 | 111 | // Initialize LinkedList with false values 112 | template 113 | LinkedList::LinkedList() 114 | { 115 | root=NULL; 116 | last=NULL; 117 | _size=0; 118 | 119 | lastNodeGot = root; 120 | lastIndexGot = 0; 121 | isCached = false; 122 | } 123 | 124 | // Clear Nodes and free Memory 125 | template 126 | LinkedList::~LinkedList() 127 | { 128 | ListNode* tmp; 129 | while(root!=NULL) 130 | { 131 | tmp=root; 132 | root=root->next; 133 | delete tmp; 134 | } 135 | last = NULL; 136 | _size=0; 137 | isCached = false; 138 | } 139 | 140 | /* 141 | Actualy "logic" coding 142 | */ 143 | 144 | template 145 | ListNode* LinkedList::getNode(int index){ 146 | 147 | int _pos = 0; 148 | ListNode* current = root; 149 | 150 | // Check if the node trying to get is 151 | // immediatly AFTER the previous got one 152 | if(isCached && lastIndexGot <= index){ 153 | _pos = lastIndexGot; 154 | current = lastNodeGot; 155 | } 156 | 157 | while(_pos < index && current){ 158 | current = current->next; 159 | 160 | _pos++; 161 | } 162 | 163 | // Check if the object index got is the same as the required 164 | if(_pos == index){ 165 | isCached = true; 166 | lastIndexGot = index; 167 | lastNodeGot = current; 168 | 169 | return current; 170 | } 171 | 172 | return NULL; 173 | } 174 | 175 | template 176 | int LinkedList::size(){ 177 | return _size; 178 | } 179 | 180 | template 181 | LinkedList::LinkedList(int sizeIndex, T _t){ 182 | for (int i = 0; i < sizeIndex; i++){ 183 | add(_t); 184 | } 185 | } 186 | 187 | template 188 | bool LinkedList::add(int index, T _t){ 189 | 190 | if(index >= _size) 191 | return add(_t); 192 | 193 | if(index == 0) 194 | return unshift(_t); 195 | 196 | ListNode *tmp = new ListNode(), 197 | *_prev = getNode(index-1); 198 | tmp->data = _t; 199 | tmp->next = _prev->next; 200 | _prev->next = tmp; 201 | 202 | _size++; 203 | isCached = false; 204 | 205 | return true; 206 | } 207 | 208 | template 209 | bool LinkedList::add(T _t){ 210 | 211 | ListNode *tmp = new ListNode(); 212 | tmp->data = _t; 213 | tmp->next = NULL; 214 | 215 | if(root){ 216 | // Already have elements inserted 217 | last->next = tmp; 218 | last = tmp; 219 | }else{ 220 | // First element being inserted 221 | root = tmp; 222 | last = tmp; 223 | } 224 | 225 | _size++; 226 | isCached = false; 227 | 228 | return true; 229 | } 230 | 231 | template 232 | bool LinkedList::unshift(T _t){ 233 | 234 | if(_size == 0) 235 | return add(_t); 236 | 237 | ListNode *tmp = new ListNode(); 238 | tmp->next = root; 239 | tmp->data = _t; 240 | root = tmp; 241 | 242 | _size++; 243 | isCached = false; 244 | 245 | return true; 246 | } 247 | 248 | 249 | template 250 | T& LinkedList::operator[](int index) { 251 | return getNode(index)->data; 252 | } 253 | 254 | template 255 | bool LinkedList::set(int index, T _t){ 256 | // Check if index position is in bounds 257 | if(index < 0 || index >= _size) 258 | return false; 259 | 260 | getNode(index)->data = _t; 261 | return true; 262 | } 263 | 264 | template 265 | T LinkedList::pop(){ 266 | if(_size <= 0) 267 | return T(); 268 | 269 | isCached = false; 270 | 271 | if(_size >= 2){ 272 | ListNode *tmp = getNode(_size - 2); 273 | T ret = tmp->next->data; 274 | delete(tmp->next); 275 | tmp->next = NULL; 276 | last = tmp; 277 | _size--; 278 | return ret; 279 | }else{ 280 | // Only one element left on the list 281 | T ret = root->data; 282 | delete(root); 283 | root = NULL; 284 | last = NULL; 285 | _size = 0; 286 | return ret; 287 | } 288 | } 289 | 290 | template 291 | T LinkedList::shift(){ 292 | if(_size <= 0) 293 | return T(); 294 | 295 | if(_size > 1){ 296 | ListNode *_next = root->next; 297 | T ret = root->data; 298 | delete(root); 299 | root = _next; 300 | _size --; 301 | isCached = false; 302 | 303 | return ret; 304 | }else{ 305 | // Only one left, then pop() 306 | return pop(); 307 | } 308 | 309 | } 310 | 311 | template 312 | T LinkedList::remove(int index){ 313 | if (index < 0 || index >= _size) 314 | { 315 | return T(); 316 | } 317 | 318 | if(index == 0) 319 | return shift(); 320 | 321 | if (index == _size-1) 322 | { 323 | return pop(); 324 | } 325 | 326 | ListNode *tmp = getNode(index - 1); 327 | ListNode *toDelete = tmp->next; 328 | T ret = toDelete->data; 329 | tmp->next = tmp->next->next; 330 | delete(toDelete); 331 | _size--; 332 | isCached = false; 333 | return ret; 334 | } 335 | 336 | 337 | template 338 | T LinkedList::get(int index){ 339 | ListNode *tmp = getNode(index); 340 | 341 | return (tmp ? tmp->data : T()); 342 | } 343 | 344 | template 345 | void LinkedList::clear(){ 346 | while(size() > 0) 347 | shift(); 348 | } 349 | 350 | template 351 | void LinkedList::sort(int (*cmp)(T &, T &)){ 352 | if(_size < 2) return; // trivial case; 353 | 354 | for(;;) { 355 | 356 | ListNode **joinPoint = &root; 357 | 358 | while(*joinPoint) { 359 | ListNode *a = *joinPoint; 360 | ListNode *a_end = findEndOfSortedString(a, cmp); 361 | 362 | if(!a_end->next ) { 363 | if(joinPoint == &root) { 364 | last = a_end; 365 | isCached = false; 366 | return; 367 | } 368 | else { 369 | break; 370 | } 371 | } 372 | 373 | ListNode *b = a_end->next; 374 | ListNode *b_end = findEndOfSortedString(b, cmp); 375 | 376 | ListNode *tail = b_end->next; 377 | 378 | a_end->next = NULL; 379 | b_end->next = NULL; 380 | 381 | while(a && b) { 382 | if(cmp(a->data, b->data) <= 0) { 383 | *joinPoint = a; 384 | joinPoint = &a->next; 385 | a = a->next; 386 | } 387 | else { 388 | *joinPoint = b; 389 | joinPoint = &b->next; 390 | b = b->next; 391 | } 392 | } 393 | 394 | if(a) { 395 | *joinPoint = a; 396 | while(a->next) a = a->next; 397 | a->next = tail; 398 | joinPoint = &a->next; 399 | } 400 | else { 401 | *joinPoint = b; 402 | while(b->next) b = b->next; 403 | b->next = tail; 404 | joinPoint = &b->next; 405 | } 406 | } 407 | } 408 | } 409 | 410 | template 411 | ListNode* LinkedList::findEndOfSortedString(ListNode *p, int (*cmp)(T &, T &)) { 412 | while(p->next && cmp(p->data, p->next->data) <= 0) { 413 | p = p->next; 414 | } 415 | 416 | return p; 417 | } 418 | 419 | #endif 420 | -------------------------------------------------------------------------------- /src/PicoAudio.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 AKsevenFour / Adam Keher (adam@keher.com.au) 3 | http://www.aksevenfour.com / https://github.com/AdamKeher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 7 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 13 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 14 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 15 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | #include "PicoAudio.h" 19 | 20 | PicoAudio::PicoAudio(bool initAudio) { 21 | ioExpander = new GPIOExpander(PE_ADDR, PE_SDA, PE_SCL); 22 | if (initAudio) { 23 | audio = new Audio(); 24 | audio->setVolume(21); // 0...21 25 | } 26 | fileList = new LinkedList(); 27 | } 28 | 29 | void PicoAudio::begin() { 30 | ioExpander->begin(); 31 | } 32 | 33 | void PicoAudio::process() { 34 | ioExpander->process(); 35 | audio->loop(); 36 | } 37 | 38 | void PicoAudio::process(bool processAudio, bool processGPIO) { 39 | if (processAudio) 40 | audio->loop(); 41 | if (processGPIO) 42 | ioExpander->process(); 43 | } 44 | 45 | bool PicoAudio::connecttoHost(String sdfile) { 46 | return audio->connecttohost(sdfile); 47 | } 48 | 49 | bool PicoAudio::connecttoSD(String sdfile) { 50 | return audio->connecttoSD(sdfile); 51 | } 52 | 53 | bool PicoAudio::pauseResume() { 54 | return audio->pauseResume(); 55 | } 56 | 57 | bool PicoAudio::isPlaying() { 58 | return audio->isPlaying(); 59 | } 60 | 61 | void PicoAudio::setVolume(uint8_t vol){ // vol 22 steps, 0...21 62 | audio->setVolume(vol); 63 | } 64 | 65 | bool PicoAudio::scanSD() { 66 | if (!SD.begin()) 67 | return false; 68 | 69 | populate(SD, "/mp3"); 70 | } 71 | 72 | void PicoAudio::populate(fs::FS &fs, const char* dirname) { 73 | File root = fs.open(dirname); 74 | 75 | if(!root) { 76 | return; 77 | } 78 | if(!root.isDirectory()){ 79 | return; 80 | } 81 | 82 | File file = root.openNextFile(); 83 | char extension[5] { 0x0, 0x0, 0x0, 0x0, 0x0 }; 84 | while(file){ 85 | if(file.isDirectory()){ 86 | populate(fs, file.name()); 87 | } else { 88 | char *temp = (char *)malloc(strlen(file.name()) + 1); 89 | strcpy(temp, file.name()); 90 | strncpy(extension, temp+(strlen(temp)-4), 4); 91 | extension[1] = toupper(extension[1]); 92 | extension[2] = toupper(extension[2]); 93 | extension[3] = toupper(extension[3]); 94 | if (strcmp(extension, ".MP3") == 0) 95 | fileList->add(temp); 96 | else 97 | delete temp; 98 | } 99 | file = root.openNextFile(); 100 | } 101 | } 102 | 103 | int PicoAudio::playFile() { 104 | if (fileList->size() == 0) 105 | return -1; 106 | 107 | connecttoSD(fileList->get(filePos)); 108 | return filePos; 109 | } 110 | 111 | int PicoAudio::playNext() { 112 | if (fileList->size() == 0) 113 | return -1; 114 | 115 | filePos++; 116 | if (filePos >= fileList->size()) { 117 | filePos = 0; 118 | } 119 | 120 | connecttoSD(fileList->get(filePos)); 121 | return filePos; 122 | } 123 | 124 | int PicoAudio::playPrev() { 125 | if (fileList->size() == 0) 126 | return -1; 127 | 128 | filePos--; 129 | if (filePos < 0) { 130 | filePos = fileList->size()-1; 131 | } 132 | 133 | connecttoSD(fileList->get(filePos)); 134 | return filePos; 135 | } 136 | 137 | bool PicoAudio::RegisterButtonCB(GPIOEvents::chngCBFn fn, void *data) { 138 | if (ioExpander == NULL) 139 | return false; 140 | 141 | ioExpander->RegisterChangeCB(fn, data); 142 | } 143 | 144 | uint8_t PicoAudio::readIO(uint8_t pin) { 145 | if (ioExpander == NULL) 146 | return -1; 147 | 148 | return ioExpander->read(pin); 149 | } 150 | 151 | void PicoAudio::writeIO(const uint8_t pin, const uint8_t value) { 152 | if (ioExpander == NULL) 153 | return; 154 | 155 | ioExpander->write(pin, value); 156 | } 157 | 158 | void PicoAudio::I2S_Init(i2s_mode_t MODE, i2s_bits_per_sample_t BPS, i2s_port_t PORT, i2s_channel_fmt_t FORMAT, int BCLK, int LRC, int DIN, int DOUT) { 159 | i2s_config_t i2s_config = { 160 | .mode = (i2s_mode_t)(I2S_MODE_MASTER | MODE), 161 | .sample_rate = SAMPLE_RATE, 162 | .bits_per_sample = BPS, 163 | .channel_format = FORMAT, 164 | .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), 165 | .intr_alloc_flags = 0, 166 | .dma_buf_count = 16, 167 | .dma_buf_len = 60 168 | }; 169 | i2s_pin_config_t pin_config = { 170 | .bck_io_num = BCLK, 171 | .ws_io_num = LRC, 172 | .data_out_num = DOUT, 173 | .data_in_num = DIN 174 | }; 175 | i2s_driver_install(PORT, &i2s_config, 0, NULL); 176 | i2s_set_pin(PORT, &pin_config); 177 | i2s_set_clk(PORT, SAMPLE_RATE, BPS, I2S_CHANNEL_STEREO); 178 | } 179 | 180 | void PicoAudio::initDAC() { 181 | I2S_Init(I2S_MODE_TX, I2S_BITS_PER_SAMPLE_32BIT, I2S_NUM_0, I2S_CHANNEL_FMT_RIGHT_LEFT, PA_DAC_BCLK, PA_DAC_LRC, PA_DAC_DIN, PA_DAC_DOUT); 182 | } 183 | 184 | void PicoAudio::initMIC() { 185 | I2S_Init(I2S_MODE_RX, I2S_BITS_PER_SAMPLE_32BIT, I2S_NUM_1, I2S_CHANNEL_FMT_RIGHT_LEFT, PA_MIC_BCLK, PA_MIC_LRC, PA_MIC_DIN, PA_MIC_DOUT); 186 | } 187 | 188 | int PicoAudio::readMIC(char* data, int numData) { 189 | return i2s_read_bytes(I2S_NUM_1, (char *)data, numData, portMAX_DELAY); 190 | } 191 | 192 | void PicoAudio::writeDAC(char* data, int numData) { 193 | i2s_write_bytes(I2S_NUM_0, (const char *)data, numData, portMAX_DELAY); 194 | } 195 | 196 | -------------------------------------------------------------------------------- /src/PicoAudio.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 AKsevenFour / Adam Keher (adam@keher.com.au) 3 | http://www.aksevenfour.com / https://github.com/AdamKeher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 7 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 13 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 14 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 15 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | #ifndef PICOAUDIO_H 19 | #define PICOAUDIO_H 20 | 21 | #include 22 | #include "GPIOExpander.h" 23 | #include "freertos/FreeRTOS.h" 24 | #include "freertos/task.h" 25 | #include "driver/i2s.h" 26 | #include "esp_system.h" 27 | #include "Audio.h" 28 | #include "LinkedList.h" 29 | #include "SD.h" 30 | #include "FS.h" 31 | 32 | #define SAMPLE_RATE (44100) 33 | 34 | #define PE_ADDR 0x20 // I2C Address 35 | #define PE_SDA 32 36 | #define PE_SCL 33 37 | 38 | #define PA_SDA 32 39 | #define PA_SCL 33 40 | 41 | #define PA_SPI_MOSI 23 42 | #define PA_SPI_MISO 19 43 | #define PA_SPI_SCK 18 44 | 45 | #define PA_DAC_BCLK 26 46 | #define PA_DAC_LRC 25 47 | #define PA_DAC_DIN I2S_PIN_NO_CHANGE 48 | #define PA_DAC_DOUT 22 49 | #define PA_MIC_BCLK 14 50 | #define PA_MIC_LRC 4 51 | #define PA_MIC_DIN 15 52 | #define PA_MIC_DOUT I2S_PIN_NO_CHANGE 53 | 54 | class PicoAudio 55 | { 56 | public: 57 | PicoAudio(bool initAudio = true); 58 | void begin(); 59 | void process(); 60 | void process(bool processAudio, bool processGPIO); 61 | 62 | GPIOExpander *getIoExpander() { return ioExpander; }; 63 | Audio *getAudio() { return audio; } 64 | 65 | bool connecttoHost(String sdfile); 66 | 67 | bool connecttoSD(String sdfile); 68 | bool pauseResume(); 69 | bool isPlaying(); 70 | void setVolume(uint8_t vol); // vol 22 steps, 0...21 71 | int getVolume() { return volumeStep; } 72 | int getVolumeRaw() { return audio->getVolume(); } 73 | int volumeUp() { 74 | if (volumeStep < 9) 75 | setVolume(volumeSteps[++volumeStep]); 76 | return volumeStep; 77 | } 78 | int volumeDown() { 79 | if (volumeStep != 0) 80 | setVolume(volumeSteps[--volumeStep]); 81 | return volumeStep; 82 | } 83 | void setVolumeMax() { 84 | setVolume(21); 85 | volumeStep = 9; 86 | } 87 | void mute() { 88 | setVolume(0); 89 | volumeStep = 0; 90 | } 91 | 92 | bool scanSD(); 93 | void populate(fs::FS &fs, const char *dirname); 94 | LinkedList* FileList() { return fileList; } 95 | int playFile(); 96 | int playNext(); 97 | int playPrev(); 98 | int getFileListPosition() { return filePos; } 99 | int getFileListSize() { return fileList->size(); } 100 | 101 | bool RegisterButtonCB(GPIOEvents::chngCBFn fn, void *data); 102 | uint8_t readIO(uint8_t pin); 103 | void writeIO(const uint8_t pin, const uint8_t value); 104 | 105 | void I2S_Init(i2s_mode_t MODE, i2s_bits_per_sample_t BPS, i2s_port_t PORT, i2s_channel_fmt_t FORMAT, int BCLK, int LRC, int DIN, int DOUT); 106 | void initDAC(); 107 | void initMIC(); 108 | int readMIC(char* data, int numData); 109 | void writeDAC(char* data, int numData); 110 | 111 | private: 112 | GPIOExpander *ioExpander; 113 | 114 | Audio *audio; 115 | uint8_t volumeStep = 0; 116 | uint8_t volumeSteps[10] {0, 1, 3, 5, 7, 10, 13, 16, 19, 21}; 117 | 118 | LinkedList *fileList; 119 | int filePos = 0; 120 | uint32_t mp3FilePos = -0; 121 | }; 122 | 123 | #endif // PICOAUDIO_H -------------------------------------------------------------------------------- /src/aac_decoder.h: -------------------------------------------------------------------------------- 1 | // based om helix aac decoder 2 | #pragma once 3 | #pragma GCC optimize ("O3") 4 | 5 | #pragma GCC optimize ("O3") 6 | 7 | #include "Arduino.h" 8 | 9 | #define ASSERT(x) /* do nothing */ 10 | 11 | #ifndef MAX 12 | #define MAX(a,b) ((a) > (b) ? (a) : (b)) 13 | #endif 14 | 15 | #ifndef MIN 16 | #define MIN(a,b) ((a) < (b) ? (a) : (b)) 17 | #endif 18 | 19 | 20 | /* AAC file format */ 21 | enum { 22 | AAC_FF_Unknown = 0, /* should be 0 on init */ 23 | AAC_FF_ADTS = 1, 24 | AAC_FF_ADIF = 2, 25 | AAC_FF_RAW = 3 26 | }; 27 | 28 | /* syntactic element type */ 29 | enum { 30 | AAC_ID_INVALID = -1, 31 | AAC_ID_SCE = 0, 32 | AAC_ID_CPE = 1, 33 | AAC_ID_CCE = 2, 34 | AAC_ID_LFE = 3, 35 | AAC_ID_DSE = 4, 36 | AAC_ID_PCE = 5, 37 | AAC_ID_FIL = 6, 38 | AAC_ID_END = 7 39 | }; 40 | 41 | enum { 42 | ERR_AAC_NONE = 0, 43 | ERR_AAC_INDATA_UNDERFLOW = -1, 44 | ERR_AAC_NULL_POINTER = -2, 45 | ERR_AAC_INVALID_ADTS_HEADER = -3, 46 | ERR_AAC_INVALID_ADIF_HEADER = -4, 47 | ERR_AAC_INVALID_FRAME = -5, 48 | ERR_AAC_MPEG4_UNSUPPORTED = -6, 49 | ERR_AAC_CHANNEL_MAP = -7, 50 | ERR_AAC_SYNTAX_ELEMENT = -8, 51 | ERR_AAC_DEQUANT = -9, 52 | ERR_AAC_STEREO_PROCESS = -10, 53 | ERR_AAC_PNS = -11, 54 | ERR_AAC_SHORT_BLOCK_DEINT = -12, 55 | ERR_AAC_TNS = -13, 56 | ERR_AAC_IMDCT = -14, 57 | ERR_AAC_NCHANS_TOO_HIGH = -15, 58 | ERR_AAC_RAWBLOCK_PARAMS = -22, 59 | ERR_AAC_UNKNOWN = -9999 60 | }; 61 | 62 | typedef struct _AACDecInfo_t { 63 | int fillExtType; 64 | int prevBlockID; /* block information */ 65 | int currBlockID; 66 | int currInstTag; 67 | int sbDeinterleaveReqd[2]; // [MAX_NCHANS_ELEM] 68 | int adtsBlocksLeft; 69 | int bitRate; /* user-accessible info */ 70 | int nChans; 71 | int sampRate; 72 | int profile; 73 | int format; 74 | int sbrEnabled; 75 | int tnsUsed; 76 | int pnsUsed; 77 | int frameCount; 78 | } AACDecInfo_t; 79 | 80 | 81 | typedef struct _aac_BitStreamInfo_t { 82 | uint8_t *bytePtr; 83 | uint32_t iCache; 84 | int cachedBits; 85 | int nBytes; 86 | } aac_BitStreamInfo_t; 87 | 88 | typedef union _U64 { 89 | int64_t w64; 90 | struct { 91 | uint32_t lo32; 92 | int32_t hi32; 93 | } r; 94 | } U64; 95 | 96 | typedef struct _AACFrameInfo_t { 97 | int bitRate; 98 | int nChans; 99 | int sampRateCore; 100 | int sampRateOut; 101 | int bitsPerSample; 102 | int outputSamps; 103 | int profile; 104 | int tnsUsed; 105 | int pnsUsed; 106 | } AACFrameInfo_t; 107 | 108 | typedef struct _HuffInfo_t { 109 | int maxBits; /* number of bits in longest codeword */ 110 | uint8_t count[20]; /* count[MAX_HUFF_BITS] = number of codes with length i+1 bits */ 111 | int offset; /* offset into symbol table */ 112 | } HuffInfo_t; 113 | 114 | typedef struct _PulseInfo_t { 115 | uint8_t pulseDataPresent; 116 | uint8_t numPulse; 117 | uint8_t startSFB; 118 | uint8_t offset[4]; // [MAX_PULSES] 119 | uint8_t amp[4]; // [MAX_PULSES] 120 | } PulseInfo_t; 121 | 122 | typedef struct _TNSInfo_t { 123 | uint8_t tnsDataPresent; 124 | uint8_t numFilt[8]; // [MAX_TNS_FILTERS] max 1 filter each for 8 short windows, or 3 filters for 1 long window 125 | uint8_t coefRes[8]; // [MAX_TNS_FILTERS] 126 | uint8_t length[8]; // [MAX_TNS_FILTERS] 127 | uint8_t order[8]; // [MAX_TNS_FILTERS] 128 | uint8_t dir[8]; // [MAX_TNS_FILTERS] 129 | int8_t coef[60]; // [MAX_TNS_COEFS] max 3 filters * 20 coefs for 1 long window, or 1 filter * 7 coefs for each of 8 short windows 130 | } TNSInfo_t; 131 | 132 | typedef struct _GainControlInfo_t { 133 | uint8_t gainControlDataPresent; 134 | uint8_t maxBand; 135 | uint8_t adjNum[3][8]; // [MAX_GAIN_BANDS][MAX_GAIN_WIN] 136 | uint8_t alevCode[3][8][7]; // [MAX_GAIN_BANDS][MAX_GAIN_WIN][MAX_GAIN_ADJUST] 137 | uint8_t alocCode[3][8][7]; // [MAX_GAIN_BANDS][MAX_GAIN_WIN][MAX_GAIN_ADJUST] 138 | } GainControlInfo_t; 139 | 140 | typedef struct _ICSInfo_t { 141 | uint8_t icsResBit; 142 | uint8_t winSequence; 143 | uint8_t winShape; 144 | uint8_t maxSFB; 145 | uint8_t sfGroup; 146 | uint8_t predictorDataPresent; 147 | uint8_t predictorReset; 148 | uint8_t predictorResetGroupNum; 149 | uint8_t predictionUsed[41]; // [MAX_PRED_SFB] 150 | uint8_t numWinGroup; 151 | uint8_t winGroupLen[8]; // [MAX_WIN_GROUPS] 152 | } ICSInfo_t; 153 | 154 | typedef struct _ADTSHeader_t { 155 | /* fixed */ 156 | uint8_t id; /* MPEG bit - should be 1 */ 157 | uint8_t layer; /* MPEG layer - should be 0 */ 158 | uint8_t protectBit; /* 0 = CRC word follows, 1 = no CRC word */ 159 | uint8_t profile; /* 0 = main, 1 = LC, 2 = SSR, 3 = reserved */ 160 | uint8_t sampRateIdx; /* sample rate index range = [0, 11] */ 161 | uint8_t privateBit; /* ignore */ 162 | uint8_t channelConfig; /* 0 = implicit, >0 = use default table */ 163 | uint8_t origCopy; /* 0 = copy, 1 = original */ 164 | uint8_t home; /* ignore */ 165 | /* variable */ 166 | uint8_t copyBit; /* 1 bit of the 72-bit copyright ID (transmitted as 1 bit per frame) */ 167 | uint8_t copyStart; /* 1 = this bit starts the 72-bit ID, 0 = it does not */ 168 | int frameLength; /* length of frame */ 169 | int bufferFull; /* number of 32-bit words left in enc buffer, 0x7FF = VBR */ 170 | uint8_t numRawDataBlocks; /* number of raw data blocks in frame */ 171 | /* CRC */ 172 | int crcCheckWord; /* 16-bit CRC check word (present if protectBit == 0) */ 173 | } ADTSHeader_t; 174 | 175 | typedef struct _ADIFHeader_t { 176 | uint8_t copyBit; /* 0 = no copyright ID, 1 = 72-bit copyright ID follows immediately */ 177 | uint8_t origCopy; /* 0 = copy, 1 = original */ 178 | uint8_t home; /* ignore */ 179 | uint8_t bsType; /* bitstream type: 0 = CBR, 1 = VBR */ 180 | int bitRate; /* bitRate: CBR = bits/sec, VBR = peak bits/frame, 0 = unknown */ 181 | uint8_t numPCE; /* number of program config elements (max = 16) */ 182 | int bufferFull; /* bits left in bit reservoir */ 183 | uint8_t copyID[9]; /* [ADIF_COPYID_SIZE] optional 72-bit copyright ID */ 184 | } ADIFHeader_t; 185 | 186 | /* sizeof(ProgConfigElement_t) = 82 bytes (if KEEP_PCE_COMMENTS not defined) */ 187 | typedef struct _ProgConfigElement_t { 188 | uint8_t elemInstTag; /* element instance tag */ 189 | uint8_t profile; /* 0 = main, 1 = LC, 2 = SSR, 3 = reserved */ 190 | uint8_t sampRateIdx; /* sample rate index range = [0, 11] */ 191 | uint8_t numFCE; /* number of front channel elements (max = 15) */ 192 | uint8_t numSCE; /* number of side channel elements (max = 15) */ 193 | uint8_t numBCE; /* number of back channel elements (max = 15) */ 194 | uint8_t numLCE; /* number of LFE channel elements (max = 3) */ 195 | uint8_t numADE; /* number of associated data elements (max = 7) */ 196 | uint8_t numCCE; /* number of valid channel coupling elements (max = 15) */ 197 | uint8_t monoMixdown; /* mono mixdown: bit 4 = present flag, bits 3-0 = element number */ 198 | uint8_t stereoMixdown; /* stereo mixdown: bit 4 = present flag, bits 3-0 = element number */ 199 | uint8_t matrixMixdown; /* matrix mixdown: bit 4 = present flag, bit 3 = unused,bits 2-1 = index, bit 0 = pseudo-surround enable */ 200 | uint8_t fce[15]; /* [MAX_NUM_FCE] front element channel pair: bit 4 = SCE/CPE flag, bits 3-0 = inst tag */ 201 | uint8_t sce[15]; /* [MAX_NUM_SCE] side element channel pair: bit 4 = SCE/CPE flag, bits 3-0 = inst tag */ 202 | uint8_t bce[15]; /* [MAX_NUM_BCE] back element channel pair: bit 4 = SCE/CPE flag, bits 3-0 = inst tag */ 203 | uint8_t lce[3]; /* [MAX_NUM_LCE] instance tag for LFE elements */ 204 | uint8_t ade[7]; /* [MAX_NUM_ADE] instance tag for ADE elements */ 205 | uint8_t cce[15]; /* [MAX_NUM_BCE] channel coupling elements: bit 4 = switching flag, bits 3-0 = inst tag */ 206 | } ProgConfigElement_t; 207 | 208 | /* state info struct for baseline (MPEG-4 LC) decoding */ 209 | typedef struct _PSInfoBase_t { 210 | int dataCount; 211 | uint8_t dataBuf[510]; // [DATA_BUF_SIZE] 212 | /* state information which is the same throughout whole frame */ 213 | int nChans; 214 | int useImpChanMap; 215 | int sampRateIdx; 216 | /* state information which can be overwritten by subsequent elements within frame */ 217 | ICSInfo_t icsInfo[2]; // [MAX_NCHANS_ELEM] 218 | int commonWin; 219 | short scaleFactors[2][15*8]; // [MAX_NCHANS_ELEM][MAX_SF_BANDS] 220 | uint8_t sfbCodeBook[2][15*8]; // [MAX_NCHANS_ELEM][MAX_SF_BANDS] 221 | int msMaskPresent; 222 | uint8_t msMaskBits[(15 * 8 + 7) >> 3]; // [MAX_MS_MASK_BYTES] 223 | int pnsUsed[2]; // [MAX_NCHANS_ELEM] 224 | int pnsLastVal; 225 | int intensityUsed[2]; // [MAX_NCHANS_ELEM] 226 | // PulseInfo_t pulseInfo[2]; // [MAX_NCHANS_ELEM] 227 | TNSInfo_t tnsInfo[2]; // [MAX_NCHANS_ELEM] 228 | int tnsLPCBuf[20]; // [MAX_TNS_ORDER] 229 | int tnsWorkBuf[20]; //[MAX_TNS_ORDER] 230 | GainControlInfo_t gainControlInfo[2]; // [MAX_NCHANS_ELEM] 231 | int gbCurrent[2]; // [MAX_NCHANS_ELEM] 232 | int coef[2][1024]; // [MAX_NCHANS_ELEM][AAC_MAX_NSAMPS] 233 | /* state information which must be saved for each element and used in next frame */ 234 | int overlap[2][1024]; // [AAC_MAX_NCHANS][AAC_MAX_NSAMPS] 235 | int prevWinShape[2]; // [AAC_MAX_NCHANS] 236 | } PSInfoBase_t; 237 | 238 | 239 | int AACFindSyncWord(uint8_t *buf, int nBytes); 240 | void AACGetLastFrameInfo(AACFrameInfo_t *aacFrameInfo); 241 | int AACDecode(uint8_t *inbuf, int *bytesLeft, short *outbuf); 242 | int AACGetSampRate(); 243 | int AACGetChannels(); 244 | int AACGetBitsPerSample(); 245 | int AACGetBitrate(); 246 | int AACGetOutputSamps(); 247 | int AACGetBitrate(); 248 | void DecodeLPCCoefs(int order, int res, int8_t *filtCoef, int *a, int *b); 249 | int FilterRegion(int size, int dir, int order, int *audioCoef, int *a, int *hist); 250 | int TNSFilter(int ch); 251 | int DecodeSingleChannelElement(); 252 | int DecodeChannelPairElement(); 253 | int DecodeLFEChannelElement(); 254 | int DecodeDataStreamElement(); 255 | int DecodeProgramConfigElement(uint8_t idx); 256 | int DecodeFillElement(); 257 | int DecodeNextElement(uint8_t **buf, int *bitOffset, int *bitsAvail); 258 | void PreMultiply(int tabidx, int *zbuf1); 259 | void PostMultiply(int tabidx, int *fft1); 260 | void PreMultiplyRescale(int tabidx, int *zbuf1, int es); 261 | void PostMultiplyRescale(int tabidx, int *fft1, int es); 262 | void DCT4(int tabidx, int *coef, int gb); 263 | void BitReverse(int *inout, int tabidx); 264 | void R4FirstPass(int *x, int bg); 265 | void R8FirstPass(int *x, int bg); 266 | void R4Core(int *x, int bg, int gp, int *wtab); 267 | void R4FFT(int tabidx, int *x); 268 | void UnpackZeros(int nVals, int *coef); 269 | void UnpackQuads(int cb, int nVals, int *coef); 270 | void UnpackPairsNoEsc(int cb, int nVals, int *coef); 271 | void UnpackPairsEsc(int cb, int nVals, int *coef); 272 | void DecodeSpectrumLong(int ch); 273 | void DecodeSpectrumShort(int ch); 274 | void DecWindowOverlap(int *buf0, int *over0, short *pcm0, int nChans, int winTypeCurr, int winTypePrev); 275 | void DecWindowOverlapLongStart(int *buf0, int *over0, short *pcm0, int nChans, int winTypeCurr, int winTypePrev); 276 | void DecWindowOverlapLongStop(int *buf0, int *over0, short *pcm0, int nChans, int winTypeCurr, int winTypePrev); 277 | void DecWindowOverlapShort(int *buf0, int *over0, short *pcm0, int nChans, int winTypeCurr, int winTypePrev); 278 | int IMDCT(int ch, int chOut, short *outbuf); 279 | void DecodeICSInfo(ICSInfo_t *icsInfo, int sampRateIdx); 280 | void DecodeSectionData(int winSequence, int numWinGrp, int maxSFB, uint8_t *sfbCodeBook); 281 | int DecodeOneScaleFactor(); 282 | void DecodeScaleFactors(int numWinGrp, int maxSFB, int globalGain, uint8_t *sfbCodeBook, short *scaleFactors); 283 | void DecodePulseInfo(uint8_t ch); 284 | void DecodeTNSInfo(int winSequence, TNSInfo_t *ti, int8_t *tnsCoef); 285 | void DecodeGainControlInfo(int winSequence, GainControlInfo_t *gi); 286 | void DecodeICS(int ch); 287 | int DecodeNoiselessData(uint8_t **buf, int *bitOffset, int *bitsAvail, int ch); 288 | int DecodeHuffmanScalar(const signed short *huffTab, const HuffInfo_t *huffTabInfo, uint32_t bitBuf, int32_t *val); 289 | int UnpackADTSHeader(uint8_t **buf, int *bitOffset, int *bitsAvail); 290 | int GetADTSChannelMapping(uint8_t *buf, int bitOffset, int bitsAvail); 291 | int GetNumChannelsADIF(int nPCE); 292 | int GetSampleRateIdxADIF(int nPCE); 293 | int UnpackADIFHeader(uint8_t **buf, int *bitOffset, int *bitsAvail); 294 | int SetRawBlockParams(int copyLast, int nChans, int sampRate, int profile); 295 | int PrepareRawBlock(); 296 | int DequantBlock(int *inbuf, int nSamps, int scale); 297 | int AACDequantize(int ch); 298 | int DeinterleaveShortBlocks(int ch); 299 | uint32_t Get32BitVal(uint32_t *last); 300 | int InvRootR(int r); 301 | int ScaleNoiseVector(int *coef, int nVals, int sf); 302 | void GenerateNoiseVector(int *coef, int *last, int nVals); 303 | void CopyNoiseVector(int *coefL, int *coefR, int nVals); 304 | int PNS(int ch); 305 | int GetSampRateIdx(int sampRate); 306 | void StereoProcessGroup(int *coefL, int *coefR, const uint16_t *sfbTab, int msMaskPres, uint8_t *msMaskPtr, 307 | int msMaskOffset, int maxSFB, uint8_t *cbRight, short *sfRight, int *gbCurrent); 308 | int StereoProcess(); 309 | int RatioPowInv(int a, int b, int c); 310 | int SqrtFix(int q, int fBitsIn, int *fBitsOut); 311 | int InvRNormalized(int r); 312 | void BitReverse32(int *inout); 313 | void R8FirstPass32(int *r0); 314 | void R4Core32(int *r0); 315 | void FFT32C(int *x); 316 | void CVKernel1(int *XBuf, int *accBuf); 317 | void CVKernel2(int *XBuf, int *accBuf); 318 | void SetBitstreamPointer(int nBytes, uint8_t *buf); 319 | inline void RefillBitstreamCache(); 320 | uint32_t GetBits(int nBits); 321 | uint32_t GetBitsNoAdvance(int nBits); 322 | void AdvanceBitstream(int nBits); 323 | int CalcBitsUsed(uint8_t *startBuf, int startOffset); 324 | void ByteAlignBitstream(); 325 | 326 | -------------------------------------------------------------------------------- /src/ak74_logo.h: -------------------------------------------------------------------------------- 1 | #define LOGOWIDTH 91 2 | #define LOGOHEIGHT 48 3 | 4 | /*Pixel format: Red: 5 bit, Green: 6 bit, Blue: 5 bit*/ 5 | const uint16_t logo[4368] PROGMEM = { 6 | 0x1f, 0x1f, 0x1f, 0x1f, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1f, 0x1f, 0x1f, 0x1f, 7 | 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 8 | 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 9 | 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 10 | 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 11 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 12 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 13 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 14 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x02, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x05, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 15 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x02, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x05, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 16 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x02, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 17 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x02, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 18 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x02, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 19 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x02, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 20 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x02, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 21 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x02, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 22 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x02, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 23 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x02, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 24 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x02, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 25 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x02, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 26 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x02, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 27 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 28 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 29 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 30 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 31 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 32 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x1f, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x1f, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x1f, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 33 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 34 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 35 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 36 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 37 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 38 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 39 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 40 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 41 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 42 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x05, 0x00, 0x1f, 0x1f, 0x01, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x05, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 43 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x03, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x05, 0x02, 0x1f, 0x1f, 0x00, 0x06, 0x1f, 0x1f, 0x02, 0x1f, 0x06, 0x00, 0x1f, 0x1f, 0x00, 0x1f, 0x1f, 0x02, 0x00, 0x1f, 0x1f, 0x00, 0x04, 0x1f, 0x02, 0x00, 0x1f, 0x1f, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x1f, 0x1f, 0x03, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 44 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x05, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x04, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x1f, 0x1f, 0x00, 0x1f, 0x1f, 0x00, 0x03, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x04, 0x1f, 0x00, 0x00, 0x03, 0x1f, 0x04, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 45 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x1f, 0x1f, 0x00, 0x1f, 0x1f, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x04, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x01, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 46 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x01, 0x00, 0x00, 0x00, 0x01, 0x1f, 0x1f, 0x1f, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x01, 0x00, 0x05, 0x1f, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x04, 0x1f, 0x00, 0x00, 0x04, 0x1f, 0x03, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 47 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x03, 0x05, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x05, 0x00, 0x1f, 0x1f, 0x00, 0x1f, 0x1f, 0x01, 0x04, 0x1f, 0x1f, 0x00, 0x1f, 0x1f, 0x1f, 0x03, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x04, 0x1f, 0x05, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x04, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x02, 0x1f, 0x1f, 0x01, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 48 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x05, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x04, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x04, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x01, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 49 | 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 50 | 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 51 | 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 52 | 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 53 | 0x1f, 0x1f, 0x1f, 0x1f, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1f, 0x1f, 0x1f, 0x1f}; 54 | -------------------------------------------------------------------------------- /src/arduinoFFT.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | FFT libray 4 | Copyright (C) 2010 Didier Longueville 5 | Copyright (C) 2014 Enrique Condes 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | 20 | */ 21 | 22 | #include "arduinoFFT.h" 23 | 24 | arduinoFFT::arduinoFFT(void) 25 | { // Constructor 26 | #warning("This method is deprecated and will be removed on future revisions.") 27 | } 28 | 29 | arduinoFFT::arduinoFFT(double *vReal, double *vImag, uint16_t samples, double samplingFrequency) 30 | {// Constructor 31 | this->_vReal = vReal; 32 | this->_vImag = vImag; 33 | this->_samples = samples; 34 | this->_samplingFrequency = samplingFrequency; 35 | this->_power = Exponent(samples); 36 | } 37 | 38 | arduinoFFT::~arduinoFFT(void) 39 | { 40 | // Destructor 41 | } 42 | 43 | uint8_t arduinoFFT::Revision(void) 44 | { 45 | return(FFT_LIB_REV); 46 | } 47 | 48 | void arduinoFFT::Compute(double *vReal, double *vImag, uint16_t samples, uint8_t dir) 49 | { 50 | #warning("This method is deprecated and will be removed on future revisions.") 51 | Compute(vReal, vImag, samples, Exponent(samples), dir); 52 | } 53 | 54 | void arduinoFFT::Compute(uint8_t dir) 55 | {// Computes in-place complex-to-complex FFT / 56 | // Reverse bits / 57 | uint16_t j = 0; 58 | for (uint16_t i = 0; i < (this->_samples - 1); i++) { 59 | if (i < j) { 60 | Swap(&this->_vReal[i], &this->_vReal[j]); 61 | if(dir==FFT_REVERSE) 62 | Swap(&this->_vImag[i], &this->_vImag[j]); 63 | } 64 | uint16_t k = (this->_samples >> 1); 65 | while (k <= j) { 66 | j -= k; 67 | k >>= 1; 68 | } 69 | j += k; 70 | } 71 | // Compute the FFT / 72 | double c1 = -1.0; 73 | double c2 = 0.0; 74 | uint16_t l2 = 1; 75 | for (uint8_t l = 0; (l < this->_power); l++) { 76 | uint16_t l1 = l2; 77 | l2 <<= 1; 78 | double u1 = 1.0; 79 | double u2 = 0.0; 80 | for (j = 0; j < l1; j++) { 81 | for (uint16_t i = j; i < this->_samples; i += l2) { 82 | uint16_t i1 = i + l1; 83 | double t1 = u1 * this->_vReal[i1] - u2 * this->_vImag[i1]; 84 | double t2 = u1 * this->_vImag[i1] + u2 * this->_vReal[i1]; 85 | this->_vReal[i1] = this->_vReal[i] - t1; 86 | this->_vImag[i1] = this->_vImag[i] - t2; 87 | this->_vReal[i] += t1; 88 | this->_vImag[i] += t2; 89 | } 90 | double z = ((u1 * c1) - (u2 * c2)); 91 | u2 = ((u1 * c2) + (u2 * c1)); 92 | u1 = z; 93 | } 94 | c2 = sqrt((1.0 - c1) / 2.0); 95 | if (dir == FFT_FORWARD) { 96 | c2 = -c2; 97 | } 98 | c1 = sqrt((1.0 + c1) / 2.0); 99 | } 100 | // Scaling for reverse transform / 101 | if (dir != FFT_FORWARD) { 102 | for (uint16_t i = 0; i < this->_samples; i++) { 103 | this->_vReal[i] /= this->_samples; 104 | this->_vImag[i] /= this->_samples; 105 | } 106 | } 107 | } 108 | 109 | void arduinoFFT::Compute(double *vReal, double *vImag, uint16_t samples, uint8_t power, uint8_t dir) 110 | { // Computes in-place complex-to-complex FFT 111 | // Reverse bits 112 | #warning("This method is deprecated and will be removed on future revisions.") 113 | uint16_t j = 0; 114 | for (uint16_t i = 0; i < (samples - 1); i++) { 115 | if (i < j) { 116 | Swap(&vReal[i], &vReal[j]); 117 | if(dir==FFT_REVERSE) 118 | Swap(&vImag[i], &vImag[j]); 119 | } 120 | uint16_t k = (samples >> 1); 121 | while (k <= j) { 122 | j -= k; 123 | k >>= 1; 124 | } 125 | j += k; 126 | } 127 | // Compute the FFT 128 | double c1 = -1.0; 129 | double c2 = 0.0; 130 | uint16_t l2 = 1; 131 | for (uint8_t l = 0; (l < power); l++) { 132 | uint16_t l1 = l2; 133 | l2 <<= 1; 134 | double u1 = 1.0; 135 | double u2 = 0.0; 136 | for (j = 0; j < l1; j++) { 137 | for (uint16_t i = j; i < samples; i += l2) { 138 | uint16_t i1 = i + l1; 139 | double t1 = u1 * vReal[i1] - u2 * vImag[i1]; 140 | double t2 = u1 * vImag[i1] + u2 * vReal[i1]; 141 | vReal[i1] = vReal[i] - t1; 142 | vImag[i1] = vImag[i] - t2; 143 | vReal[i] += t1; 144 | vImag[i] += t2; 145 | } 146 | double z = ((u1 * c1) - (u2 * c2)); 147 | u2 = ((u1 * c2) + (u2 * c1)); 148 | u1 = z; 149 | } 150 | c2 = sqrt((1.0 - c1) / 2.0); 151 | if (dir == FFT_FORWARD) { 152 | c2 = -c2; 153 | } 154 | c1 = sqrt((1.0 + c1) / 2.0); 155 | } 156 | // Scaling for reverse transform 157 | if (dir != FFT_FORWARD) { 158 | for (uint16_t i = 0; i < samples; i++) { 159 | vReal[i] /= samples; 160 | vImag[i] /= samples; 161 | } 162 | } 163 | } 164 | 165 | void arduinoFFT::ComplexToMagnitude() 166 | { // vM is half the size of vReal and vImag 167 | for (uint16_t i = 0; i < this->_samples; i++) { 168 | this->_vReal[i] = sqrt(sq(this->_vReal[i]) + sq(this->_vImag[i])); 169 | } 170 | } 171 | 172 | void arduinoFFT::ComplexToMagnitude(double *vReal, double *vImag, uint16_t samples) 173 | { // vM is half the size of vReal and vImag 174 | #warning("This method is deprecated and will be removed on future revisions.") 175 | for (uint16_t i = 0; i < samples; i++) { 176 | vReal[i] = sqrt(sq(vReal[i]) + sq(vImag[i])); 177 | } 178 | } 179 | 180 | void arduinoFFT::DCRemoval() 181 | { 182 | // calculate the mean of vData 183 | double mean = 0; 184 | for (uint16_t i = 1; i < ((this->_samples >> 1) + 1); i++) 185 | { 186 | mean += this->_vReal[i]; 187 | } 188 | mean /= this->_samples; 189 | // Subtract the mean from vData 190 | for (uint16_t i = 1; i < ((this->_samples >> 1) + 1); i++) 191 | { 192 | this->_vReal[i] -= mean; 193 | } 194 | } 195 | 196 | void arduinoFFT::DCRemoval(double *vData, uint16_t samples) 197 | { 198 | // calculate the mean of vData 199 | #warning("This method is deprecated and will be removed on future revisions.") 200 | double mean = 0; 201 | for (uint16_t i = 1; i < ((samples >> 1) + 1); i++) 202 | { 203 | mean += vData[i]; 204 | } 205 | mean /= samples; 206 | // Subtract the mean from vData 207 | for (uint16_t i = 1; i < ((samples >> 1) + 1); i++) 208 | { 209 | vData[i] -= mean; 210 | } 211 | } 212 | 213 | void arduinoFFT::Windowing(uint8_t windowType, uint8_t dir) 214 | {// Weighing factors are computed once before multiple use of FFT 215 | // The weighing function is symetric; half the weighs are recorded 216 | double samplesMinusOne = (double(this->_samples) - 1.0); 217 | for (uint16_t i = 0; i < (this->_samples >> 1); i++) { 218 | double indexMinusOne = double(i); 219 | double ratio = (indexMinusOne / samplesMinusOne); 220 | double weighingFactor = 1.0; 221 | // Compute and record weighting factor 222 | switch (windowType) { 223 | case FFT_WIN_TYP_RECTANGLE: // rectangle (box car) 224 | weighingFactor = 1.0; 225 | break; 226 | case FFT_WIN_TYP_HAMMING: // hamming 227 | weighingFactor = 0.54 - (0.46 * cos(twoPi * ratio)); 228 | break; 229 | case FFT_WIN_TYP_HANN: // hann 230 | weighingFactor = 0.54 * (1.0 - cos(twoPi * ratio)); 231 | break; 232 | case FFT_WIN_TYP_TRIANGLE: // triangle (Bartlett) 233 | weighingFactor = 1.0 - ((2.0 * abs(indexMinusOne - (samplesMinusOne / 2.0))) / samplesMinusOne); 234 | break; 235 | case FFT_WIN_TYP_NUTTALL: // nuttall 236 | weighingFactor = 0.355768 - (0.487396 * (cos(twoPi * ratio))) + (0.144232 * (cos(fourPi * ratio))) - (0.012604 * (cos(sixPi * ratio))); 237 | break; 238 | case FFT_WIN_TYP_BLACKMAN: // blackman 239 | weighingFactor = 0.42323 - (0.49755 * (cos(twoPi * ratio))) + (0.07922 * (cos(fourPi * ratio))); 240 | break; 241 | case FFT_WIN_TYP_BLACKMAN_NUTTALL: // blackman nuttall 242 | weighingFactor = 0.3635819 - (0.4891775 * (cos(twoPi * ratio))) + (0.1365995 * (cos(fourPi * ratio))) - (0.0106411 * (cos(sixPi * ratio))); 243 | break; 244 | case FFT_WIN_TYP_BLACKMAN_HARRIS: // blackman harris 245 | weighingFactor = 0.35875 - (0.48829 * (cos(twoPi * ratio))) + (0.14128 * (cos(fourPi * ratio))) - (0.01168 * (cos(sixPi * ratio))); 246 | break; 247 | case FFT_WIN_TYP_FLT_TOP: // flat top 248 | weighingFactor = 0.2810639 - (0.5208972 * cos(twoPi * ratio)) + (0.1980399 * cos(fourPi * ratio)); 249 | break; 250 | case FFT_WIN_TYP_WELCH: // welch 251 | weighingFactor = 1.0 - sq((indexMinusOne - samplesMinusOne / 2.0) / (samplesMinusOne / 2.0)); 252 | break; 253 | } 254 | if (dir == FFT_FORWARD) { 255 | this->_vReal[i] *= weighingFactor; 256 | this->_vReal[this->_samples - (i + 1)] *= weighingFactor; 257 | } 258 | else { 259 | this->_vReal[i] /= weighingFactor; 260 | this->_vReal[this->_samples - (i + 1)] /= weighingFactor; 261 | } 262 | } 263 | } 264 | 265 | 266 | void arduinoFFT::Windowing(double *vData, uint16_t samples, uint8_t windowType, uint8_t dir) 267 | {// Weighing factors are computed once before multiple use of FFT 268 | // The weighing function is symetric; half the weighs are recorded 269 | #warning("This method is deprecated and will be removed on future revisions.") 270 | double samplesMinusOne = (double(samples) - 1.0); 271 | for (uint16_t i = 0; i < (samples >> 1); i++) { 272 | double indexMinusOne = double(i); 273 | double ratio = (indexMinusOne / samplesMinusOne); 274 | double weighingFactor = 1.0; 275 | // Compute and record weighting factor 276 | switch (windowType) { 277 | case FFT_WIN_TYP_RECTANGLE: // rectangle (box car) 278 | weighingFactor = 1.0; 279 | break; 280 | case FFT_WIN_TYP_HAMMING: // hamming 281 | weighingFactor = 0.54 - (0.46 * cos(twoPi * ratio)); 282 | break; 283 | case FFT_WIN_TYP_HANN: // hann 284 | weighingFactor = 0.54 * (1.0 - cos(twoPi * ratio)); 285 | break; 286 | case FFT_WIN_TYP_TRIANGLE: // triangle (Bartlett) 287 | weighingFactor = 1.0 - ((2.0 * abs(indexMinusOne - (samplesMinusOne / 2.0))) / samplesMinusOne); 288 | break; 289 | case FFT_WIN_TYP_NUTTALL: // nuttall 290 | weighingFactor = 0.355768 - (0.487396 * (cos(twoPi * ratio))) + (0.144232 * (cos(fourPi * ratio))) - (0.012604 * (cos(sixPi * ratio))); 291 | break; 292 | case FFT_WIN_TYP_BLACKMAN: // blackman 293 | weighingFactor = 0.42323 - (0.49755 * (cos(twoPi * ratio))) + (0.07922 * (cos(fourPi * ratio))); 294 | break; 295 | case FFT_WIN_TYP_BLACKMAN_NUTTALL: // blackman nuttall 296 | weighingFactor = 0.3635819 - (0.4891775 * (cos(twoPi * ratio))) + (0.1365995 * (cos(fourPi * ratio))) - (0.0106411 * (cos(sixPi * ratio))); 297 | break; 298 | case FFT_WIN_TYP_BLACKMAN_HARRIS: // blackman harris 299 | weighingFactor = 0.35875 - (0.48829 * (cos(twoPi * ratio))) + (0.14128 * (cos(fourPi * ratio))) - (0.01168 * (cos(sixPi * ratio))); 300 | break; 301 | case FFT_WIN_TYP_FLT_TOP: // flat top 302 | weighingFactor = 0.2810639 - (0.5208972 * cos(twoPi * ratio)) + (0.1980399 * cos(fourPi * ratio)); 303 | break; 304 | case FFT_WIN_TYP_WELCH: // welch 305 | weighingFactor = 1.0 - sq((indexMinusOne - samplesMinusOne / 2.0) / (samplesMinusOne / 2.0)); 306 | break; 307 | } 308 | if (dir == FFT_FORWARD) { 309 | vData[i] *= weighingFactor; 310 | vData[samples - (i + 1)] *= weighingFactor; 311 | } 312 | else { 313 | vData[i] /= weighingFactor; 314 | vData[samples - (i + 1)] /= weighingFactor; 315 | } 316 | } 317 | } 318 | 319 | double arduinoFFT::MajorPeak() 320 | { 321 | double maxY = 0; 322 | uint16_t IndexOfMaxY = 0; 323 | //If sampling_frequency = 2 * max_frequency in signal, 324 | //value would be stored at position samples/2 325 | for (uint16_t i = 1; i < ((this->_samples >> 1) + 1); i++) { 326 | if ((this->_vReal[i-1] < this->_vReal[i]) && (this->_vReal[i] > this->_vReal[i+1])) { 327 | if (this->_vReal[i] > maxY) { 328 | maxY = this->_vReal[i]; 329 | IndexOfMaxY = i; 330 | } 331 | } 332 | } 333 | double delta = 0.5 * ((this->_vReal[IndexOfMaxY-1] - this->_vReal[IndexOfMaxY+1]) / (this->_vReal[IndexOfMaxY-1] - (2.0 * this->_vReal[IndexOfMaxY]) + this->_vReal[IndexOfMaxY+1])); 334 | double interpolatedX = ((IndexOfMaxY + delta) * this->_samplingFrequency) / (this->_samples-1); 335 | if(IndexOfMaxY==(this->_samples >> 1)) //To improve calculation on edge values 336 | interpolatedX = ((IndexOfMaxY + delta) * this->_samplingFrequency) / (this->_samples); 337 | // returned value: interpolated frequency peak apex 338 | return(interpolatedX); 339 | } 340 | 341 | void arduinoFFT::MajorPeak(double *f, double *v) 342 | { 343 | double maxY = 0; 344 | uint16_t IndexOfMaxY = 0; 345 | //If sampling_frequency = 2 * max_frequency in signal, 346 | //value would be stored at position samples/2 347 | for (uint16_t i = 1; i < ((this->_samples >> 1) + 1); i++) { 348 | if ((this->_vReal[i - 1] < this->_vReal[i]) && (this->_vReal[i] > this->_vReal[i + 1])) { 349 | if (this->_vReal[i] > maxY) { 350 | maxY = this->_vReal[i]; 351 | IndexOfMaxY = i; 352 | } 353 | } 354 | } 355 | double delta = 0.5 * ((this->_vReal[IndexOfMaxY - 1] - this->_vReal[IndexOfMaxY + 1]) / (this->_vReal[IndexOfMaxY - 1] - (2.0 * this->_vReal[IndexOfMaxY]) + this->_vReal[IndexOfMaxY + 1])); 356 | double interpolatedX = ((IndexOfMaxY + delta) * this->_samplingFrequency) / (this->_samples - 1); 357 | if (IndexOfMaxY == (this->_samples >> 1)) //To improve calculation on edge values 358 | interpolatedX = ((IndexOfMaxY + delta) * this->_samplingFrequency) / (this->_samples); 359 | // returned value: interpolated frequency peak apex 360 | *f = interpolatedX; 361 | *v = abs(this->_vReal[IndexOfMaxY - 1] - (2.0 * this->_vReal[IndexOfMaxY]) + this->_vReal[IndexOfMaxY + 1]); 362 | } 363 | 364 | double arduinoFFT::MajorPeak(double *vD, uint16_t samples, double samplingFrequency) 365 | { 366 | #warning("This method is deprecated and will be removed on future revisions.") 367 | double maxY = 0; 368 | uint16_t IndexOfMaxY = 0; 369 | //If sampling_frequency = 2 * max_frequency in signal, 370 | //value would be stored at position samples/2 371 | for (uint16_t i = 1; i < ((samples >> 1) + 1); i++) { 372 | if ((vD[i-1] < vD[i]) && (vD[i] > vD[i+1])) { 373 | if (vD[i] > maxY) { 374 | maxY = vD[i]; 375 | IndexOfMaxY = i; 376 | } 377 | } 378 | } 379 | double delta = 0.5 * ((vD[IndexOfMaxY-1] - vD[IndexOfMaxY+1]) / (vD[IndexOfMaxY-1] - (2.0 * vD[IndexOfMaxY]) + vD[IndexOfMaxY+1])); 380 | double interpolatedX = ((IndexOfMaxY + delta) * samplingFrequency) / (samples-1); 381 | if(IndexOfMaxY==(samples >> 1)) //To improve calculation on edge values 382 | interpolatedX = ((IndexOfMaxY + delta) * samplingFrequency) / (samples); 383 | // returned value: interpolated frequency peak apex 384 | return(interpolatedX); 385 | } 386 | 387 | void arduinoFFT::MajorPeak(double *vD, uint16_t samples, double samplingFrequency, double *f, double *v) 388 | { 389 | #warning("This method is deprecated and will be removed on future revisions.") 390 | double maxY = 0; 391 | uint16_t IndexOfMaxY = 0; 392 | //If sampling_frequency = 2 * max_frequency in signal, 393 | //value would be stored at position samples/2 394 | for (uint16_t i = 1; i < ((samples >> 1) + 1); i++) { 395 | if ((vD[i - 1] < vD[i]) && (vD[i] > vD[i + 1])) { 396 | if (vD[i] > maxY) { 397 | maxY = vD[i]; 398 | IndexOfMaxY = i; 399 | } 400 | } 401 | } 402 | double delta = 0.5 * ((vD[IndexOfMaxY - 1] - vD[IndexOfMaxY + 1]) / (vD[IndexOfMaxY - 1] - (2.0 * vD[IndexOfMaxY]) + vD[IndexOfMaxY + 1])); 403 | double interpolatedX = ((IndexOfMaxY + delta) * samplingFrequency) / (samples - 1); 404 | //double popo = 405 | if (IndexOfMaxY == (samples >> 1)) //To improve calculation on edge values 406 | interpolatedX = ((IndexOfMaxY + delta) * samplingFrequency) / (samples); 407 | // returned value: interpolated frequency peak apex 408 | *f = interpolatedX; 409 | *v = abs(vD[IndexOfMaxY - 1] - (2.0 * vD[IndexOfMaxY]) + vD[IndexOfMaxY + 1]); 410 | } 411 | 412 | uint8_t arduinoFFT::Exponent(uint16_t value) 413 | { 414 | #warning("This method will not be accessible on future revisions.") 415 | // Calculates the base 2 logarithm of a value 416 | uint8_t result = 0; 417 | while (((value >> result) & 1) != 1) result++; 418 | return(result); 419 | } 420 | 421 | // Private functions 422 | 423 | void arduinoFFT::Swap(double *x, double *y) 424 | { 425 | double temp = *x; 426 | *x = *y; 427 | *y = temp; 428 | } 429 | -------------------------------------------------------------------------------- /src/arduinoFFT.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | FFT libray 4 | Copyright (C) 2010 Didier Longueville 5 | Copyright (C) 2014 Enrique Condes 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | 20 | */ 21 | 22 | #ifndef arduinoFFT_h /* Prevent loading library twice */ 23 | #define arduinoFFT_h 24 | #ifdef ARDUINO 25 | #if ARDUINO >= 100 26 | #include "Arduino.h" 27 | #else 28 | #include "WProgram.h" /* This is where the standard Arduino code lies */ 29 | #endif 30 | #else 31 | #include 32 | #include 33 | #ifdef __AVR__ 34 | #include 35 | #endif 36 | #include 37 | #include "defs.h" 38 | #include "types.h" 39 | #endif 40 | 41 | #define FFT_LIB_REV 0x14 42 | /* Custom constants */ 43 | #define FFT_FORWARD 0x01 44 | #define FFT_REVERSE 0x00 45 | 46 | /* Windowing type */ 47 | #define FFT_WIN_TYP_RECTANGLE 0x00 /* rectangle (Box car) */ 48 | #define FFT_WIN_TYP_HAMMING 0x01 /* hamming */ 49 | #define FFT_WIN_TYP_HANN 0x02 /* hann */ 50 | #define FFT_WIN_TYP_TRIANGLE 0x03 /* triangle (Bartlett) */ 51 | #define FFT_WIN_TYP_NUTTALL 0x04 /* nuttall */ 52 | #define FFT_WIN_TYP_BLACKMAN 0x05 /* blackman */ 53 | #define FFT_WIN_TYP_BLACKMAN_NUTTALL 0x06 /* blackman nuttall */ 54 | #define FFT_WIN_TYP_BLACKMAN_HARRIS 0x07 /* blackman harris*/ 55 | #define FFT_WIN_TYP_FLT_TOP 0x08 /* flat top */ 56 | #define FFT_WIN_TYP_WELCH 0x09 /* welch */ 57 | /*Mathematial constants*/ 58 | #define twoPi 6.28318531 59 | #define fourPi 12.56637061 60 | #define sixPi 18.84955593 61 | 62 | class arduinoFFT { 63 | public: 64 | /* Constructor */ 65 | arduinoFFT(void); 66 | arduinoFFT(double *vReal, double *vImag, uint16_t samples, double samplingFrequency); 67 | /* Destructor */ 68 | ~arduinoFFT(void); 69 | /* Functions */ 70 | uint8_t Revision(void); 71 | uint8_t Exponent(uint16_t value); 72 | void ComplexToMagnitude(double *vReal, double *vImag, uint16_t samples); 73 | void Compute(double *vReal, double *vImag, uint16_t samples, uint8_t dir); 74 | void Compute(double *vReal, double *vImag, uint16_t samples, uint8_t power, uint8_t dir); 75 | void DCRemoval(double *vData, uint16_t samples); 76 | double MajorPeak(double *vD, uint16_t samples, double samplingFrequency); 77 | void Windowing(double *vData, uint16_t samples, uint8_t windowType, uint8_t dir); 78 | void ComplexToMagnitude(); 79 | void Compute(uint8_t dir); 80 | void DCRemoval(); 81 | double MajorPeak(); 82 | void Windowing(uint8_t windowType, uint8_t dir); 83 | 84 | void MajorPeak(double *f, double *v); 85 | void MajorPeak(double *vD, uint16_t samples, double samplingFrequency, double *f, double *v); 86 | 87 | 88 | private: 89 | /* Variables */ 90 | uint16_t _samples; 91 | double _samplingFrequency; 92 | double *_vReal; 93 | double *_vImag; 94 | uint8_t _power; 95 | /* Functions */ 96 | void Swap(double *x, double *y); 97 | }; 98 | 99 | #endif 100 | -------------------------------------------------------------------------------- /src/defs.h: -------------------------------------------------------------------------------- 1 | /*! \file avrlibdefs.h \brief AVRlib global defines and macros. */ 2 | //***************************************************************************** 3 | // 4 | // File Name : 'avrlibdefs.h' 5 | // Title : AVRlib global defines and macros include file 6 | // Author : Pascal Stang 7 | // Created : 7/12/2001 8 | // Revised : 9/30/2002 9 | // Version : 1.1 10 | // Target MCU : Atmel AVR series 11 | // Editor Tabs : 4 12 | // 13 | // Description : This include file is designed to contain items useful to all 14 | // code files and projects, regardless of specific implementation. 15 | // 16 | // This code is distributed under the GNU Public License 17 | // which can be found at http://www.gnu.org/licenses/gpl.txt 18 | // 19 | //***************************************************************************** 20 | 21 | 22 | #ifndef AVRLIBDEFS_H 23 | #define AVRLIBDEFS_H 24 | 25 | //#define F_CPU 4000000 26 | #define MEM_TYPE 1 27 | 28 | // Code compatibility to new AVR-libc 29 | // outb(), inb(), inw(), outw(), BV(), sbi(), cbi(), sei(), cli() 30 | #ifndef outb 31 | #define outb(addr, data) addr = (data) 32 | #endif 33 | #ifndef inb 34 | #define inb(addr) (addr) 35 | #endif 36 | #ifndef outw 37 | #define outw(addr, data) addr = (data) 38 | #endif 39 | #ifndef inw 40 | #define inw(addr) (addr) 41 | #endif 42 | #ifndef BV 43 | #define BV(bit) (1<<(bit)) 44 | #endif 45 | //#ifndef cbi 46 | // #define cbi(reg,bit) reg &= ~(BV(bit)) 47 | //#endif 48 | //#ifndef sbi 49 | // #define sbi(reg,bit) reg |= (BV(bit)) 50 | //#endif 51 | #ifndef cli 52 | #define cli() __asm__ __volatile__ ("cli" ::) 53 | #endif 54 | #ifndef sei 55 | #define sei() __asm__ __volatile__ ("sei" ::) 56 | #endif 57 | 58 | // support for individual port pin naming in the mega128 59 | // see port128.h for details 60 | #ifdef __AVR_ATmega128__ 61 | // not currently necessary due to inclusion 62 | // of these defines in newest AVR-GCC 63 | // do a quick test to see if include is needed 64 | #ifndef PD0 65 | //#include "port128.h" 66 | #endif 67 | #endif 68 | 69 | // use this for packed structures 70 | // (this is seldom necessary on an 8-bit architecture like AVR, 71 | // but can assist in code portability to AVR) 72 | #define GNUC_PACKED __attribute__((packed)) 73 | 74 | // port address helpers 75 | #define DDR(x) ((x)-1) // address of data direction register of port x 76 | #define PIN(x) ((x)-2) // address of input register of port x 77 | 78 | // MIN/MAX/ABS macros 79 | #define MIN(a,b) ((ab)?(a):(b)) 81 | #define ABS(x) ((x>0)?(x):(-x)) 82 | 83 | // constants 84 | #define PI 3.14159265359 85 | 86 | //Math 87 | #define sq(x) ((x)*(x)) 88 | #define constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt))) 89 | 90 | #endif 91 | -------------------------------------------------------------------------------- /src/lcd.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 AKsevenFour / Adam Keher (adam@keher.com.au) 3 | http://www.aksevenfour.com / https://github.com/AdamKeher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 7 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 13 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 14 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 15 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | #include "lcd.h" 18 | #include "Arduino.h" 19 | 20 | LCD::LCD(int address, int columns, int rows, int sda, int scl) { 21 | this->address = address; 22 | this->columns = columns; 23 | this-> rows = rows; 24 | this->sda = sda; 25 | this->scl = scl; 26 | 27 | lcd = new LiquidCrystal_I2C(address, columns, rows); 28 | // lcd->init(sda, scl); 29 | Wire.begin(sda, scl); 30 | lcd->init(); 31 | lcd->begin(columns, rows); 32 | lcd->backlight(); 33 | 34 | row1Text = (char *)malloc(128); 35 | row0Text = (char *)malloc(128); 36 | 37 | memset(row1Text, 0, 128); 38 | memset(row0Text, 0, 128); 39 | }; 40 | 41 | void LCD::print(int col, int row, bool clear, char *text) { 42 | char displayText[64]; 43 | 44 | lcd->setCursor(col, row); 45 | 46 | if (clear) 47 | sprintf(displayText, "%-16s", text); 48 | else 49 | strcpy(displayText, text); 50 | 51 | if (row == 0) { 52 | strcpy(row0Text, text); 53 | row0Pos = 0; 54 | row0Forward = true; 55 | lcd->print(displayText); 56 | } 57 | else 58 | if (row == 1) { 59 | strcpy(row1Text, text); 60 | row1Pos = 0; 61 | row1Forward = true; 62 | lcd->print(displayText); 63 | } 64 | }; 65 | 66 | void LCD::setScrollDelay(int delay) { 67 | row1ScrollDelay = delay; 68 | } 69 | 70 | void LCD::loop() { 71 | char buffer[17]; 72 | 73 | long now = millis(); 74 | 75 | if ((now - row0lastUpdate > row0ScrollDelay) && (strlen(row0Text) > 16)) 76 | { 77 | if (row0Forward) 78 | row0Pos++; 79 | else 80 | row0Pos--; 81 | 82 | if (row0Pos > strlen(row0Text) - columns) 83 | { 84 | row0Forward = false; 85 | row0Pos--; 86 | } 87 | 88 | if (row0Pos == 0) 89 | { 90 | row0Forward = true; 91 | } 92 | memcpy(buffer, row0Text + row0Pos, 16); 93 | lcd->setCursor(0, 0); 94 | lcd->print(buffer); 95 | 96 | row0lastUpdate = now; 97 | } 98 | 99 | if ((now - row1lastUpdate > row1ScrollDelay) && (strlen(row1Text) > 16)) 100 | { 101 | if (row1Forward) 102 | row1Pos++; 103 | else 104 | row1Pos--; 105 | 106 | if (row1Pos > strlen(row1Text) - columns) 107 | { 108 | row1Forward = false; 109 | row1Pos--; 110 | } 111 | 112 | if (row1Pos == 0) 113 | { 114 | row1Forward = true; 115 | } 116 | memcpy(buffer, row1Text + row1Pos, 16); 117 | lcd->setCursor(0, 1); 118 | lcd->print(buffer); 119 | 120 | row1lastUpdate = now; 121 | } 122 | 123 | 124 | }; -------------------------------------------------------------------------------- /src/lcd.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 AKsevenFour / Adam Keher (adam@keher.com.au) 3 | http://www.aksevenfour.com / https://github.com/AdamKeher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 7 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 13 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 14 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 15 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | #include 18 | 19 | #ifndef LCD_H 20 | #define LCD_H 21 | 22 | class LCD 23 | { 24 | public: 25 | LCD(int address, int columns, int rows, int sda, int scl); 26 | 27 | void print(int col, int row, bool clear, char *text); 28 | void loop(); 29 | void setScrollDelay(int delay); 30 | 31 | LiquidCrystal_I2C* getLCD() { return lcd; } 32 | 33 | private: 34 | LiquidCrystal_I2C *lcd; 35 | int address; 36 | int columns; 37 | int rows; 38 | int sda; 39 | int scl; 40 | 41 | // TODO: convert to struct and use list for #rows 42 | // TODO: use columns for row clear etc 43 | 44 | char *row0Text; 45 | int row0Pos = 0; 46 | bool row0Forward = true; 47 | int row0ScrollDelay = 500; 48 | long row0lastUpdate = 0; 49 | 50 | char *row1Text; 51 | int row1Pos = 0; 52 | bool row1Forward = true; 53 | int row1ScrollDelay = 500; 54 | long row1lastUpdate = 0; 55 | }; 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /src/mp3_decoder.h: -------------------------------------------------------------------------------- 1 | // based om helix mp3 decoder 2 | #pragma once 3 | #pragma GCC optimize ("O3") 4 | 5 | #include "Arduino.h" 6 | #include "assert.h" 7 | 8 | static const uint8_t m_HUFF_PAIRTABS =32; 9 | static const uint8_t m_BLOCK_SIZE =18; 10 | static const uint8_t m_NBANDS =32; 11 | static const uint8_t m_MAX_REORDER_SAMPS =(192-126)*3; // largest critical band for short blocks (see sfBandTable) 12 | static const uint16_t m_VBUF_LENGTH =17*2* m_NBANDS; // for double-sized vbuf FIFO 13 | static const uint8_t m_MAX_SCFBD =4; // max scalefactor bands per channel 14 | static const uint16_t m_MAINBUF_SIZE =1940; 15 | static const uint8_t m_MAX_NGRAN =2; // max granules 16 | static const uint8_t m_MAX_NCHAN =2; // max channels 17 | static const uint16_t m_MAX_NSAMP =576; // max samples per channel, per granule 18 | 19 | enum { 20 | ERR_MP3_NONE = 0, 21 | ERR_MP3_INDATA_UNDERFLOW = -1, 22 | ERR_MP3_MAINDATA_UNDERFLOW = -2, 23 | ERR_MP3_FREE_BITRATE_SYNC = -3, 24 | ERR_MP3_OUT_OF_MEMORY = -4, 25 | ERR_MP3_NULL_POINTER = -5, 26 | ERR_MP3_INVALID_FRAMEHEADER = -6, 27 | ERR_MP3_INVALID_SIDEINFO = -7, 28 | ERR_MP3_INVALID_SCALEFACT = -8, 29 | ERR_MP3_INVALID_HUFFCODES = -9, 30 | ERR_MP3_INVALID_DEQUANTIZE = -10, 31 | ERR_MP3_INVALID_IMDCT = -11, 32 | ERR_MP3_INVALID_SUBBAND = -12, 33 | 34 | ERR_UNKNOWN = -9999 35 | }; 36 | 37 | typedef struct MP3FrameInfo { 38 | int bitrate; 39 | int nChans; 40 | int samprate; 41 | int bitsPerSample; 42 | int outputSamps; 43 | int layer; 44 | int version; 45 | } MP3FrameInfo_t; 46 | 47 | typedef struct SFBandTable { 48 | int/*short*/ l[23]; 49 | int/*short*/ s[14]; 50 | } SFBandTable_t; 51 | 52 | typedef struct BitStreamInfo { 53 | unsigned char *bytePtr; 54 | unsigned int iCache; 55 | int cachedBits; 56 | int nBytes; 57 | } BitStreamInfo_t; 58 | 59 | typedef enum { /* map these to the corresponding 2-bit values in the frame header */ 60 | Stereo = 0x00, /* two independent channels, but L and R frames might have different # of bits */ 61 | Joint = 0x01, /* coupled channels - layer III: mix of M-S and intensity, Layers I/II: intensity and direct coding only */ 62 | Dual = 0x02, /* two independent channels, L and R always have exactly 1/2 the total bitrate */ 63 | Mono = 0x03 /* one channel */ 64 | } StereoMode_t; 65 | 66 | typedef enum { /* map to 0,1,2 to make table indexing easier */ 67 | MPEG1 = 0, 68 | MPEG2 = 1, 69 | MPEG25 = 2 70 | } MPEGVersion_t; 71 | 72 | typedef struct FrameHeader { 73 | int layer; /* layer index (1, 2, or 3) */ 74 | int crc; /* CRC flag: 0 = disabled, 1 = enabled */ 75 | int brIdx; /* bitrate index (0 - 15) */ 76 | int srIdx; /* sample rate index (0 - 2) */ 77 | int paddingBit; /* padding flag: 0 = no padding, 1 = single pad byte */ 78 | int privateBit; /* unused */ 79 | int modeExt; /* used to decipher joint stereo mode */ 80 | int copyFlag; /* copyright flag: 0 = no, 1 = yes */ 81 | int origFlag; /* original flag: 0 = copy, 1 = original */ 82 | int emphasis; /* deemphasis mode */ 83 | int CRCWord; /* CRC word (16 bits, 0 if crc not enabled) */ 84 | } FrameHeader_t; 85 | 86 | typedef struct SideInfoSub { 87 | int part23Length; /* number of bits in main data */ 88 | int nBigvals; /* 2x this = first set of Huffman cw's (maximum amplitude can be > 1) */ 89 | int globalGain; /* overall gain for dequantizer */ 90 | int sfCompress; /* unpacked to figure out number of bits in scale factors */ 91 | int winSwitchFlag; /* window switching flag */ 92 | int blockType; /* block type */ 93 | int mixedBlock; /* 0 = regular block (all short or long), 1 = mixed block */ 94 | int tableSelect[3]; /* index of Huffman tables for the big values regions */ 95 | int subBlockGain[3]; /* subblock gain offset, relative to global gain */ 96 | int region0Count; /* 1+region0Count = num scale factor bands in first region of bigvals */ 97 | int region1Count; /* 1+region1Count = num scale factor bands in second region of bigvals */ 98 | int preFlag; /* for optional high frequency boost */ 99 | int sfactScale; /* scaling of the scalefactors */ 100 | int count1TableSelect; /* index of Huffman table for quad codewords */ 101 | } SideInfoSub_t; 102 | 103 | typedef struct SideInfo { 104 | int mainDataBegin; 105 | int privateBits; 106 | int scfsi[m_MAX_NCHAN][m_MAX_SCFBD]; /* 4 scalefactor bands per channel */ 107 | } SideInfo_t; 108 | 109 | typedef struct { 110 | int cbType; /* pure long = 0, pure short = 1, mixed = 2 */ 111 | int cbEndS[3]; /* number nonzero short cb's, per subbblock */ 112 | int cbEndSMax; /* max of cbEndS[] */ 113 | int cbEndL; /* number nonzero long cb's */ 114 | } CriticalBandInfo_t; 115 | 116 | typedef struct DequantInfo { 117 | int workBuf[m_MAX_REORDER_SAMPS]; /* workbuf for reordering short blocks */ 118 | } DequantInfo_t; 119 | 120 | typedef struct HuffmanInfo { 121 | int huffDecBuf[m_MAX_NCHAN][m_MAX_NSAMP]; /* used both for decoded Huffman values and dequantized coefficients */ 122 | int nonZeroBound[m_MAX_NCHAN]; /* number of coeffs in huffDecBuf[ch] which can be > 0 */ 123 | int gb[m_MAX_NCHAN]; /* minimum number of guard bits in huffDecBuf[ch] */ 124 | } HuffmanInfo_t; 125 | 126 | typedef enum HuffTabType { 127 | noBits, 128 | oneShot, 129 | loopNoLinbits, 130 | loopLinbits, 131 | quadA, 132 | quadB, 133 | invalidTab 134 | } HuffTabType_t; 135 | 136 | typedef struct HuffTabLookup { 137 | int linBits; 138 | int tabType; /*HuffTabType*/ 139 | } HuffTabLookup_t; 140 | 141 | typedef struct IMDCTInfo { 142 | int outBuf[m_MAX_NCHAN][m_BLOCK_SIZE][m_NBANDS]; /* output of IMDCT */ 143 | int overBuf[m_MAX_NCHAN][m_MAX_NSAMP / 2]; /* overlap-add buffer (by symmetry, only need 1/2 size) */ 144 | int numPrevIMDCT[m_MAX_NCHAN]; /* how many IMDCT's calculated in this channel on prev. granule */ 145 | int prevType[m_MAX_NCHAN]; 146 | int prevWinSwitch[m_MAX_NCHAN]; 147 | int gb[m_MAX_NCHAN]; 148 | } IMDCTInfo_t; 149 | 150 | typedef struct BlockCount { 151 | int nBlocksLong; 152 | int nBlocksTotal; 153 | int nBlocksPrev; 154 | int prevType; 155 | int prevWinSwitch; 156 | int currWinSwitch; 157 | int gbIn; 158 | int gbOut; 159 | } BlockCount_t; 160 | 161 | typedef struct ScaleFactorInfoSub { /* max bits in scalefactors = 5, so use char's to save space */ 162 | char l[23]; /* [band] */ 163 | char s[13][3]; /* [band][window] */ 164 | } ScaleFactorInfoSub_t; 165 | 166 | typedef struct ScaleFactorJS { /* used in MPEG 2, 2.5 intensity (joint) stereo only */ 167 | int intensityScale; 168 | int slen[4]; 169 | int nr[4]; 170 | } ScaleFactorJS_t; 171 | 172 | /* NOTE - could get by with smaller vbuf if memory is more important than speed 173 | * (in Subband, instead of replicating each block in FDCT32 you would do a memmove on the 174 | * last 15 blocks to shift them down one, a hardware style FIFO) 175 | */ 176 | typedef struct SubbandInfo { 177 | int vbuf[m_MAX_NCHAN * m_VBUF_LENGTH]; /* vbuf for fast DCT-based synthesis PQMF - double size for speed (no modulo indexing) */ 178 | int vindex; /* internal index for tracking position in vbuf */ 179 | } SubbandInfo_t; 180 | 181 | typedef struct MP3DecInfo { 182 | /* buffer which must be large enough to hold largest possible main_data section */ 183 | unsigned char mainBuf[m_MAINBUF_SIZE]; 184 | /* special info for "free" bitrate files */ 185 | int freeBitrateFlag; 186 | int freeBitrateSlots; 187 | /* user-accessible info */ 188 | int bitrate; 189 | int nChans; 190 | int samprate; 191 | int nGrans; /* granules per frame */ 192 | int nGranSamps; /* samples per granule */ 193 | int nSlots; 194 | int layer; 195 | 196 | int mainDataBegin; 197 | int mainDataBytes; 198 | int part23Length[m_MAX_NGRAN][m_MAX_NCHAN]; 199 | } MP3DecInfo_t; 200 | 201 | 202 | 203 | 204 | /* format = Q31 205 | * #define M_PI 3.14159265358979323846 206 | * double u = 2.0 * M_PI / 9.0; 207 | * float c0 = sqrt(3.0) / 2.0; 208 | * float c1 = cos(u); 209 | * float c2 = cos(2*u); 210 | * float c3 = sin(u); 211 | * float c4 = sin(2*u); 212 | */ 213 | 214 | const int c9_0 = 0x6ed9eba1; 215 | const int c9_1 = 0x620dbe8b; 216 | const int c9_2 = 0x163a1a7e; 217 | const int c9_3 = 0x5246dd49; 218 | const int c9_4 = 0x7e0e2e32; 219 | 220 | 221 | 222 | const int c3_0 = 0x6ed9eba1; /* format = Q31, cos(pi/6) */ 223 | const int c6[3] = { 0x7ba3751d, 0x5a82799a, 0x2120fb83 }; /* format = Q31, cos(((0:2) + 0.5) * (pi/6)) */ 224 | 225 | /* format = Q31 226 | * cos(((0:8) + 0.5) * (pi/18)) 227 | */ 228 | const uint32_t c18[9] = { 0x7f834ed0, 0x7ba3751d, 0x7401e4c1, 0x68d9f964, 0x5a82799a, 0x496af3e2, 0x36185aee, 0x2120fb83, 0x0b27eb5c}; 229 | 230 | /* scale factor lengths (num bits) */ 231 | const char m_SFLenTab[16][2] = { {0, 0}, {0, 1}, {0, 2}, {0, 3}, {3, 0}, {1, 1}, {1, 2}, {1, 3}, 232 | {2, 1}, {2, 2}, {2, 3}, {3, 1}, {3, 2}, {3, 3}, {4, 2}, {4, 3}}; 233 | 234 | /* NRTab[size + 3*is_right][block type][partition] 235 | * block type index: 0 = (bt0,bt1,bt3), 1 = bt2 non-mixed, 2 = bt2 mixed 236 | * partition: scale factor groups (sfb1 through sfb4) 237 | * for block type = 2 (mixed or non-mixed) / by 3 is rolled into this table 238 | * (for 3 short blocks per long block) 239 | * see 2.4.3.2 in MPEG 2 (low sample rate) spec 240 | * stuff rolled into this table: 241 | * NRTab[x][1][y] --> (NRTab[x][1][y]) / 3 242 | * NRTab[x][2][>=1] --> (NRTab[x][2][>=1]) / 3 (first partition is long block) 243 | */ 244 | const char NRTab[6][3][4] = { 245 | {{ 6, 5, 5, 5}, {3, 3, 3, 3}, {6, 3, 3, 3}}, 246 | {{ 6, 5, 7, 3}, {3, 3, 4, 2}, {6, 3, 4, 2}}, 247 | {{11, 10, 0, 0}, {6, 6, 0, 0}, {6, 3, 6, 0}}, 248 | {{ 7, 7, 7, 0}, {4, 4, 4, 0}, {6, 5, 4, 0}}, 249 | {{ 6, 6, 6, 3}, {4, 3, 3, 2}, {6, 4, 3, 2}}, 250 | {{ 8, 8, 5, 0}, {5, 4, 3, 0}, {6, 6, 3, 0}} 251 | }; 252 | 253 | 254 | 255 | /* optional pre-emphasis for high-frequency scale factor bands */ 256 | const char preTab[22] = { 0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,2,2,3,3,3,2,0 }; 257 | 258 | /* pow(2,-i/4) for i=0..3, Q31 format */ 259 | const int pow14[4] PROGMEM = { 260 | 0x7fffffff, 0x6ba27e65, 0x5a82799a, 0x4c1bf829 261 | }; 262 | 263 | 264 | /* 265 | * Minimax polynomial approximation to pow(x, 4/3), over the range 266 | * poly43lo: x = [0.5, 0.7071] 267 | * poly43hi: x = [0.7071, 1.0] 268 | * 269 | * Relative error < 1E-7 270 | * Coefs are scaled by 4, 2, 1, 0.5, 0.25 271 | */ 272 | const unsigned int poly43lo[5] PROGMEM = { 0x29a0bda9, 0xb02e4828, 0x5957aa1b, 0x236c498d, 0xff581859 }; 273 | const unsigned int poly43hi[5] PROGMEM = { 0x10852163, 0xd333f6a4, 0x46e9408b, 0x27c2cef0, 0xfef577b4 }; 274 | 275 | /* pow(2, i*4/3) as exp and frac */ 276 | const int pow2exp[8] PROGMEM = { 14, 13, 11, 10, 9, 7, 6, 5 }; 277 | 278 | const int pow2frac[8] PROGMEM = { 279 | 0x6597fa94, 0x50a28be6, 0x7fffffff, 0x6597fa94, 280 | 0x50a28be6, 0x7fffffff, 0x6597fa94, 0x50a28be6 281 | }; 282 | 283 | const uint16_t m_HUFF_OFFSET_01= 0; 284 | const uint16_t m_HUFF_OFFSET_02= 9 + m_HUFF_OFFSET_01; 285 | const uint16_t m_HUFF_OFFSET_03= 65 + m_HUFF_OFFSET_02; 286 | const uint16_t m_HUFF_OFFSET_05= 65 + m_HUFF_OFFSET_03; 287 | const uint16_t m_HUFF_OFFSET_06=257 + m_HUFF_OFFSET_05; 288 | const uint16_t m_HUFF_OFFSET_07=129 + m_HUFF_OFFSET_06; 289 | const uint16_t m_HUFF_OFFSET_08=110 + m_HUFF_OFFSET_07; 290 | const uint16_t m_HUFF_OFFSET_09=280 + m_HUFF_OFFSET_08; 291 | const uint16_t m_HUFF_OFFSET_10= 93 + m_HUFF_OFFSET_09; 292 | const uint16_t m_HUFF_OFFSET_11=320 + m_HUFF_OFFSET_10; 293 | const uint16_t m_HUFF_OFFSET_12=296 + m_HUFF_OFFSET_11; 294 | const uint16_t m_HUFF_OFFSET_13=185 + m_HUFF_OFFSET_12; 295 | const uint16_t m_HUFF_OFFSET_15=497 + m_HUFF_OFFSET_13; 296 | const uint16_t m_HUFF_OFFSET_16=580 + m_HUFF_OFFSET_15; 297 | const uint16_t m_HUFF_OFFSET_24=651 + m_HUFF_OFFSET_16; 298 | 299 | const int huffTabOffset[m_HUFF_PAIRTABS] PROGMEM = { 300 | 0, m_HUFF_OFFSET_01, m_HUFF_OFFSET_02, m_HUFF_OFFSET_03, 301 | 0, m_HUFF_OFFSET_05, m_HUFF_OFFSET_06, m_HUFF_OFFSET_07, 302 | m_HUFF_OFFSET_08, m_HUFF_OFFSET_09, m_HUFF_OFFSET_10, m_HUFF_OFFSET_11, 303 | m_HUFF_OFFSET_12, m_HUFF_OFFSET_13, 0, m_HUFF_OFFSET_15, 304 | m_HUFF_OFFSET_16, m_HUFF_OFFSET_16, m_HUFF_OFFSET_16, m_HUFF_OFFSET_16, 305 | m_HUFF_OFFSET_16, m_HUFF_OFFSET_16, m_HUFF_OFFSET_16, m_HUFF_OFFSET_16, 306 | m_HUFF_OFFSET_24, m_HUFF_OFFSET_24, m_HUFF_OFFSET_24, m_HUFF_OFFSET_24, 307 | m_HUFF_OFFSET_24, m_HUFF_OFFSET_24, m_HUFF_OFFSET_24, m_HUFF_OFFSET_24,}; 308 | 309 | const HuffTabLookup_t huffTabLookup[m_HUFF_PAIRTABS] PROGMEM = { 310 | { 0, noBits }, 311 | { 0, oneShot }, 312 | { 0, oneShot }, 313 | { 0, oneShot }, 314 | { 0, invalidTab }, 315 | { 0, oneShot }, 316 | { 0, oneShot }, 317 | { 0, loopNoLinbits }, 318 | { 0, loopNoLinbits }, 319 | { 0, loopNoLinbits }, 320 | { 0, loopNoLinbits }, 321 | { 0, loopNoLinbits }, 322 | { 0, loopNoLinbits }, 323 | { 0, loopNoLinbits }, 324 | { 0, invalidTab }, 325 | { 0, loopNoLinbits }, 326 | { 1, loopLinbits }, 327 | { 2, loopLinbits }, 328 | { 3, loopLinbits }, 329 | { 4, loopLinbits }, 330 | { 6, loopLinbits }, 331 | { 8, loopLinbits }, 332 | { 10, loopLinbits }, 333 | { 13, loopLinbits }, 334 | { 4, loopLinbits }, 335 | { 5, loopLinbits }, 336 | { 6, loopLinbits }, 337 | { 7, loopLinbits }, 338 | { 8, loopLinbits }, 339 | { 9, loopLinbits }, 340 | { 11, loopLinbits }, 341 | { 13, loopLinbits }, 342 | }; 343 | 344 | 345 | const int quadTabOffset[2] PROGMEM = {0, 64}; 346 | const int quadTabMaxBits[2] PROGMEM = {6, 4}; 347 | 348 | /* indexing = [version][samplerate index] 349 | * sample rate of frame (Hz) 350 | */ 351 | const int samplerateTab[3][3] PROGMEM = { 352 | { 44100, 48000, 32000 }, /* MPEG-1 */ 353 | { 22050, 24000, 16000 }, /* MPEG-2 */ 354 | { 11025, 12000, 8000 }, /* MPEG-2.5 */ 355 | }; 356 | 357 | 358 | 359 | /* indexing = [version][layer] 360 | * number of samples in one frame (per channel) 361 | */ 362 | const int/*short*/samplesPerFrameTab[3][3] PROGMEM = { { 384, 1152, 1152 }, /* MPEG1 */ 363 | { 384, 1152, 576 }, /* MPEG2 */ 364 | { 384, 1152, 576 }, /* MPEG2.5 */ 365 | }; 366 | 367 | /* layers 1, 2, 3 */ 368 | const short bitsPerSlotTab[3] = { 32, 8, 8 }; 369 | 370 | /* indexing = [version][mono/stereo] 371 | * number of bytes in side info section of bitstream 372 | */ 373 | const int/*short*/sideBytesTab[3][2] PROGMEM = { { 17, 32 }, /* MPEG-1: mono, stereo */ 374 | { 9, 17 }, /* MPEG-2: mono, stereo */ 375 | { 9, 17 }, /* MPEG-2.5: mono, stereo */ 376 | }; 377 | 378 | /* indexing = [version][sampleRate][long (.l) or short (.s) block] 379 | * sfBandTable[v][s].l[cb] = index of first bin in critical band cb (long blocks) 380 | * sfBandTable[v][s].s[cb] = index of first bin in critical band cb (short blocks) 381 | */ 382 | const SFBandTable_t sfBandTable[3][3] PROGMEM = { 383 | { /* MPEG-1 (44, 48, 32 kHz) */ 384 | { {0, 4, 8, 12, 16, 20, 24, 30, 36, 44, 52, 62, 74, 90, 110, 134, 162, 196, 238, 288, 342, 418, 576 }, 385 | {0, 4, 8, 12, 16, 22, 30, 40, 52, 66, 84, 106, 136, 192} }, 386 | { {0, 4, 8, 12, 16, 20, 24, 30, 36, 42, 50, 60, 72, 88, 106, 128, 156, 190, 230, 276, 330, 384, 576 }, 387 | {0, 4, 8, 12, 16, 22, 28, 38, 50, 64, 80, 100, 126, 192} }, 388 | { {0, 4, 8, 12, 16, 20, 24, 30, 36, 44, 54, 66, 82, 102, 126, 156, 194, 240, 296, 364, 448, 550, 576 }, 389 | {0, 4, 8, 12, 16, 22, 30, 42, 58, 78, 104, 138, 180, 192} } }, 390 | { /* MPEG-2 (22, 24, 16 kHz) */ 391 | { {0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576 }, 392 | {0, 4, 8, 12, 18, 24, 32, 42, 56, 74, 100, 132, 174, 192} }, 393 | { {0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 114, 136, 162, 194, 232, 278, 332, 394, 464, 540, 576 }, 394 | {0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 136, 180, 192} }, 395 | { {0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576 }, 396 | {0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192} }, }, 397 | { /* MPEG-2.5 (11, 12, 8 kHz) */ 398 | { {0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576 }, 399 | {0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192 } }, 400 | { {0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576 }, 401 | {0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192 } }, 402 | { {0, 12, 24, 36, 48, 60, 72, 88, 108, 132, 160, 192, 232, 280, 336, 400, 476, 566, 568, 570, 572, 574, 576 }, 403 | {0, 8, 16, 24, 36, 52, 72, 96, 124, 160, 162, 164, 166, 192 } }, }, 404 | }; 405 | 406 | 407 | /* indexing = [intensity scale on/off][left/right] 408 | * format = Q30, range = [0.0, 1.414] 409 | * 410 | * illegal intensity position scalefactors (see comments on ISFMpeg1) 411 | */ 412 | const int ISFIIP[2][2] PROGMEM = { 413 | {0x40000000, 0x00000000}, /* mid-side off */ 414 | {0x40000000, 0x40000000}, /* mid-side on */ 415 | }; 416 | 417 | const unsigned char uniqueIDTab[8] = {0x5f, 0x4b, 0x43, 0x5f, 0x5f, 0x4a, 0x52, 0x5f}; 418 | 419 | /* anti-alias coefficients - see spec Annex B, table 3-B.9 420 | * csa[0][i] = CSi, csa[1][i] = CAi 421 | * format = Q31 422 | */ 423 | const uint32_t csa[8][2] PROGMEM = { 424 | {0x6dc253f0, 0xbe2500aa}, 425 | {0x70dcebe4, 0xc39e4949}, 426 | {0x798d6e73, 0xd7e33f4a}, 427 | {0x7ddd40a7, 0xe8b71176}, 428 | {0x7f6d20b7, 0xf3e4fe2f}, 429 | {0x7fe47e40, 0xfac1a3c7}, 430 | {0x7ffcb263, 0xfe2ebdc6}, 431 | {0x7fffc694, 0xff86c25d}, 432 | }; 433 | 434 | /* format = Q30, right shifted by 12 (sign bits only in top 12 - undo this when rounding to short) 435 | * this is to enable early-terminating multiplies on ARM 436 | * range = [-1.144287109, 1.144989014] 437 | * max gain of filter (per output sample) ~= 2.731 438 | * 439 | * new (properly sign-flipped) values 440 | * - these actually are correct to 32 bits, (floating-pt coefficients in spec 441 | * chosen such that only ~20 bits are required) 442 | * 443 | * Reordering - see table 3-B.3 in spec (appendix B) 444 | * 445 | * polyCoef[i] = 446 | * D[ 0, 32, 64, ... 480], i = [ 0, 15] 447 | * D[ 1, 33, 65, ... 481], i = [ 16, 31] 448 | * D[ 2, 34, 66, ... 482], i = [ 32, 47] 449 | * ... 450 | * D[15, 47, 79, ... 495], i = [240,255] 451 | * 452 | * also exploits symmetry: D[i] = -D[512 - i], for i = [1, 255] 453 | * 454 | * polyCoef[256, 257, ... 263] are for special case of sample 16 (out of 0) 455 | * see PolyphaseStereo() and PolyphaseMono() 456 | */ 457 | 458 | // prototypes 459 | void EraseBuffers(void); 460 | int MP3Decode( unsigned char *inbuf, int *bytesLeft, short *outbuf, int useSize); 461 | void MP3GetLastFrameInfo(); 462 | int MP3GetNextFrameInfo(unsigned char *buf); 463 | int MP3FindSyncWord(unsigned char *buf, int nBytes); 464 | int MP3GetSampRate(); 465 | int MP3GetChannels(); 466 | int MP3GetBitsPerSample(); 467 | int MP3GetBitrate(); 468 | int MP3GetOutputSamps(); 469 | 470 | //internally used 471 | void PolyphaseMono(short *pcm, int *vbuf, const uint32_t *coefBase); 472 | void PolyphaseStereo(short *pcm, int *vbuf, const uint32_t *coefBase); 473 | void SetBitstreamPointer(BitStreamInfo_t *bsi, int nBytes, unsigned char *buf); 474 | unsigned int GetBits(BitStreamInfo_t *bsi, int nBits); 475 | int CalcBitsUsed(BitStreamInfo_t *bsi, unsigned char *startBuf, int startOffset); 476 | int DequantChannel(int *sampleBuf, int *workBuf, int *nonZeroBound, SideInfoSub_t *sis, ScaleFactorInfoSub_t *sfis, CriticalBandInfo_t *cbi); 477 | void MidSideProc(int x[m_MAX_NCHAN][m_MAX_NSAMP], int nSamps, int mOut[2]); 478 | void IntensityProcMPEG1(int x[m_MAX_NCHAN][m_MAX_NSAMP], int nSamps, ScaleFactorInfoSub_t *sfis, CriticalBandInfo_t *cbi, int midSideFlag, int mixFlag, int mOut[2]); 479 | void IntensityProcMPEG2(int x[m_MAX_NCHAN][m_MAX_NSAMP], int nSamps, ScaleFactorInfoSub_t *sfis, CriticalBandInfo_t *cbi, ScaleFactorJS_t *sfjs, int midSideFlag, int mixFlag, int mOut[2]); 480 | void FDCT32(int *x, int *d, int offset, int oddBlock, int gb);// __attribute__ ((section (".data"))); 481 | void FreeBuffers(); 482 | int CheckPadBit(); 483 | int UnpackFrameHeader(unsigned char *buf); 484 | int UnpackSideInfo(unsigned char *buf); 485 | int DecodeHuffman( unsigned char *buf, int *bitOffset, int huffBlockBits, int gr, int ch); 486 | int MP3Dequantize( int gr); 487 | int IMDCT( int gr, int ch); 488 | int UnpackScaleFactors( unsigned char *buf, int *bitOffset, int bitsAvail, int gr, int ch); 489 | int Subband(short *pcmBuf); 490 | short ClipToShort(int x, int fracBits); 491 | void RefillBitstreamCache(BitStreamInfo_t *bsi); 492 | void UnpackSFMPEG1(BitStreamInfo_t *bsi, SideInfoSub_t *sis, ScaleFactorInfoSub_t *sfis, int *scfsi, int gr, ScaleFactorInfoSub_t *sfisGr0); 493 | void UnpackSFMPEG2(BitStreamInfo_t *bsi, SideInfoSub_t *sis, ScaleFactorInfoSub_t *sfis, int gr, int ch, int modeExt, ScaleFactorJS_t *sfjs); 494 | int MP3FindFreeSync(unsigned char *buf, unsigned char firstFH[4], int nBytes); 495 | void MP3ClearBadFrame( short *outbuf); 496 | int DecodeHuffmanPairs(int *xy, int nVals, int tabIdx, int bitsLeft, unsigned char *buf, int bitOffset); 497 | int DecodeHuffmanQuads(int *vwxy, int nVals, int tabIdx, int bitsLeft, unsigned char *buf, int bitOffset); 498 | int DequantBlock(int *inbuf, int *outbuf, int num, int scale); 499 | void AntiAlias(int *x, int nBfly); 500 | void WinPrevious(int *xPrev, int *xPrevWin, int btPrev); 501 | int FreqInvertRescale(int *y, int *xPrev, int blockIdx, int es); 502 | void idct9(int *x); 503 | int IMDCT36(int *xCurr, int *xPrev, int *y, int btCurr, int btPrev, int blockIdx, int gb); 504 | void imdct12(int *x, int *out); 505 | int IMDCT12x3(int *xCurr, int *xPrev, int *y, int btPrev, int blockIdx, int gb); 506 | int HybridTransform(int *xCurr, int *xPrev, int y[m_BLOCK_SIZE][m_NBANDS], SideInfoSub_t *sis, BlockCount_t *bc); 507 | inline uint64_t SAR64(uint64_t x, int n) {return x >> n;} 508 | inline int MULSHIFT32(int x, int y) { int z; z = (uint64_t) x * (uint64_t) y >> 32; return z;} 509 | inline uint64_t MADD64(uint64_t sum64, int x, int y) {sum64 += (uint64_t) x * (uint64_t) y; return sum64;}/* returns 64-bit value in [edx:eax] */ 510 | inline int CLZ(int x){int numZeros; if (!x) return(sizeof(int) * 8); numZeros = 0; while (!(x & 0x80000000)){numZeros++; x <<= 1;} return numZeros;} 511 | inline uint64_t xSAR64(uint64_t x, int n){return x >> n;} 512 | inline int FASTABS(int x){ int sign; sign=x>>(sizeof(int)*8-1); x^=sign; x-=sign; return x;} 513 | 514 | 515 | -------------------------------------------------------------------------------- /src/types.h: -------------------------------------------------------------------------------- 1 | //useful things to include in code 2 | 3 | #ifndef TYPES_H 4 | #define TYPES_H 5 | 6 | #ifndef WIN32 7 | // true/false defines 8 | #define FALSE 0 9 | #define TRUE -1 10 | #endif 11 | 12 | // datatype definitions macros 13 | typedef unsigned char u08; 14 | typedef signed char s08; 15 | typedef unsigned short u16; 16 | typedef signed short s16; 17 | typedef unsigned long u32; 18 | typedef signed long s32; 19 | typedef unsigned long long u64; 20 | typedef signed long long s64; 21 | 22 | // #ifndef __AVR__ 23 | #ifdef __MBED__ 24 | // use inttypes.h instead 25 | // C99 standard integer type definitions 26 | typedef unsigned char uint8_t; 27 | typedef signed char int8_t; 28 | typedef unsigned short uint16_t; 29 | typedef signed short int16_t; 30 | /*typedef unsigned long uint32_t; 31 | typedef signed long int32_t; 32 | typedef unsigned long uint64_t; 33 | typedef signed long int64_t; 34 | */ 35 | #endif 36 | 37 | // maximum value that can be held 38 | // by unsigned data types (8,16,32bits) 39 | #define MAX_U08 255 40 | #define MAX_U16 65535 41 | #define MAX_U32 4294967295 42 | 43 | // maximum values that can be held 44 | // by signed data types (8,16,32bits) 45 | #define MIN_S08 -128 46 | #define MAX_S08 127 47 | #define MIN_S16 -32768 48 | #define MAX_S16 32767 49 | #define MIN_S32 -2147483648 50 | #define MAX_S32 2147483647 51 | 52 | #ifndef WIN32 53 | // more type redefinitions 54 | typedef unsigned char BOOL; 55 | typedef unsigned char BYTE; 56 | typedef unsigned int WORD; 57 | typedef unsigned long DWORD; 58 | 59 | typedef unsigned char UCHAR; 60 | typedef unsigned int UINT; 61 | typedef unsigned short USHORT; 62 | typedef unsigned long ULONG; 63 | 64 | typedef char CHAR; 65 | typedef int INT; 66 | typedef long LONG; 67 | #endif 68 | 69 | #endif 70 | --------------------------------------------------------------------------------