diff --git a/README.md b/README.md index 7246567..f52fb7a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ -# yoradio +# ёRadio +![Logo](yoRadio/data/www/elogo100.png) Web-radio based on ESP32-audioI2S library -- diff --git a/images/page1.jpg b/images/page1.jpg new file mode 100644 index 0000000..7728dd1 Binary files /dev/null and b/images/page1.jpg differ diff --git a/images/page2.jpg b/images/page2.jpg new file mode 100644 index 0000000..a6dce9a Binary files /dev/null and b/images/page2.jpg differ diff --git a/images/page3.jpg b/images/page3.jpg new file mode 100644 index 0000000..3f0fa09 Binary files /dev/null and b/images/page3.jpg differ diff --git a/images/page4.jpg b/images/page4.jpg new file mode 100644 index 0000000..4f502ad Binary files /dev/null and b/images/page4.jpg differ diff --git a/yoRadio/audiohandlers.ino b/yoRadio/audiohandlers.ino new file mode 100644 index 0000000..e0248c2 --- /dev/null +++ b/yoRadio/audiohandlers.ino @@ -0,0 +1,39 @@ +void audio_info(const char *info) { + if(config.store.audioinfo) telnet.printf("##AUDIO.INFO#: %s\n", info); + if (strstr(info, "failed!") != NULL) { + display.title("[Request failed!]"); + player.mode = STOPPED; + player.stopInfo(); + } +} + +void audio_bitrate(const char *info) +{ + telnet.printf("%s %s\n", "##AUDIO.BITRATE#:", info); + config.station.bitrate = atoi(info) / 1000; + netserver.requestOnChange(BITRATE, 0); +} + +void audio_showstation(const char *info) { + if (strlen(info) > 0) { + display.title(info); + if (player.requesToStart) { + telnet.info(); + player.requesToStart = false; + } else { + telnet.printf("##CLI.ICY0#: %s\n", info); + } + } +} + +void audio_showstreamtitle(const char *info) { + if (strlen(info) > 0) { + display.title(info); + if (player.requesToStart) { + telnet.info(); + player.requesToStart = false; + } else { + telnet.printf("##CLI.META#: %s\n", info); + } + } +} diff --git a/yoRadio/config.cpp b/yoRadio/config.cpp new file mode 100644 index 0000000..a51a465 --- /dev/null +++ b/yoRadio/config.cpp @@ -0,0 +1,328 @@ +#include "config.h" +#include +#include + +Config config; + +void Config::init() { + eepromRead(EEPROM_START, store); + if (store.config_set != 4256) setDefaults(); + //if (!SPIFFS.begin(false, "/spiffs", 30)) { + if (!SPIFFS.begin(false)) { + return; + } + ssidsCount = 0; + initPlaylist(); + if (store.lastStation == 0 && store.countStation > 0) { + store.lastStation = 1; + save(); + } + loadStation(store.lastStation); +} + +template int Config::eepromWrite(int ee, const T& value) { + const byte* p = (const byte*)(const void*)&value; + int i; + EEPROM.begin(EEPROM_SIZE); + for (i = 0; i < sizeof(value); i++) + EEPROM.write(ee++, *p++); + EEPROM.commit(); + delay(20); + EEPROM.end(); + return i; +} + +template int Config::eepromRead(int ee, T& value) { + byte* p = (byte*)(void*)&value; + int i; + EEPROM.begin(EEPROM_SIZE); + for (i = 0; i < sizeof(value); i++) + *p++ = EEPROM.read(ee++); + EEPROM.end(); + return i; +} + +void Config::setDefaults() { + store.config_set = 4256; + store.volume = 12; + store.balance = 0; + store.trebble = 0; + store.middle = 0; + store.bass = 0; + store.lastStation = 0; + store.countStation = 0; + store.lastSSID = 0; + store.audioinfo = false; + store.smartstart = 2; +} + +void Config::save() { + eepromWrite(EEPROM_START, store); +} + +byte Config::setVolume(byte val, bool dosave) { + store.volume = val; + if (dosave) save(); + return store.volume; +} + +void Config::setTone(int8_t bass, int8_t middle, int8_t trebble) { + store.bass = bass; + store.middle = middle; + store.trebble = trebble; + save(); +} + +void Config::setSmartStart(byte ss) { + if (store.smartstart < 2) { + store.smartstart = ss; + save(); + } +} + +void Config::setBalance(int8_t balance) { + store.balance = balance; + save(); +} + +byte Config::setLastStation(byte val) { + store.lastStation = val; + save(); + return store.lastStation; +} + +byte Config::setCountStation(byte val) { + store.countStation = val; + save(); + return store.countStation; +} + +byte Config::setLastSSID(byte val) { + store.lastSSID = val; + save(); + return store.lastSSID; +} + +void Config::indexPlaylist() { + File playlist = SPIFFS.open(PLAYLIST_PATH, "r"); + if (!playlist) { + return; + } + char sName[BUFLEN], sUrl[BUFLEN]; + int sOvol; + File index = SPIFFS.open(INDEX_PATH, "w"); + while (playlist.available()) { + uint32_t pos = playlist.position(); + if (parseCSV(playlist.readStringUntil('\n').c_str(), sName, sUrl, sOvol)) { + index.write((byte *) &pos, 4); + } + } + index.close(); + playlist.close(); +} + +void Config::initPlaylist() { + store.countStation = 0; + if (!SPIFFS.exists(INDEX_PATH)) indexPlaylist(); + + if (SPIFFS.exists(INDEX_PATH)) { + File index = SPIFFS.open(INDEX_PATH, "r"); + store.countStation = index.size() / 4; + index.close(); + save(); + } +} + +void Config::loadStation(uint16_t ls) { + char sName[BUFLEN], sUrl[BUFLEN]; + int sOvol; + if (store.countStation == 0) { + memset(station.url, 0, BUFLEN); + memset(station.name, 0, BUFLEN); + strncpy(station.name, "ёRadio", BUFLEN); + station.ovol = 0; + return; + } + if (ls > store.countStation) { + ls = 1; + } + File playlist = SPIFFS.open(PLAYLIST_PATH, "r"); + + File index = SPIFFS.open(INDEX_PATH, "r"); + index.seek((ls - 1) * 4, SeekSet); + uint32_t pos; + + index.readBytes((char *) &pos, 4); + + index.close(); + playlist.seek(pos, SeekSet); + if (parseCSV(playlist.readStringUntil('\n').c_str(), sName, sUrl, sOvol)) { + memset(station.url, 0, BUFLEN); + memset(station.name, 0, BUFLEN); + strncpy(station.name, sName, BUFLEN); + strncpy(station.url, sUrl, BUFLEN); + station.ovol = sOvol; + setLastStation(ls); + } + playlist.close(); +} + +void Config::fillPlMenu(char plmenu[][40], int from, byte count) { + int ls = from; + byte c = 0; + bool finded = false; + char sName[BUFLEN], sUrl[BUFLEN]; + int sOvol; + if (store.countStation == 0) { + return; + } + File playlist = SPIFFS.open(PLAYLIST_PATH, "r"); + File index = SPIFFS.open(INDEX_PATH, "r"); + while (true) { + if (ls < 1) { + ls++; + c++; + continue; + } + if (!finded) { + index.seek((ls - 1) * 4, SeekSet); + uint32_t pos; + index.readBytes((char *) &pos, 4); + finded = true; + index.close(); + playlist.seek(pos, SeekSet); + } + while (playlist.available()) { + if (parseCSV(playlist.readStringUntil('\n').c_str(), sName, sUrl, sOvol)) { + strlcpy(plmenu[c], sName, 39); + c++; + } + if (c >= count) break; + } + break; + } + playlist.close(); +} + +bool Config::parseCSV(const char* line, char* name, char* url, int &ovol) { + char *tmpe; + const char* cursor = line; + char buf[4]; + tmpe=strstr(cursor, "\t"); + if(tmpe==NULL) return false; + strlcpy(name, cursor, tmpe-cursor+1); + if(strlen(name)==0) return false; + cursor=tmpe+1; + tmpe=strstr(cursor, "\t"); + if(tmpe==NULL) return false; + strlcpy(url, cursor, tmpe-cursor+1); + if(strlen(url)==0) return false; + cursor=tmpe+1; + if (strlen(cursor) == 0) return false; + strlcpy(buf, cursor, 3); + ovol = atoi(buf); + return true; +} + +bool Config::parseJSON(const char* line, char* name, char* url, int &ovol) { + char* tmps, *tmpe; + const char* cursor = line; + char port[8], host[254], file[254]; + tmps=strstr(cursor, "\":\""); + if(tmps==NULL) return false; + tmpe=strstr(tmps, "\",\""); + if(tmpe==NULL) return false; + strlcpy(name, tmps+3, tmpe-tmps-3+1); + if(strlen(name)==0) return false; + cursor=tmpe+3; + tmps=strstr(cursor, "\":\""); + if(tmps==NULL) return false; + tmpe=strstr(tmps, "\",\""); + if(tmpe==NULL) return false; + strlcpy(host, tmps+3, tmpe-tmps-3+1); + if(strlen(host)==0) return false; + if(strstr(host,"http://")==NULL && strstr(host,"https://")==NULL) { + sprintf(file, "http://%s", host); + strlcpy(host, file, strlen(file)+1); + } + cursor=tmpe+3; + tmps=strstr(cursor, "\":\""); + if(tmps==NULL) return false; + tmpe=strstr(tmps, "\",\""); + if(tmpe==NULL) return false; + strlcpy(file, tmps+3, tmpe-tmps-3+1); + cursor=tmpe+3; + tmps=strstr(cursor, "\":\""); + if(tmps==NULL) return false; + tmpe=strstr(tmps, "\",\""); + if(tmpe==NULL) return false; + strlcpy(port, tmps+3, tmpe-tmps-3+1); + int p = atoi(port); + if(p>0){ + sprintf(url, "%s:%d%s", host, p, file); + }else{ + sprintf(url, "%s%s", host, file); + } + cursor=tmpe+3; + tmps=strstr(cursor, "\":\""); + if(tmps==NULL) return false; + tmpe=strstr(tmps, "\"}"); + if(tmpe==NULL) return false; + strlcpy(port, tmps+3, tmpe-tmps-3+1); + ovol = atoi(port); + return true; +} + +bool Config::parseWsCommand(const char* line, char* cmd, char* val, byte cSize) { + char *tmpe; + tmpe=strstr(line, "="); + if(tmpe==NULL) return false; + memset(cmd, 0, cSize); + strlcpy(cmd, line, tmpe-line+1); + if (strlen(tmpe+1) == 0) return false; + memset(val, 0, cSize); + strlcpy(val, tmpe+1, tmpe+1-line+1); + return true; +} + +bool Config::parseSsid(const char* line, char* ssid, char* pass) { + char *tmpe; + tmpe=strstr(line, "\t"); + if(tmpe==NULL) return false; + uint16_t pos= tmpe-line; + if (pos > 19 || strlen(line) > 61) return false; + memset(ssid, 0, 20); + strlcpy(ssid, line, pos + 1); + memset(pass, 0, 40); + strlcpy(pass, line + pos + 1, strlen(line) - pos); + return true; +} + +bool Config::saveWifi(const char* post) { + File file = SPIFFS.open(SSIDS_PATH, "w"); + if (!file) { + return false; + } else { + file.print(post); + file.close(); + ESP.restart(); + } +} + +bool Config::initNetwork() { + File file = SPIFFS.open(SSIDS_PATH, "r"); + if (!file || file.isDirectory()) { + return false; + } + char ssidval[20], passval[40]; + byte c = 0; + while (file.available()) { + if (parseSsid(file.readStringUntil('\n').c_str(), ssidval, passval)) { + strlcpy(ssids[c].ssid, ssidval, 20); + strlcpy(ssids[c].password, passval, 40); + ssidsCount++; + c++; + } + } + file.close(); +} diff --git a/yoRadio/config.h b/yoRadio/config.h new file mode 100644 index 0000000..0dd5331 --- /dev/null +++ b/yoRadio/config.h @@ -0,0 +1,78 @@ +#ifndef config_h +#define config_h +#include "Arduino.h" + +#define EEPROM_SIZE 1024 +#define EEPROM_START 0 +#define BUFLEN 140 +#define PLAYLIST_PATH "/data/playlist.csv" +#define SSIDS_PATH "/data/wifi.csv" +#define TMP_PATH "/data/tmpfile.txt" +#define INDEX_PATH "/data/index.dat" + +struct config_t +{ + unsigned int config_set; //must be 4256 + byte volume; + int8_t balance; + int8_t trebble; + int8_t middle; + int8_t bass; + uint16_t lastStation; + uint16_t countStation; + byte lastSSID; + bool audioinfo; + byte smartstart; +}; + +struct station_t +{ + char name[BUFLEN]; + char url[BUFLEN]; + char title[BUFLEN]; + uint16_t bitrate; + int ovol; +}; + +struct neworkItem +{ + char ssid[20]; + char password[40]; +}; + +class Config { + public: + config_t store; + station_t station; + neworkItem ssids[5]; + byte ssidsCount; + public: + Config() {}; + void save(); + void init(); + byte setVolume(byte val, bool dosave); + void setTone(int8_t bass, int8_t middle, int8_t trebble); + void setBalance(int8_t balance); + byte setLastStation(byte val); + byte setCountStation(byte val); + byte setLastSSID(byte val); + bool parseCSV(const char* line, char* name, char* url, int &ovol); + bool parseJSON(const char* line, char* name, char* url, int &ovol); + bool parseWsCommand(const char* line, char* cmd, char* val, byte cSize); + bool parseSsid(const char* line, char* ssid, char* pass); + void loadStation(uint16_t station); + bool initNetwork(); + bool saveWifi(const char* post); + void setSmartStart(byte ss); + void initPlaylist(); + void indexPlaylist(); + void fillPlMenu(char plmenu[][40], int from, byte count); + private: + template int eepromWrite(int ee, const T& value); + template int eepromRead(int ee, T& value); + void setDefaults(); +}; + +extern Config config; + +#endif diff --git a/yoRadio/controls.cpp b/yoRadio/controls.cpp new file mode 100644 index 0000000..3c8b73a --- /dev/null +++ b/yoRadio/controls.cpp @@ -0,0 +1,101 @@ +#include "controls.h" +#include "options.h" +#include "config.h" +#include "player.h" +#include "display.h" + +#include +#include "OneButton.h" + +long encOldPosition = 0; + +ESP32Encoder encoder; +OneButton encbutton(ENC_BTNB, true); + +OneButton btnleft(BTN_LEFT, true); +OneButton btncenter(BTN_CENTER, true); +OneButton btnright(BTN_RIGHT, true); + +void initControls() { + ESP32Encoder::useInternalWeakPullResistors = UP; + encoder.attachHalfQuad(ENC_BTNL, ENC_BTNR); + encbutton.attachClick(onEncClick); + encbutton.attachDoubleClick(onEncDoubleClick); + encbutton.attachLongPressStart(onEncLPStart); + + btnleft.attachClick(onLeftClick); + btnleft.attachDoubleClick(onLeftDoubleClick); + + btncenter.attachClick(onEncClick); + btncenter.attachDoubleClick(onEncDoubleClick); + btncenter.attachLongPressStart(onEncLPStart); + + btnright.attachClick(onRightClick); + btnright.attachDoubleClick(onRightDoubleClick); +} + +void loopControls() { + encbutton.tick(); + encoderLoop(); + yield(); +} + +void encoderLoop() { + long encNewPosition = encoder.getCount() / 2; + if (encNewPosition != 0 && encNewPosition != encOldPosition) { + encOldPosition = encNewPosition; + encoder.setCount(0); + controlsEvent(encNewPosition > 0); + } +} + +void onEncClick() { + if (display.mode == PLAYER) { + player.toggle(); + } + if (display.mode == STATIONS) { + display.swichMode(PLAYER); + player.play(display.currentPlItem); + } +} + +void onEncDoubleClick() { + display.swichMode(display.mode == PLAYER ? STATIONS : PLAYER); +} + +void onEncLPStart() { + display.swichMode(display.mode == PLAYER ? STATIONS : PLAYER); +} + +void controlsEvent(bool toRight) { + if (display.mode != STATIONS) { + display.swichMode(VOL); + player.stepVol(toRight); + } + if (display.mode == STATIONS) { + int p = toRight ? display.currentPlItem + 1 : display.currentPlItem - 1; + if (p < 1) p = 1; + if (p > config.store.countStation) p = config.store.countStation; + display.currentPlItem = p; + display.clear(); + display.drawPlaylist(); + } +} + +void onLeftClick() { + controlsEvent(false); +} + +void onLeftDoubleClick() { + display.swichMode(PLAYER); + player.prev(); +} + +void onRightClick() { + controlsEvent(true); +} + +void onRightDoubleClick() { + display.swichMode(PLAYER); + player.next(); +} diff --git a/yoRadio/controls.h b/yoRadio/controls.h new file mode 100644 index 0000000..ca3bb29 --- /dev/null +++ b/yoRadio/controls.h @@ -0,0 +1,18 @@ +#ifndef controls_h +#define controls_h + +void initControls(); +void loopControls(); +void onEncClick(); +void onEncDoubleClick(); +void onEncLPStart(); +void encoderLoop(); + +void controlsEvent(bool toRight); + +void onLeftClick(); +void onLeftDoubleClick(); +void onRightClick(); +void onRightDoubleClick(); + +#endif diff --git a/yoRadio/data/data/wifi.csv b/yoRadio/data/data/wifi.csv new file mode 100644 index 0000000..e69de29 diff --git a/yoRadio/data/www/dragpl.js b/yoRadio/data/www/dragpl.js new file mode 100644 index 0000000..90c4b69 --- /dev/null +++ b/yoRadio/data/www/dragpl.js @@ -0,0 +1,40 @@ +let dragged; +let id; +let index; +let indexDrop; +let list; + +document.addEventListener("dragstart", ({target}) => { + dragged = target.parentNode; + id = target.parentNode.id; + list = target.parentNode.parentNode.children; + for(let i = 0; i < list.length; i += 1) { + if(list[i] === dragged){ + index = i; + } + } +}); + +document.addEventListener("dragover", (event) => { + event.preventDefault(); +}); + +document.addEventListener("drop", ({target}) => { + if(target.parentNode.className == "pleitem" && target.parentNode.id !== id) { + dragged.remove( dragged ); + for(let i = 0; i < list.length; i += 1) { + if(list[i] === target.parentNode){ + indexDrop = i; + } + } + if(index > indexDrop) { + target.parentNode.before( dragged ); + } else { + target.parentNode.after( dragged ); + } + let items=document.getElementById('pleditorcontent').getElementsByTagName('li'); + for (let i = 0; i <= items.length-1; i++) { + items[i].getElementsByTagName('span')[0].innerText=("00"+(i+1)).slice(-3); + } + } +}); diff --git a/yoRadio/data/www/elogo.png b/yoRadio/data/www/elogo.png new file mode 100644 index 0000000..bdc932f Binary files /dev/null and b/yoRadio/data/www/elogo.png differ diff --git a/yoRadio/data/www/elogo100.png b/yoRadio/data/www/elogo100.png new file mode 100644 index 0000000..8e5cdc5 Binary files /dev/null and b/yoRadio/data/www/elogo100.png differ diff --git a/yoRadio/data/www/index.html b/yoRadio/data/www/index.html new file mode 100644 index 0000000..f5740f0 --- /dev/null +++ b/yoRadio/data/www/index.html @@ -0,0 +1,130 @@ + + + + + + + + + + ёRadio + + + +
+ + +
+
+
 
+
 
+
+
+
+
+
+
+
+
+
+ + +
+
volume: 0
+
bitrate: 0kBit
+
rssi: 0dBm
+
+
    +
+
+
+
+

WiFi Settings

+
    +
  • + 1. +
    +
    +
  • +
  • + 2. +
    +
    +
  • +
  • + 3. +
    +
    +
  • +
  • + 4. +
    +
    +
  • +
  • + 5. +
    +
    +
  • +
+
+
Cancel
+
Save
+
+
+ +
+
powered by ёRadio | v%VERSION%
+
+ + + + diff --git a/yoRadio/data/www/script.js b/yoRadio/data/www/script.js new file mode 100644 index 0000000..9d91902 --- /dev/null +++ b/yoRadio/data/www/script.js @@ -0,0 +1,347 @@ +var gateway = `ws://${window.location.hostname}/ws`; +var websocket; +var currentItem = 0; + +window.addEventListener('load', onLoad); +function initWebSocket() { + console.log('Trying to open a WebSocket connection...'); + websocket = new WebSocket(gateway); + websocket.onopen = onOpen; + websocket.onclose = onClose; + websocket.onmessage = onMessage; +} +function onOpen(event) { + console.log('Connection opened'); +} +function onClose(event) { + console.log('Connection closed'); + document.getElementById('playbutton').setAttribute("class", "stopped"); + setTimeout(initWebSocket, 2000); +} +function cleanString(input) { + var output = ""; + for (var i=0; i= 160 && input.charCodeAt(i) <= 255) { + output += input.charAt(i); + } + } + return output; +} +function onMessage(event) { + var data = JSON.parse(event.data); + if(data.nameset) document.getElementById('nameset').innerHTML = data.nameset; + if(data.meta) document.getElementById('meta').innerHTML = cleanString(data.meta); + if(data.vol) { + setVolRangeValue(document.getElementById('volrange'),data.vol); + } + if(data.current) setCurrentItem(data.current); + if(data.file) generatePlaylist(data.file); + if(data.bitrate) document.getElementById('bitinfo').innerText = 'bitrate: '+data.bitrate+'kBits'; + if(data.rssi) document.getElementById('rsiinfo').innerText = 'rssi: '+data.rssi+'dBm'; + if(data.mode) { + document.getElementById('playbutton').setAttribute("class",data.mode); + } + if(data.bass) { + setVolRangeValue(document.getElementById('eqbass'),data.bass); + setVolRangeValue(document.getElementById('eqmiddle'),data.middle); + setVolRangeValue(document.getElementById('eqtreble'),data.trebble); + } + if(data.balance) { + setVolRangeValue(document.getElementById('eqbal'),data.balance); + } +} +function scrollToCurrent(){ + var pl = document.getElementById('playlist'); + var lis = pl.getElementsByTagName('li'); + var plh = pl.offsetHeight; + var plt = pl.offsetTop; + var topPos = 0; + var lih = 0; + for (var i = 0; i <= lis.length - 1; i++) { + if(i+1==currentItem) { + topPos = lis[i].offsetTop; + lih = lis[i].offsetHeight; + } + } + pl.scrollTo({ + top: topPos-plt-plh/2+lih/2, + left: 0, + behavior: 'smooth' + }); +} +function setCurrentItem(item){ + currentItem=item; + var pl = document.getElementById('playlist'); + var lis = pl.getElementsByTagName('li'); + for (var i = 0; i <= lis.length - 1; i++) { + lis[i].removeAttribute('class'); + if(i+1==currentItem) { + lis[i].setAttribute("class","active"); + + } + } + scrollToCurrent(); +} +function generatePlaylist(lines){ + var ul = document.getElementById('playlist'); + ul.innerHTML=""; + lines.forEach((line, index) => { + li = document.createElement('li'); + li.setAttribute('onclick','playStation(this);'); + li.setAttribute('attr-id', index+1); + li.setAttribute('attr-name', line.name); + li.setAttribute('attr-url', line.url); + li.setAttribute('attr-ovol', line.ovol); + if(index+1==currentItem){ + li.setAttribute("class","active"); + } + var span = document.createElement('span'); + span.innerHTML = index+1; + li.appendChild(document.createTextNode(line.name)); + li.appendChild(span); + ul.appendChild(li); + initPLEditor(); + }); + scrollToCurrent(); +} +function initPLEditor(){ + ple= document.getElementById('pleditorcontent'); + ple.innerHTML=""; + pllines = document.getElementById('playlist').getElementsByTagName('li'); + for (let i = 0; i <= pllines.length - 1; i++) { + plitem = document.createElement('li'); + plitem.setAttribute('class', 'pleitem'); + plitem.setAttribute('id', 'plitem'+i); + let pName = pllines[i].getAttribute('attr-name'); + let pUrl = pllines[i].getAttribute('attr-url'); + let pOvol = pllines[i].getAttribute('attr-ovol'); + plitem.innerHTML = ''+("00"+(i+1)).slice(-3)+'\ + \ + \ + \ + '; + ple.appendChild(plitem); + } +} +function playStation(el){ + let lis = document.getElementById('playlist').getElementsByTagName('li'); + + for (let i = 0; i <= lis.length - 1; i++) { + lis[i].removeAttribute('class'); + } + el.setAttribute("class","active"); + id=el.getAttribute('attr-id'); + xhr = new XMLHttpRequest(); + xhr.open("POST","/",true); + xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xhr.send("playstation="+id); +} +function setVolRangeValue(el, val=null){ + let slave = el.getAttribute('data-slaveid'); + if(val){ + el.value = val; + document.getElementById(slave).innerText=val; + } + document.getElementById(slave).innerText=el.value; + var value = (el.value-el.min)/(el.max-el.min)*100; + el.style.background = 'linear-gradient(to right, #bfa73e 0%, #bfa73e ' + value + '%, #272727 ' + value + '%, #272727 100%)'; +} + +function onRangeVolChange(value) { + xhr = new XMLHttpRequest(); + xhr.open("POST","/",true); + xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xhr.send("vol=" + value+"&"); +} +function onRangeEqChange(el){ + let trebble = document.getElementById('eqtreble').value; + let middle = document.getElementById('eqmiddle').value; + let bass = document.getElementById('eqbass').value; + xhr = new XMLHttpRequest(); + xhr.open("POST","/",true); + xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xhr.send("trebble=" + trebble + "&middle=" + middle + "&bass=" + bass + "&"); +} +function onRangeBalChange(el){ + xhr = new XMLHttpRequest(); + xhr.open("POST","/",true); + xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xhr.send("ballance=" + el.value+"&"); +} +function showSettings(){ + document.getElementById('pleditorwrap').hidden=true; + document.getElementById('settings').hidden=false; +} +function showEditor(){ + document.getElementById('settings').hidden=true; + initPLEditor(); + document.getElementById('pleditorwrap').hidden=false; +} +function doCancel() { + document.getElementById('settings').hidden=true; + document.getElementById('pleditorwrap').hidden=true; +} +function doExport() { + window.open("/data/playlist.csv"); +} +function doUpload(finput) { + var formData = new FormData(); + formData.append("plfile", finput.files[0]); + var xhr = new XMLHttpRequest(); + xhr.open("POST","/upload",true); + xhr.send(formData); +} +function doAdd(){ + let ple=document.getElementById('pleditorcontent'); + let plitem = document.createElement('li'); + let cnt=ple.getElementsByTagName('li'); + plitem.setAttribute('class', 'pleitem'); + plitem.setAttribute('id', 'plitem'+(cnt.length)); + plitem.innerHTML = ''+("00"+(cnt.length+1)).slice(-3)+'\ + \ + \ + \ + '; + ple.appendChild(plitem); + ple.scrollTo({ + top: ple.scrollHeight, + left: 0, + behavior: 'smooth' + }); +} +function doRemove(){ + let items=document.getElementById('pleditorcontent').getElementsByTagName('li'); + let pass=[]; + for (let i = 0; i <= items.length - 1; i++) { + if(items[i].getElementsByTagName('span')[1].getElementsByTagName('input')[0].checked) { + pass.push(items[i]); + } + } + if(pass.length==0) { + alert('Choose something first'); + return; + } + for (var i = 0; i < pass.length; i++) + { + pass[i].remove(); + } + items=document.getElementById('pleditorcontent').getElementsByTagName('li'); + for (let i = 0; i <= items.length-1; i++) { + items[i].getElementsByTagName('span')[0].innerText=("00"+(i+1)).slice(-3); + } +} +function showEqualizer(isshowing){ + document.getElementById('equalizerbg').classList.toggle('hidden'); +} +function submitWiFi(){ + var items=document.getElementById("credentialwrap").getElementsByTagName("li"); + var output=""; + for (var i = 0; i <= items.length - 1; i++) { + inputs=items[i].getElementsByTagName("input"); + if(inputs[0].value == "") continue; + output+=inputs[0].value+"\t"+inputs[1].value+"\n"; + } + if(output!=""){ // Well, let's say, quack. + xhr = new XMLHttpRequest(); + xhr.open("POST","/",true); + xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xhr.send("wifisettings="+output); + document.getElementById("settings").innerHTML="

Settings saved. Rebooting...

"; + setTimeout(function(){ window.location.reload(); }, 10000); + } +} +function submitPlaylist(){ + var items=document.getElementById("pleditorcontent").getElementsByTagName("li"); + var output=""; + for (var i = 0; i <= items.length - 1; i++) { + inputs=items[i].getElementsByTagName("input"); + if(inputs[1].value == "" || inputs[2].value == "") continue; + let ovol = inputs[3].value; + if(ovol < -30) ovol = -30; + if(ovol > 30) ovol = 30; + output+=inputs[1].value+"\t"+inputs[2].value+"\t"+inputs[3].value+"\n"; + } + xhr = new XMLHttpRequest(); + xhr.open("POST","/",true); + xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xhr.send("playlist="+output.slice(0, -1)); + document.getElementById('pleditorwrap').hidden=true; +} +function initSliers(){ + var sliders = document.getElementsByClassName('slider'); + for (var i = 0; i <= sliders.length - 1; i++) { + sliders[i].oninput = function() { + setVolRangeValue(this); + }; + setVolRangeValue(sliders[i], 0); + } + return; + var volslider = document.getElementById("volrange"); + var eqvolslider = document.getElementById("eqvol"); + var balslider = document.getElementById("eqbal"); + volslider.oninput = function() { + setVolRangeValue(this); + }; + eqvolslider.oninput = function() { + setVolRangeValue(this); + }; + balslider.oninput = function() { + setVolRangeValue(this); + }; + setVolRangeValue(volslider, 0); + setVolRangeValue(eqvolslider, 0); + setVolRangeValue(balslider, 0); +} +function onLoad(event) { + initWebSocket(); + initButton(); + initSliers(); + document.getElementById("volrange").addEventListener("wheel", function(e){ + if (e.deltaY < 0){ + this.valueAsNumber += 1; + }else{ + this.value -= 1; + } + websocket.send('volume='+this.value); + e.preventDefault(); + e.stopPropagation(); + }, {passive: false}); +} +function initButton() { + document.getElementById('playbutton').addEventListener('click', playbutton); + document.getElementById('prevbutton').addEventListener('click', prevbutton); + document.getElementById('nextbutton').addEventListener('click', nextbutton); + document.getElementById('volmbutton').addEventListener('click', volmbutton); + document.getElementById('volpbutton').addEventListener('click', volpbutton); +} + +function playercommand(cmd){ + xhr = new XMLHttpRequest(); + xhr.open("POST","/",true); + xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xhr.send(cmd+"=1"); +} +function playbutton(){ + var btn=document.getElementById('playbutton'); + if(btn.getAttribute("class")=="stopped") { + btn.setAttribute("class", "playing"); + playercommand("start"); + return; + } + if(btn.getAttribute("class")=="playing") { + btn.setAttribute("class", "stopped"); + playercommand("stop"); + } +} +function prevbutton(){ + playercommand("prev"); +} +function nextbutton(){ + playercommand("next"); +} +function volmbutton(){ + playercommand("volm"); +} +function volpbutton(){ + playercommand("volp"); +} diff --git a/yoRadio/data/www/style.css b/yoRadio/data/www/style.css new file mode 100644 index 0000000..07e40b0 --- /dev/null +++ b/yoRadio/data/www/style.css @@ -0,0 +1,505 @@ +html, body {margin: 0; padding: 0; height: 100%; } +body { + background-color: #000; + color: #fff; + font-family: Times, "Times New Roman", serif; +} +a { color: #e3d25f; text-decoration: none; font-weight: bold } +a:hover { text-decoration: underline } +.logo { + display: block; + width: 155px; + height: 100px; + background-color: transparent; + background-image: url(elogo100.png); + background-position: center center; + background-repeat: no-repeat; + overflow: hidden; + text-indent: -2022px; + z-index: 2; + position: relative; +} +#nameset { + text-transform: uppercase; + font-weight: bold; + font-size: 22px; + color: #e3d25f; + margin: 20px 0 8px 0; +} +#meta { + text-transform: uppercase; + font-size: 16px; + margin-bottom: 14px; + text-align: center; +} +.content { + display: flex; + max-width: 960px; + margin: 0 auto; + flex-direction: column; + align-items: center; + padding: 30px; + height: 100%; + box-sizing: border-box; + position: relative; +} +.playerwrap { + width: 100%; + flex: 1; + display: flex; + position: relative; +} +.player { + display: flex; + flex-direction: column; + align-items: center; + flex: 1; + max-width: 480px; + margin: 0 auto; +} +#equalizerwrap { + position: relative; + display: flex; + flex: 1; + flex-direction: column; + align-items: center; +} + +#equalizerbg { + position: absolute; + left: 0; + right: 0; + bottom: 0; + top: 2px; + background: rgba(0, 0, 0, 0.3); + z-index: 7; +} +#equalizerbg.hidden { + display: none; +} +#equalizer { + padding: 20px; + background: #000; + box-shadow: #000 0 10px 20px; + list-style-type: none; + margin: 0; +} +#equalizer li { + margin-bottom: 10px; + color: #ccc; +} +#equalizer li input { + width: 100%; + height: 25px!important; + border-radius: 13px; +} +.eqinfo { + float: right; +} +#settings, #pleditorwrap { + position: absolute; + background: rgba(0,0,0,1); + top: 0; + right: 0; + left: 0; + bottom: 0; + padding: 30px 10px 20px 0; + overflow-y: auto; + box-sizing: border-box; +} + +#credentialwrap { + max-width: 520px; + margin: 0 auto; + padding: 0; + list-style-type: none; +} + +#settings h2, #pleditor h2{ + text-align: center; + padding-top: 40px 0; + text-transform: uppercase; + font-weight: bold; + font-size: 26px; + color: #e3d25f; + overflow-y: auto; +} +#pleditor h2 { + line-height: 48px; + margin: 5px 0; + padding-left: 48px; +} +#pleditor h2 span { + display: block; + float: right; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAA2FBMVEUAAAC+pDnm12HXxVHk1l65mjLJskO9oTjo3GPEqD3cylWyljDfz1rOuknBpjq+oDbt4Gjz6W6zli/17HC1mTLw5Wupjyq8nTOuky3Tv07p22PGq0G1mDDx5Wzw42vj1l7ez1nFqUHCpz64mjKzljDx5WzHq0G1lzHby1a2mTHbyla7nTTXxVHUwU/LtEXIr0HFqj3DpzrApDm9nzW5mzKyli/063Dz6G7v5Wvi013g0FvdzVfZx1POuUi+ojjs4Gbo22PVwk/RvkzQu0qvky2skSyqjyvl119lX1m8AAAAKXRSTlMAAgKgoaCfo6KioaGgn5+fLaSko6OioqGhoEItLfX19fX19fX1pEJCLVUTdPcAAAEKSURBVDjL1ZLZdoJADEDJoKCiqHVt7b7DgOybIO72//+o9FSdSeWhr+YtuXdyMjkRLik6BOdwi/Obuy5BfPTygd7fx1GPcFym2ivfg7xFYTggJ656vnYFAm90wyiukQOv5/TImdGLF8sK+eWO5/ePnBmDxTJpkoJLdk6rjDOj9pWkIgHJcjzGkVGZ6cZ1y7LdEePYaOqrzdRy2jzHhmjOp7bK+Lmw/kcDlx8R84axnkuqSzUFynlmbiSAtq8FYyj9gmG2CgDVINgO4XwJabb64YWhbPc7BcqWdCjCeL/TZMBrTtJMPJVgGPh5nTc6DzNd5A+mT92nCTq5xwY+Ofn5HQ/x+fdoJ8IFxTeZ6Bp9c7k3QwAAAABJRU5ErkJggg=='); + width: 48px; + height: 48px; + background-position: center center; + background-repeat: no-repeat; + cursor: pointer; + border-radius: 24px; +} +#pleditor h2 span:hover { + background-color: #272727; +} +#pleheader { + display: flex; +} +#pleheader span { + padding: 8px; + font-weight: bold; + box-sizing: border-box; + color: #e3d25f!important; +} +#pleheader .space { + width: 70px; +} +#pleditor { + display: flex; + flex-direction: column; + justify-content: center; + min-width: 900px; + height: 100%; +} +#settings::-webkit-scrollbar, #pleditorcontent::-webkit-scrollbar { + width: 5px; + height: 8px; + background-color: #000; +} +#settings::-webkit-scrollbar-thumb, #pleditorcontent::-webkit-scrollbar-thumb { + background: #e3d25f; +} +#pleditorcontent { + flex: 1 1 auto; + overflow-y: scroll; + height: 0px; + margin: 0; + padding: 0 0px 0 0; + list-style-type: none; + border: #e3d25f 2px solid; + box-sizing: border-box; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAABAAgMAAACrYYWUAAAACVBMVEUnJycAAAAtLS3JOrneAAAAGElEQVQI12NYtYKBZgBoOAiFhtAIAQ0HALK+GReOr3bpAAAAAElFTkSuQmCC'); +} +.grabbable { + cursor: move; + cursor: grab; + cursor: -moz-grab; + cursor: -webkit-grab; +} +.pleitem { + display: flex; + justify-content: space-between; + background: #000; +} +.pleitem span { + display: flex; + flex-direction: column; + justify-content: center; + text-align: right; + color: #d7d7d7; + width: 40px; + padding: 0 8px; + box-sizing: border-box; + font-size: 14px; + border: #2d2d2d 1px solid; +} +.pleitem input { + font-family: Times, "Times New Roman", serif; + background: #000; + color: #e3d25f; + height: 32px; + line-height: 32px; + text-indent: 8px; + font-size: 18px; + border: #2d2d2d 1px solid; + font-weight: normal; + box-sizing: border-box; + margin-top: 4px; + border-radius: 0px; + outline: none; + margin: 0; +} +#pleditorcontent li:nth-child(odd) input, #pleditorcontent li:nth-child(odd) span { + background: #272727; +} +.plecheck { + width: 30px!important; +} +.plecheck input { + line-height: 10px; + height: auto; +} +.plename { + width: 300px; +} +.pleurl { + flex: 1; + color: #d7d7d7!important; +} +.pleovol { + width: 53px; + font-size: 16px!important; +} +#pleheader .pleovol { + width: 62px; +} +.credentialitem { + width: 100%; + display: flex; + justify-content: space-between; + margin-bottom: 14px; +} +.credentialitem span { + display: flex; + flex-direction: column; + justify-content: center; + font-weight: bold; +} +.credentialitem .textinput { + width: 47%; +} +.credentialitem input { + width: 100%; + box-sizing: border-box; + background: #272727; + color: #e3d25f; + padding: 6px 12px; + font-size: 20px; + border: #2d2d2d 1px solid; + font-weight: normal; + box-sizing: border-box; + margin-top: 4px; + border-radius: 0px; +} +.credentialitem label { + text-transform: uppercase; + color: #ccc; +} +.credentialitem input:focus { + outline: none; +} +.formbuttons { + display: flex; + justify-content: space-around; + max-width: 280px; + width: 100%; + margin: 0 auto; + margin-top: 30px; +} +#pleditor .formbuttons { + max-width: 100%; +} +.button { + padding: 8px 22px; + font-size: 18px; + border: #e3d25f 2px solid; + font-weight: normal; + box-sizing: border-box; + border-radius: 20px; + cursor: pointer; + text-align: center; + min-width: 120px; + margin-left: 20px; + text-transform: uppercase; + color: #e3d25f; + background: #272727; +} +.button:hover { + background: #e3d25f; + color: #000; +} +.button:active { + position: relative; + top: 2px; +} +#save_button { + background: #e3d25f; + color: #000; +} +#navbar { + display: flex; + width: 400px; + justify-content: space-between; + position: absolute; + top: 0; + padding: 30px 0 0; + box-sizing: border-box; +} +.playerbyttonwrap { + display: flex; + width: 400px; + justify-content: space-between; + margin-top: 14px; +} +.playerbytton, .stopped, .playing { + background-color: transparent; + background-position: center center; + background-repeat: no-repeat; + width: 48px; + height: 48px; + cursor: pointer; + border: #e3d25f 2px solid; + border-radius: 26px; + -webkit-tap-highlight-color: rgba(255, 255, 255, 0); +} +.navbutton { + border-color: transparent; +} +.navbutton:hover { + border-color: #e3d25f; +} +.playerbytton:hover, .stopped:hover, .playing:hover { + background-color: #272727; +} +.playerbytton:active, .stopped:active, .playing:active { + position: relative; + top: 2px; +} +.stopped { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAvVBMVEUAAADg0Vvez1fYx1Lr4V/q3l7l2F3o3F/h01nw52rr4WDx53Dez1nZyVL69o707Xju5Gbt42X38oXx6XP584n17X3t42v584fw6HX27nzs4Wzdz1Tx6HTw523h0lvez1naylXl11/m2GHj1V7f0FrdzVjq3WTn2mLZx1L38IDv5Wjo3GPi01zczFfcy1bz6m/s4Wf7+JP69o317nnt4mju42Pu5W7x6Gzw5Wvr32Xl2F/59If07HP38oTj1lmRTpVfAAAAHnRSTlMAEkkE/taStm377SkpKe3t7e3W1ra2tpKSbW1tSQQSPakHAAAA9klEQVQ4y82R2XaCMBRFtdLaWjvXWRMCQkhQQcIkDv//WSaRpWI0vnpe9+aek0XtgfL/fkdYLnt1vUCiz8FEJwQBiX7HOiHLA7Lu3JwSZTmN45SQfv2GkOdxGLpuyr6GjasCjcOV6/sWTtnf0xVhLTm2LGRb6abbVoUjn4Epom8vl1O2kheSw/kC0Z/XRlUQXGAAOTc9EyXPlSnEx4fPp5J73s64EMp66MxNzoHRGlUrGDrj0DCUkcy2geD8/AInzQ/lmWxWcrMQ69RsyvN20pLvUwXJQZiU5UoCfh669FiuCo6D6alcTVbQb16uEcTv0aXZrj1u9gzXJnV9YpvtAAAAAElFTkSuQmCC'); +} +.playing { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAP1BMVEUAAADv42vy52369Ivu4Wnz6W7x5Wv162/79o/7+JLt32js3Gb48YP584j484Xn12L17Xv17XHq2mXz6XTk0mCFQY+JAAAAAXRSTlMAQObYZgAAALFJREFUOMvlz90OgyAMhmH+CqJQcXr/17p+3cySsjuwRyTvQ5O6Z815nmOM1pxrzD3KWHBd+/7aBPCaUvLegqEdoGufgfaNAdDzYoH2tTsXPfoEmvYb5BAmgPwFWXqoFqCnFAUgB5qAdgXIVCxgdO8VkPQJ/K5fAvoxA/QMgG433NfjuIr/hRYL0D+gHNJDtAA5KDiK9PwfUMUGku67BchUAChIT2zAgqm16itG5uYeNW/M9AgMwr5N0gAAAABJRU5ErkJggg=='); +} +#prevbutton { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAABR1BMVEUAAADRu03Zxz7g0Fbn2lvx6Hbp22jXxFC/qTvo2WS2nTXFrkPt4WfbyUHl11rk1Vfs4Wbn11jj1FPYxT/27X/x53LVwj3TwDro2F3Wwz/byUnRvTzp3WbSvz/k1Fvq3GbezVTNuDvOu0DItDnh0FvJszzTv0rErjjezFrWwk7Xw1K/qDjbyVi7pDi3nzbZxVXTvlDBqUHKs0jezETcyUC9pT3t4GPq3FzWwVLj1E7FrUO/pz7u4mbi0F3o2VzcyFjUv1HOt0vIsEa7ojvTvjPNtyv17nzz6XTw5Wvi0l3ey1rax1Xl1VLXw1LSvU/Qu03h0Urgz0jDrEDVwjj38ILz6Xbx5m/q3Wnr3l/fzFvi0ljl1lbZxlLVwkDPu0DXxDvQuy7LtCrl1GHm1mDl1VrYxkzbyUfJs0XIsj/KtT3ItDvBqzvKsin8qlL8AAAAL3RSTlMAFv6t66tWVi0VCwX19e3t7Ozs7Ovr4uLU1MXFsbGvrZ6ejIx2dmRkWVk9PR4eFHLHxygAAAE0SURBVDjL7ZHndoJAFISVJCYxvffeKx0FxIYlKAhI1CT29Pb+v13KXV/C+QFzznyzZ+/d0EhD/feH/m4P2/AYuH4e3MzZHAt+cvwJbB6Amy22Y0M9pgkY6Hm/+yOuXeWtoJ4sUxjo5dzvxQrxrcq06NdfKCqdACCHgKkdrsPzMl1FwNVSslWh4sYDBpzoCdH+U91c0r16JR03SAw4zgbxq6o8TUtSVl9ItkyUk2SqDkC3u8798LJMS9ks05xH10N5OVUqAkBw0WP28wvlDJOJhfeFNxL1S88Y4Dh0yW3xg0F5Bm3ncjGhoFypAcB62ztfbhoZ0xTcMQ/qDUXRChiw/eUdxt7jlD98ZK3YeMWAbQXmelPQgu1MTxRqjwBYIn6s01k8fGQVA6IewrrdBYcOCY0EGgATKDjicNrzUgAAAABJRU5ErkJggg=='); +} +#nextbutton { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAABCFBMVEUAAAC+ojXKsDi0mC/awzndxjvXwTTgzUjRuTTUvDjj1VbUvDrHrjHEqjLXxEu6ny/Sv0vWwTXhzkXUvjXq3lzl1lDRujPQujLbxj7RujTcyEfMtTPXwT7byUjRuDnJsTLSukDEqzHHqzm5oC/JsD+1mjDbxDfVwDS0ly+7nDO1mDDYwjW4mjLizULEqTzBpTnAozjUvTG3mTHFqz7fyTzKsDnUvjTSvDDq3Vjl1Evj0UbHrkDdxjvCpzq8njTIsEHgyz7awza/oTbQuTTs4V3k1lPn2VDMt0bQuUXLtUXKskPVvz7VvTvJsTO7oDDXxUzYwkDYwTzDpjrFqjjErDPDqzK/ozLCqTEl328YAAAAJnRSTlMAFVgI7ez7+sWuq6uMY1ctFfXs7Ovr4uLU1LGxr62ennZ2PT0eHggzQnUAAAEdSURBVDjL7ZFncsIwFITtQEJ6771KNrIt4QoOdmwTAoSQXu5/kzwZJE1mcgT215u3366kkTaV1OK6HA+v1RohMd1vr4oxrp1X/gG6TVESD+39SwHkAmj2X7o74xKbfbz5p3o550QCvUGv1jrROVAUaYCqK3xNFDB4vLvtx5szmpalqWmmHTS/BgAWwAP4nuO92se6b5qmZbnf4dxyBSuA+w5lQbuKSr9eNz7xbiiBFvgJZQUHwAfbML7wXqMhAc95p4w++Qt623Jd8J87cESoAMhD/yxcEvF6d4T5JVVDnNCEx2FEEP8Jt8pnRpEAbBb4PM4BaCdngP4FhtmSPh7zET64mqwjeUQG8YnIxoX8LPVMiAsd3WhSmGhTSf0Cx6gshxH98P4AAAAASUVORK5CYII='); +} +#volpbutton { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAABp1BMVEUAAADTv03byVXSvkzax1PYxEvbyVLWw07Svkrj0Ufr31vt4WPr317q3V7m2lvj1Vjm2mHdzE3cy1DRu0PbylHXxEzYxlHWw0/u5Gbs4V7r31nu5Gft4mPq3Vnq31/n2lfm2Fjn21zl11fo3WDk1lbj1VXl2FrdzErh0VPg0VPYxEXfz1Lcykzf0FTayEzWwUfcy0/ezlbfz1nUwEvPuUXXxVLGqjvNt0Xv52rs4V3s313k0kjs4V/o2lTm1lDp21fq3Vvu5Gnn2FPr3l3k003s4mfq317q3mHs4mbk1FHbx0Hp3mHo21/cx0Lfz0zdy0jo22Hfz07h0VDj1Vfezk7h01fk2F/YxEfj1Fvh0Vbj1F3g0FjQuD7h1FzZxk7g0lvdzVfez1nTv0nNt0XGrTzaylbYyFTYx1PXxVHt42XayVXv5Wjs4mbu42Ps4WHs4Fzez1np3Fjn2VXn22Dcy1fi0lDgz03w52ru5Wjq3mLk1l3o21rdzlbk1VTayFTo2VDWxFDk1U/dyknv5WXu42Dg0Vvs4Frq3lfk1lfg0Vbp21TUwU3gzUTlFyGcAAAAZXRSTlMAASgEEVk8JhX+9O7k1LaWjo1kTElFGQ78/Pzv5+XX0sm7uLe0sKWjmJCPiYV9cmppT0AtHRQUCPr69vby7+/t6+rq6OXf39zYzMrJwry4tLGxqpqaj4iGhXdsbGZeXllVNDQkGQe+VJQAAAGqSURBVDjLzdJlc9tAEIDh15Jsx2zHThxsqGkDDadJmZmZuZVkmSkOYxl+dCVZmlEyU3/s5Pm2s7d7t3fHvuN656URbygXEBrkj15drmSj/JN4Kx9423lexNbEbs9ylSjRxfd27Lko4TTdtpKdYnrpNZaHhWYcmrqW0xkBKfME0wyJQr9zwIFstXhMxNV2z4yfB5n3d9ineJnTtMxXuXgN6LqJIVwQGCwlqAuspBRFlouXkxA6ALTyeLWZ0XXrEJK2oaZlOV3VBqAvCJEIkz/GGF84gknQ9HJZUdSi1sqhU3DiIC3lIdwLh62Bs3q5nk+l8gki7XCuF6H8gJbaHavDkp5X9fzaF72DH/xGh0e4a1YHabGqGAuUfBj6umF4iEl9//Gdp9R1Voz6rY0rSegJma8Q255gZGeCuheZz1pubUs1JuzuxXB/c4a7vrjjJsP5lHpcxNUxaMbuS675k74552cIrv4ueJG+j2KaJe67jZOn/WdpCk/pDZaRP6/YZezb+jCxXx/s+NONuT0/tr901n36gohNZI9kT7m2HaOB2eubZ7w0FP/I//cX2VZVBFBJqXUAAAAASUVORK5CYII='); +} +#volmbutton { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAABd1BMVEUAAAC4njLo11PYxDrm00/hzlDaxEnIrUDHrUG9ojnk0Uvn1lHk0U3SvTPWwTfp2lnj0E3gzUniz0/dyEXj0VHgzU/bxkbaxUjfzVLey1HHsDTaxlHFrTq7ojLTu0i9ozXUvUyxlyyulSzNtEXGrECzmTDTvjLeykLq3Vzp2lnaxT7QujHm01HRuzPm1FPdyELYxEDNuC/WwTvLtS/l1Vfjz1Dm1ljaxkTJszDaxUbKtDLWwULVwELfzFLKszjey1LOuD7AqS/Zw0zKtDy7oyzHsDu+pjK5oC7Wv0zayFTXwk7XxFHTvU3TvU2pjyu9ojq6nze1mzO0mTLKr0LApDy2nDXUvzSxljHp2Vbhz0jgy0PMs0HaxTy+ozu8oDi4nTbQujLTvTGymDDr3Vvo2FnfzE7gzEbcyUDSvDbVwTXi0VXaxEzFrD/EqD/IsD7Cpj7OuD2+pTm6oDm+pDe7ozbMtzW5oTTKtDPIsTO+pTPCqjLFrjGuky/WrPCGAAAAT3RSTlMAJ/7+/bOWGQ4E/vby7u3s5OLX1Mm5tqWQjo5bS0A9NyUlFRQSEf377+/u7uvn5ubf3NjV09HMycK7uLaxl46IiIV7bGxeWVVPTTQoHgUEcf+/bgAAAS1JREFUOMvNzkV3wkAYheGLS4sVKS4F6u7ubgQCQQtJ8Lr7jy90l55Mlj082/fOdwad5yAkmUMznEEGsr4p9p11k/v5ImvYNxp1INni3txYLwRI3df/2fDCV9gjfWDshVfIEKlvEgbLTPxVoQMGVkXiLsc0mx+J52kAEwsig5Gv73Q6EX+a1AJWKwCNsEcZPs0nUqWH+gqwZAHUagjImNbzVIkq3tY06O0GulQQCDR+eyyWzHmh1gOjtj8XFPEU9Ri7SOb14heiBYoqtvp11gHYzYBzDULD9+1+R1u0wFwPgDCEdnK5WjZ/Q7fbuA3iHFk6r9QBchVhEDZXaXkQkcwGCPzKK+Uh/BkPSLblVRdc5WMQ2TMmz5DpDETa2cplxQkJp/PlwRNIOgri//0AbVY1/4adyuMAAAAASUVORK5CYII='); +} +#playlistbutton { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAABTVBMVEUAAADr4GLo3GTSvUrn2mLVwlDh0lLq32Xm2l/f0Ffi013o3Gbs4Wrg0VvXxlDf0Frz7Hncx0Dz63jfzUjm2Vjo3F3x6HPq4GTfzkrbyUXk2Fnx6XXu5W7y63fo3WLk11rw6HPVvTvr4Wnr4Gnf0VTv5nLUvUDZyEzs4m3v5XHo3GbMsjri1Fzl2WLUwEnHrDjaylPdzlfQukbXx1DGqjnWxE/r3mjYxlPPukjy6nTx6HHn2mPm2GHj1F3ez1nayFPfzUj07Xrw527u5W3u5Gns4mbg0Frdzljl1lPRvUnJsELGrT/s4mvq3mHk1l/h0lzm2VjczFfh0lLk1VDWxE/i0k7Tv03i0Uzgz0rNt0TKs0PdyULcxj/Eqj3Dpzvz63fr4Gfq3mfo3GPn22Dm2Vbby1XYyFHfy0Xbx0Tv5W3Tv0nZxEPTvT7Ruj7Irjt5kAYfAAAAOXRSTlMA+40mCQPLurZ0V01CPjYW/Pz6+Pf27+7r5uPi3t3Z0cnGw62roqGVhHt3dmtkXVNNREQ6MiwiHhGFwXcaAAABNUlEQVQ4y83SR3OCUBQFYHuNMb333nujWlCBBJQmgiHGrqn/f5mXZCZA5rr3bjjM+ebCm3me4Z2g/yw6oEomjn2RFa8oSUC5v702gzN1gmJTut4GQIZpEBTNdrqapvMcBMgiVaDlFJquBgOSJIimKE6vbnEqAJ4xfCnsOzq/RZkrgyDwl9UKADDMzmUQ4A5gQCBr50oaALgDGBDIvtg53YMA4wCPAGByv0//yYW/B4HcD7gOqUa63/8HAnHfQSyXR+lySuHvH55qlqsfWcSEeqOJQHBBMasIfFqufgwnSwW2g8CoxJvfC97XnWAZL1IlWi54EWjzaEHtwzp19DcZAl0EVm5F0CdCymvVfBs/dP3hhIAW0C1vAr3c7c5Nzu9cuc+whwkkkZ+NeQZOfHMjHE16hnS+AIiQOxEJiBv0AAAAAElFTkSuQmCC'); +} +#eqbutton { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAwFBMVEUAAAD27X/QvErn2WH27oH0633v43Hj01vcyk7w5XDj02HbyVf28IPx53Xu4m7s4Gni0ljgz1TayUvq3Wfo2mPy53Tt4G7s3Wrt4Gn27oDx5nP48of274Li0lT38YT17H306nru4mv59Irz6Hbr32bq3GTl1ljfz07v5G3p2mHo2l7gzl3m2Fvgz1LOuEzXxEDeylrczEvLtEjbykfZx0TVwj7SvznQuzXKtS7j02HcyVHSvVDZx07dzEzNuDTMtzDLJSoKAAAAGHRSTlMA4Uvh0tLS0tJLS0vh4eHh4eHh0tJLS0sGkbICAAAA4klEQVQ4y83Sx9KCMBSG4SN2/XtNCCGEKgjYe7v/uzJRR8YZOePSd5l5FpkvgSet+/PdQUE1Dt5Q4A/7Jty0PCzms8F0EmU8TOMzcCi1CrDXYDKKeBgmARANhAYlyf7KA6x3Il9R0Gk0/u8cG62WAVhf2/UHCqJxHkJJc7WBBikEPumZniOoZdk2uwI10giycZ5AQKQGtABFPN/EgPWZ8SoKjGazC4/1V6+3UVBxnAoKKJE23KTemadJ7BNpmp5QgNjAmOtewU5/lOQy0gkwYK4CJVmyx/BLCvGCgnat9gvP1BGHfhnUEswA0wAAAABJRU5ErkJggg=='); +} +#settingsbutton { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAB8lBMVEUAAACDbR+QdyKgkjzEqirCrDWReCCyp0WJciCJcyKYhzChkzqTgS3Vw0G6oSe6oSjRw0q1nCm8oy3TyFO4nyrIuEKxlyfHu0qiiSKegyGijCyRdyKmlziomjqklTexqEmvpkmKdiXFrCzJry7n3mLf0lLXxUPHrS7cz0/KrzDZy0rPuDfBpyrn32jSwEDKsjTEqi3BqCzIsDTp4264nynPv0Pj3GfWyU/c01yzmybh2mSulSS+pi/Zz1rBrDazmSiqkCXFt0Pa1GOliiOtkynNxFTZ0mOpkCrBtUmqkivIv1OpkyybgSK+s0rJwlqplzKUeyGQdx++tU6ypUHCu1WWfSOnmDjCvFexpkSahCqNdSGHcB+XgimonECAax7OtDDLsC7Hrivt42js4mXq3l3h0Uy8oC/38Xv07XPv5Wvt5GPs4mHn21jez1fm2FLXxVDUwUneyULLtEHbxT7JrzzFqDS/oDTUuTPDpjH59ILz63Hp3GLi1Vzf0Frh0lbby1DUwk/ezUfSvUPQu0PNszvBpTrZwDjVvDfKsDa7nTPVujLRtjHPtDG2mS+xlS7Jry3Eqiz27nfx6W/k12Dq4F7bylXYx07j1E3Qu0rhz0fMtkTJsUHcxz/Vvj/Otj/Eqj7NsTTRtjPHqjO+oDLApDC+pCyMAG9RAAAAX3RSTlMADUYD/NtgQy4hFxQK+fTt49/e1tTT0bizqGVOSEE8Jg8G/vz6+vr49/f29vX09PTz8vHv6efm5uXl4eDf2NXRysbCu7i1s6Keko+Li31zcXFwal9dWU9NS0s+OjcbEJmukO4AAAFySURBVDjLYhiCIJZdlguXnLyXrUpWdkpOXiqvnTcnhnSIVUZVUnXt1PyGGc3Nc9vahUJRpMON06uSJtfU5ubWTysomt9aVhrfzc8El2Z2q0zPrE5OTs5OURUwVSssbl9Q2pvWnygJU+BXOTEpc1KmsEcwN0+YjCKXlLtgfEJa39JENpgKp/SMDAv2CHEBjbzpM+do27gwSZskTEiUQNhhphskLzwlpa6+YNbskpKOhfFinAF81kiOVFBwTc6qyW/IL2ia11La2VkRryTByIjsDfak7Nw6YBDosbLql3f1LOmbwBKNGg6+WSk5orLMXFKRPMxMYgn9iYHoIQWYuLmMnChvYXFbl7o9B5OlJGZQczumNjaVtJaVVcQnOscxYFGglVpU1NKxqCKtN4GPEVtsyTUWFhuJiBjGJ7BwYI9Pf9YYBkYehihBNpzJwVOzvFvHB096ESpfnBbvgEcBo0FPPL8iviTHoczCiT9RskkzDEcAAG+FX5nDaQtoAAAAAElFTkSuQmCC'); +} +.infowrap { + display: flex; + width: 400px; + justify-content: space-around; + font-size: 14px; + margin: 16px 0; +} +#volrange, .slider { + overflow: hidden; + width: 432px; + margin-top: 34px; + background: linear-gradient(to right, #bfa73e 0%, #bfa73e 0%, #272727 0%, #272727 100%); + border: solid 2px #e3d25f; + border-radius: 13px; + height: 25px!important; + outline: none; + transition: background 450ms ease-in; + -webkit-appearance: none; + box-sizing: border-box; +} +#volrange::-webkit-slider-thumb, .slider::-webkit-slider-thumb { + width: 10px; + -webkit-appearance: none;Раменское + height: 10px; + cursor: ew-resize; +} +.slider { + width: 400px; + margin-top: 4px; +} +#playlist { + width: 400px; + scroll-behavior: smooth; + overflow: auto; + margin: 0 0 0 0; + padding: 0; + list-style-type: none; + border: solid 1px #e3d25f; + flex: 1 1 auto; + overflow-y: auto; + height: 0px; + box-sizing: border-box; +} +#playlist::-webkit-scrollbar { + width: 5px; + height: 8px; + background-color: #000; +} +#playlist::-webkit-scrollbar-thumb { + background: #e3d25f; +} +#playlist li { + text-transform: uppercase; + font-size: 20px; + padding: 8px 16px; + color: #bfa73e; + cursor: pointer; + position: relative; +} +#playlist li span { + position: absolute; + display: flex; + flex-direction: column; + justify-content: center; + right: 0; + top: 0; + bottom: 0; + padding: 0 10px; + background: #000; + width: 44px; + box-sizing: border-box; + text-align: right; + font-size: 14px; + vertical-align: middle; +} +#playlist li:nth-child(odd), #playlist li:nth-child(odd) span { + background: #272727; +} + +#playlist li:hover, #playlist li:hover span { + color: #fff; + background: #323232; +} +#playlist li.active, #playlist li.active span { + background: #bfa73e; + color: #000; +} +#copy { + padding-top: 14px; + font-size: 14px; +} + +@-webkit-keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} +@media screen and (max-width: 480px) { + .content { padding: 20px; } + #playlist { width: 100%; } + #playlist li { font-size: 16px; } + #volrange { width: 100%; margin-top: 24px;} + .playerbyttonwrap { width: 100%; margin-top: 8px; } + #navbar { width: 100%; padding: 20px 20px 0 20px; } + #meta { margin-bottom: 8px; } + .infowrap {width: 100%; padding: 0 10px;} + .logo { width: 100px; height: 65px; background-size: cover; } + #copy { font-size: 10px; padding-top: 10px; } +} +@media screen and (max-width: 480px) { + .playerbytton, #playbutton { + width: 42px; + height: 42px; + background-size: 75%; + } +} + diff --git a/yoRadio/display.cpp b/yoRadio/display.cpp new file mode 100644 index 0000000..ddcbc95 --- /dev/null +++ b/yoRadio/display.cpp @@ -0,0 +1,311 @@ +#include "WiFi.h" +#include "time.h" +#include "display.h" + +#include "player.h" +#include "netserver.h" +#include "options.h" +#include "network.h" + +/* +#include "displayDummy.h" +DisplayDummy dsp; +*/ +#include "displayST7735.h" +DisplayST7735 dsp; + +Display display; + +void ticks() { + display.clockRequest = true; +} + +#define STARTTIME 5000 +#define SCROLLTIME 83 +#define SCROLLDELTA 3 + +void Scroll::init(char *sep, byte tsize, byte top, uint16_t dlay, uint16_t fgcolor, uint16_t bgcolor) { + textsize = tsize; + texttop = top; + fg = fgcolor; + bg = bgcolor; + delayStartScroll=dlay; + memset(separator, 0, 4); + strlcpy(separator, sep, 4); + locked = false; +} + +void Scroll::setText(const char *txt) { + memset(text, 0, BUFLEN / 2); + strlcpy(text, txt, BUFLEN / 2); + getbounds(textwidth, textheight, sepwidth); + if (!locked) { + clearscrolls(); + reset(); + } +} + +void Scroll::lock() { locked = true; } + +void Scroll::unlock() { locked = false; } + +void Scroll::reset() { + locked = false; + clear(); + setTextParams(); + dsp.set_Cursor(TFT_FRAMEWDT, texttop); + dsp.printText(text); + drawFrame(); +} + +void Scroll::setTextParams() { + dsp.set_TextSize(textsize); + dsp.set_TextColor(fg, bg); +} + +void Scroll::clearscrolls() { + x = TFT_FRAMEWDT; + scrolldelay = millis(); + clear(); +} + +void Scroll::loop() { + if (checkdelay(x == TFT_FRAMEWDT ? delayStartScroll : SCROLLTIME, scrolldelay)) { + scroll(); + ticks(); + } + yield(); +} + +boolean Scroll::checkdelay(int m, unsigned long &tstamp) { + if (millis() - tstamp > m) { + tstamp = millis(); + return true; + } else { + return false; + } +} + +void Scroll::drawFrame() { + dsp.drawScrollFrame(texttop, textheight, bg); +} + +void Scroll::ticks() { + if (!doscroll || locked) return; + setTextParams(); + dsp.set_Cursor(x, texttop); + dsp.printText(text); + dsp.printText(separator); + dsp.printText(text); + drawFrame(); +} + +void Scroll::scroll() { + if (!doscroll) return; + if (textwidth > display.screenwidth) { + x -= SCROLLDELTA; + if (-x >= textwidth + sepwidth - TFT_FRAMEWDT) x = TFT_FRAMEWDT; + } +} + +void Scroll::clear() { + dsp.clearScroll(texttop, textheight, bg); +} + +void Scroll::getbounds(uint16_t &tWidth, uint16_t &tHeight, uint16_t &sWidth) { + if (strlen(text) == 0) { + dsp.getScrolBbounds("EMPTY", separator, textsize, tWidth, tHeight, sWidth); + } else { + dsp.getScrolBbounds(text, separator, textsize, tWidth, tHeight, sWidth); + } + doscroll = (tWidth > display.screenwidth); +} + +void Display::init() { + dsp.initD(screenwidth, screenheight); + dsp.drawLogo(); + meta.init(" * ", 2, TFT_FRAMEWDT, STARTTIME, TFT_LOGO, TFT_BG); + title1.init(" * ", 1, TFT_FRAMEWDT + 2 * TFT_LINEHGHT, STARTTIME, TFT_FG, TFT_BG); + title2.init(" * ", 1, TFT_FRAMEWDT + 3 * TFT_LINEHGHT, STARTTIME, TFT_FG, TFT_BG); + plCurrent.init(" * ", 2, 57, 0, TFT_BG, TFT_LOGO); + plCurrent.lock(); +} + +void Display::apScreen() { + meta.setText(dsp.utf8Rus("ёRADIO * ёRADIO * ёRADIO", false)); + dsp.apScreen(); +} + +void Display::start() { + clear(); + if (network.status != CONNECTED) { + apScreen(); + return; + } + mode = PLAYER; + title("[READY]"); + ip(); + volume(); + station(); + rssi(); + time(); + configTime(TIMEZONE, OFFSET, "pool.ntp.org", "ru.pool.ntp.org"); + timer.attach_ms(1000, ticks); + // Экстреминатус секвестирован +} + +void Display::clear() { + dsp.clearDsp(); +} + +void Display::swichMode(displayMode_e newmode) { + if (newmode == VOL) { + volDelay = millis(); + } + if (newmode == mode) return; + clear(); + mode = newmode; + if (newmode != STATIONS) { + ip(); + volume(); + } + if (newmode == PLAYER) { + meta.reset(); + title1.reset(); + title2.reset(); + plCurrent.lock(); + time(); + } else { + meta.lock(); + title1.lock(); + title2.lock(); + } + if (newmode == VOL) { + dsp.frameTitle("VOLUME"); + } + if (newmode == STATIONS) { + currentPlItem = config.store.lastStation; + plCurrent.reset(); + drawPlaylist(); + } +} + +void Display::drawPlayer() { + if (clockRequest) { + if (syncTicks % 21600 == 0) { //6hours + configTime(TIMEZONE, OFFSET, "pool.ntp.org", "ru.pool.ntp.org"); + yield(); + syncTicks = 0; + } + getLocalTime(&timeinfo); + time(); + syncTicks++; + clockRequest = false; + } + meta.loop(); + title1.loop(); + title2.loop(); +} + +void Display::drawVolume() { + if (millis() - volDelay > 3000) { + volDelay = millis(); + swichMode(PLAYER); + } +} + +void Display::drawPlaylist() { + char buf[PLMITEMLENGHT]; + dsp.drawPlaylist(currentPlItem, buf); + plCurrent.setText(dsp.utf8Rus(buf, true)); +} + +void Display::loop() { + switch (mode) { + case PLAYER: { + drawPlayer(); + break; + } + case VOL: { + drawVolume(); + break; + } + case STATIONS: { + plCurrent.loop(); + break; + } + } + yield(); +} + +void Display::centerText(const char* text, byte y, uint16_t fg, uint16_t bg) { + dsp.centerText(text, y, fg, bg); +} + +void Display::bootString(const char* text, byte y) { + dsp.centerText(text, y, TFT_LOGO, TFT_BG); +} + +void Display::rightText(const char* text, byte y, uint16_t fg, uint16_t bg) { + dsp.rightText(text, y, fg, bg); +} + +void Display::station() { + meta.setText(dsp.utf8Rus(config.station.name, true)); + netserver.requestOnChange(STATION, 0); +} + +void Display::title(const char *str) { + const char *title = str; + char ttl[BUFLEN / 2] = { 0 }; + char sng[BUFLEN / 2] = { 0 }; + memset(config.station.title, 0, BUFLEN); + strlcpy(config.station.title, title, BUFLEN); + if (strlen(config.station.title) > 0) { + char* ici; + if ((ici = strstr(config.station.title, " - ")) != NULL) { + strlcpy(sng, ici + 3, BUFLEN / 2); + strlcpy(ttl, config.station.title, strlen(config.station.title) - strlen(ici) + 1); + } else { + strlcpy(ttl, config.station.title, BUFLEN / 2); + sng[0] = '\0'; + } + title1.setText(dsp.utf8Rus(ttl, true)); + title2.setText(dsp.utf8Rus(sng, true)); + } + netserver.requestOnChange(TITLE, 0); +} + +void Display::heap() { + if(config.store.audioinfo) dsp.displayHeapForDebug(); +} + +void Display::rssi() { + char buf[20]; + int rssi = WiFi.RSSI(); + sprintf(buf, "%ddBm", rssi); + dsp.rssi(buf); + netserver.setRSSI(rssi); +} + +void Display::ip() { + dsp.ip(WiFi.localIP().toString().c_str()); +} + +void Display::time() { + char timeStringBuff[20] = { 0 }; + if (!dt) { + heap(); + rssi(); + strftime(timeStringBuff, sizeof(timeStringBuff), "%H:%M", &timeinfo); + } else { + strftime(timeStringBuff, sizeof(timeStringBuff), "%H %M", &timeinfo); + } + dsp.printClock(timeStringBuff); + dt = !dt; +} + +void Display::volume() { + dsp.drawVolumeBar(mode == VOL); + netserver.requestOnChange(VOLUME, 0); +} diff --git a/yoRadio/display.h b/yoRadio/display.h new file mode 100644 index 0000000..b2c59b2 --- /dev/null +++ b/yoRadio/display.h @@ -0,0 +1,81 @@ +#ifndef display_h +#define display_h + +#include "Arduino.h" +#include +#include "config.h" + +enum displayMode_e { PLAYER, VOL, STATIONS }; + +#define TIMEZONE 10800 // 3600*3=10800 (UTC+3) +#define OFFSET 0 // Daylight Offset (sec.) + +class Scroll { + public: + Scroll() { }; + void init(char *sep, byte tsize, byte top, uint16_t dlay, uint16_t fgcolor, uint16_t bgcolor); + void setText(const char *txt); + void loop(); + void reset(); + void lock(); + void unlock(); + private: + byte textsize, texttop; + char text[BUFLEN/2]; + char separator[4]; + uint16_t fg, bg; + uint16_t delayStartScroll; + uint16_t textwidth, textheight, sepwidth, startticks, scrollticks; + int x; + bool doscroll, locked; + unsigned long scrolldelay; + void clearscrolls(); + void getbounds(uint16_t &tWidth, uint16_t &tHeight, uint16_t &sWidth); + boolean checkdelay(int m, unsigned long &tstamp); + void scroll(); + void ticks(); + void clear(); + void setTextParams(); + void drawFrame(); +}; + +class Display { + public: + struct tm timeinfo; + uint16_t syncTicks; + bool clockRequest; + uint16_t screenwidth, screenheight; + displayMode_e mode; + uint16_t currentPlItem; + public: + Display() {}; + void init(); + void clear(); + void loop(); + void start(); + void centerText(const char* text, byte y, uint16_t fg, uint16_t bg); + void rightText(const char* text, byte y, uint16_t fg, uint16_t bg); + void bootString(const char* text, byte y); + void station(); + void title(const char *str); + void time(); + void volume(); + void ip(); + void swichMode(displayMode_e newmode); + void drawPlaylist(); + private: + Ticker timer; + Scroll meta, title1, title2, plCurrent; + bool dt; // dots + unsigned long volDelay; + void heap(); + void rssi(); + void apScreen(); + void drawPlayer(); + void drawVolume(); + +}; + +extern Display display; + +#endif diff --git a/yoRadio/displayDummy.cpp b/yoRadio/displayDummy.cpp new file mode 100644 index 0000000..9883d50 --- /dev/null +++ b/yoRadio/displayDummy.cpp @@ -0,0 +1,163 @@ +#include "displayDummy.h" +#include +#include "player.h" +#include "config.h" +#include "network.h" + +DisplayDummy::DisplayDummy() { + +} + +char* DisplayDummy::utf8Rus(const char* str, bool uppercase) { + int index = 0; + static char strn[BUFLEN]; + bool E = false; + strlcpy(strn, str, BUFLEN); + if (uppercase) { + bool next = false; + for (char *iter = strn; *iter != '\0'; ++iter) + { + if (E) { + E = false; + continue; + } + byte rus = (byte) * iter; + if (rus == 208 && (byte) * (iter + 1) == 129) { // ёКостыли + *iter = (char)209; + *(iter + 1) = (char)145; + E = true; + continue; + } + if (rus == 209 && (byte) * (iter + 1) == 145) { + *iter = (char)209; + *(iter + 1) = (char)145; + E = true; + continue; + } + if (next) { + if (rus >= 128 && rus <= 143) *iter = (char)(rus + 32); + if (rus >= 176 && rus <= 191) *iter = (char)(rus - 32); + next = false; + } + if (rus == 208) next = true; + if (rus == 209) { + *iter = (char)208; + next = true; + } + *iter = toupper(*iter); + } + } + while (strn[index]) + { + if (strn[index] >= 0xBF) + { + switch (strn[index]) { + case 0xD0: { + if (strn[index + 1] == 0x81) { + strn[index] = 0xA8; + break; + } + if (strn[index + 1] >= 0x90 && strn[index + 1] <= 0xBF) strn[index] = strn[index + 1] + 0x30; + break; + } + case 0xD1: { + if (strn[index + 1] == 0x91) { + //strn[index] = 0xB7; + strn[index] = 0xB8; + break; + } + if (strn[index + 1] >= 0x80 && strn[index + 1] <= 0x8F) strn[index] = strn[index + 1] + 0x70; + break; + } + } + int sind = index + 2; + while (strn[sind]) { + strn[sind - 1] = strn[sind]; + sind++; + } + strn[sind - 1] = 0; + } + index++; + } + return strn; +} + +void DisplayDummy::apScreen() { + +} + +void DisplayDummy::initD(uint16_t &screenwidth, uint16_t &screenheight) { + +} + +void DisplayDummy::drawLogo() { + +} + +void DisplayDummy::drawPlaylist(uint16_t currentItem, char* currentItemText) { + +} + +void DisplayDummy::clearDsp() { + +} + +void DisplayDummy::drawScrollFrame(uint16_t texttop, uint16_t textheight, uint16_t bg) { + +} + +void DisplayDummy::getScrolBbounds(const char* text, const char* separator, byte textsize, uint16_t &tWidth, uint16_t &tHeight, uint16_t &sWidth) { + +} + +void DisplayDummy::clearScroll(uint16_t texttop, uint16_t textheight, uint16_t bg) { + +} + +void DisplayDummy::centerText(const char* text, byte y, uint16_t fg, uint16_t bg) { + +} + +void DisplayDummy::rightText(const char* text, byte y, uint16_t fg, uint16_t bg) { + +} + +void DisplayDummy::displayHeapForDebug() { + +} + +void DisplayDummy::printClock(const char* timestr) { + +} + +void DisplayDummy::drawVolumeBar(bool withNumber) { + +} + +void DisplayDummy::frameTitle(const char* str) { + +} + +void DisplayDummy::rssi(const char* str) { +; +} + +void DisplayDummy::ip(const char* str) { + +} + +void DisplayDummy::set_TextSize(uint8_t s) { + +} + +void DisplayDummy::set_TextColor(uint16_t fg, uint16_t bg) { + +} + +void DisplayDummy::set_Cursor(int16_t x, int16_t y) { + +} + +void DisplayDummy::printText(const char* txt) { + +} diff --git a/yoRadio/displayDummy.h b/yoRadio/displayDummy.h new file mode 100644 index 0000000..b0cb378 --- /dev/null +++ b/yoRadio/displayDummy.h @@ -0,0 +1,80 @@ +#ifndef displayDummy_h +#define displayDummy_h + +#include "Arduino.h" +#include "options.h" + +#define TFT_ROTATE 3 +#define TFT_LINEHGHT 10 +#define TFT_FRAMEWDT 4 + +#define PLMITEMS 7 +#define PLMITEMLENGHT 40 +#define PLMITEMHEIGHT 22 + +class DisplayDummy { + public: + DisplayDummy(); + char plMenu[PLMITEMS][PLMITEMLENGHT]; + uint16_t clockY; + void initD(uint16_t &screenwidth, uint16_t &screenheight); + void apScreen(); + void drawLogo(); + void clearDsp(); + void centerText(const char* text, byte y, uint16_t fg, uint16_t bg); + void rightText(const char* text, byte y, uint16_t fg, uint16_t bg); + void set_TextSize(uint8_t s); + void set_TextColor(uint16_t fg, uint16_t bg); + void set_Cursor(int16_t x, int16_t y); + void printText(const char* txt); + void printClock(const char* timestr); + void displayHeapForDebug(); + void drawVolumeBar(bool withNumber); + char* utf8Rus(const char* str, bool uppercase); + void drawScrollFrame(uint16_t texttop, uint16_t textheight, uint16_t bg); + void getScrolBbounds(const char* text, const char* separator, byte textsize, uint16_t &tWidth, uint16_t &tHeight, uint16_t &sWidth); + void clearScroll(uint16_t texttop, uint16_t textheight, uint16_t bg); + void frameTitle(const char* str); + void rssi(const char* str); + void ip(const char* str); + void drawPlaylist(uint16_t currentItem, char* currentItemText); + private: + uint16_t swidth, sheight; + +}; + +extern DisplayDummy dsp; + +/* + * TFT COLORS + */ +#define BLACK 0x0000 +#define BLUE 0x001F +#define RED 0xF800 +#define GREEN 0x07E0 +#define MAGENTA 0xF81F +#define YELLOW 0xFFE0 +#define WHITE 0xFFFF +#define GRAY 0x7BEF +#define DARK_GRAY 0x2945 +#define LIGHT_GRAY 0xC618 +#define LIME 0x87E0 +#define AQUA 0x5D1C +#define CYAN 0x07FF +#define DARK_CYAN 0x03EF +#define ORANGE 0xFCA0 +#define PINK 0xF97F +#define BROWN 0x8200 +#define VIOLET 0x9199 +#define SILVER 0xA510 +#define GOLD 0xA508 +#define NAVY 0x000F +#define MAROON 0x7800 +#define PURPLE 0x780F +#define OLIVE 0x7BE0 + +#define TFT_BG BLACK +#define TFT_FG WHITE +#define TFT_LOGO 0xE68B // 224, 209, 92 + +#endif diff --git a/yoRadio/displayST7735.cpp b/yoRadio/displayST7735.cpp new file mode 100644 index 0000000..5d034dd --- /dev/null +++ b/yoRadio/displayST7735.cpp @@ -0,0 +1,321 @@ +#include "displayST7735.h" +#include +#include "fonts/bootlogo.h" +#include "player.h" +#include "config.h" +#include "network.h" + +#define DTYPE INITR_BLACKTAB // 1.8' https://aliexpress.ru/item/1005002822797745.html +/* If there is a noisy line on one side of the screen, then in Adafruit_ST7735.cpp: + + // Black tab, change MADCTL color filter + if ((options == INITR_BLACKTAB) || (options == INITR_MINI160x80)) { + uint8_t data = 0xC0; + sendCommand(ST77XX_MADCTL, &data, 1); + _add this_ -> _colstart = 2; + _add this_ -> _rowstart = 1; + } + +*/ +//#define DTYPE INITR_144GREENTAB // 1.44' https://aliexpress.ru/item/1005002822797745.html + +class GFXClock { + public: + GFXClock() {}; + uint16_t init(Adafruit_ST7735 &tftd, const GFXfont *font, uint16_t fgcolor, uint16_t bgcolor ) { + _dsp = &tftd; + tftd.setFont(font); + tftd.getTextBounds("88:88", 0, 0, &x, &y, &cwidth, &cheight); + tftd.setFont(); + fg = fgcolor; + bg = bgcolor; + swidth = tftd.width(); + _canvas = new GFXcanvas1(swidth, cheight + 3); + _canvas->setFont(font); + _canvas->setTextWrap(false); + _canvas->setTextColor(WHITE); + uint16_t header = TFT_FRAMEWDT + 4 * TFT_LINEHGHT; + uint16_t footer = TFT_FRAMEWDT * 2 + TFT_LINEHGHT + 5; + clockY = header + (tftd.height() - header - footer) / 2 - cheight / 2; + return cheight; + } + void print(const char* timestr) { + _canvas->fillScreen(BLACK); + _canvas->getTextBounds(timestr, 0, 0, &x, &y, &cwidth, &cheight); + _canvas->setCursor((swidth - cwidth) / 2 - 4, cheight); + _canvas->print(timestr); + _dsp->drawBitmap(0, clockY , _canvas->getBuffer(), swidth, cheight + 3, fg, bg); + } + private: + int16_t x, y; + uint16_t cwidth, cheight, fg, bg, clockY, swidth; + GFXcanvas1 *_canvas; + Adafruit_ST7735 *_dsp; +}; + +GFXClock gclock; + +DisplayST7735::DisplayST7735(): Adafruit_ST7735(&SPI, TFT_CS, TFT_DC, TFT_RST) { + +} + +char* DisplayST7735::utf8Rus(const char* str, bool uppercase) { + int index = 0; + static char strn[BUFLEN]; + bool E = false; + strlcpy(strn, str, BUFLEN); + if (uppercase) { + bool next = false; + for (char *iter = strn; *iter != '\0'; ++iter) + { + if (E) { + E = false; + continue; + } + byte rus = (byte) * iter; + if (rus == 208 && (byte) * (iter + 1) == 129) { // ёКостыли + *iter = (char)209; + *(iter + 1) = (char)145; + E = true; + continue; + } + if (rus == 209 && (byte) * (iter + 1) == 145) { + *iter = (char)209; + *(iter + 1) = (char)145; + E = true; + continue; + } + if (next) { + if (rus >= 128 && rus <= 143) *iter = (char)(rus + 32); + if (rus >= 176 && rus <= 191) *iter = (char)(rus - 32); + next = false; + } + if (rus == 208) next = true; + if (rus == 209) { + *iter = (char)208; + next = true; + } + *iter = toupper(*iter); + } + } + while (strn[index]) + { + if (strn[index] >= 0xBF) + { + switch (strn[index]) { + case 0xD0: { + if (strn[index + 1] == 0x81) { + strn[index] = 0xA8; + break; + } + if (strn[index + 1] >= 0x90 && strn[index + 1] <= 0xBF) strn[index] = strn[index + 1] + 0x30; + break; + } + case 0xD1: { + if (strn[index + 1] == 0x91) { + //strn[index] = 0xB7; + strn[index] = 0xB8; + break; + } + if (strn[index + 1] >= 0x80 && strn[index + 1] <= 0x8F) strn[index] = strn[index + 1] + 0x70; + break; + } + } + int sind = index + 2; + while (strn[sind]) { + strn[sind - 1] = strn[sind]; + sind++; + } + strn[sind - 1] = 0; + } + index++; + } + return strn; +} + +void DisplayST7735::apScreen() { + setTextSize(1); + setTextColor(TFT_FG, TFT_BG); + setCursor(TFT_FRAMEWDT, TFT_FRAMEWDT + 2 * TFT_LINEHGHT); + print("AP NAME: "); + print(apSsid); + setCursor(TFT_FRAMEWDT, TFT_FRAMEWDT + 3 * TFT_LINEHGHT); + print("PASSWORD: "); + print(apPassword); + setTextColor(SILVER, TFT_BG); + setCursor(TFT_FRAMEWDT, 107); + print("SETTINGS PAGE ON: "); + setCursor(TFT_FRAMEWDT, 117); + print("http://"); + print(WiFi.softAPIP().toString().c_str()); + print("/"); +} + +void DisplayST7735::initD(uint16_t &screenwidth, uint16_t &screenheight) { + initR(DTYPE); + cp437(true); + fillScreen(TFT_BG); + setRotation(TFT_ROTATE); + setTextWrap(false); + screenwidth = width(); + screenheight = height(); + swidth = screenwidth; + sheight = screenheight; + gclock.init(dsp, &DS_DIGI28pt7b, TFT_LOGO, BLACK); +} + +void DisplayST7735::drawLogo() { + drawRGBBitmap((swidth - 99) / 2, 18, bootlogo2, 99, 64); +} + +// http://greekgeeks.net/#maker-tools_convertColor +#define CLR_ITEM1 0x52AA +#define CLR_ITEM2 0x39C7 +#define CLR_ITEM3 0x18E3 + +void DisplayST7735::drawPlaylist(uint16_t currentItem, char* currentItemText) { + for (byte i = 0; i < PLMITEMS; i++) { + plMenu[i][0] = '\0'; + } + config.fillPlMenu(plMenu, currentItem - 3, PLMITEMS); + setTextSize(2); + int yStart = (sheight / 2 - PLMITEMHEIGHT / 2) - PLMITEMHEIGHT * (PLMITEMS - 1) / 2 + 3; + fillRect(0, (sheight / 2 - PLMITEMHEIGHT / 2) - 1, swidth, PLMITEMHEIGHT + 2, TFT_LOGO); + for (byte i = 0; i < PLMITEMS; i++) { + if (abs(i - 3) == 3) setTextColor(CLR_ITEM3, TFT_BG); + if (abs(i - 3) == 2) setTextColor(CLR_ITEM2, TFT_BG); + if (abs(i - 3) == 1) setTextColor(CLR_ITEM1, TFT_BG); + if (i == 3) { + strlcpy(currentItemText, plMenu[i], PLMITEMLENGHT - 1); + } else { + setCursor(TFT_FRAMEWDT, yStart + i * PLMITEMHEIGHT); + print(utf8Rus(plMenu[i], true)); + } + } +} + +void DisplayST7735::clearDsp() { + fillScreen(TFT_BG); +} + +void DisplayST7735::drawScrollFrame(uint16_t texttop, uint16_t textheight, uint16_t bg) { + fillRect(0, texttop, TFT_FRAMEWDT, textheight, bg); + fillRect(swidth - TFT_FRAMEWDT, texttop, TFT_FRAMEWDT, textheight, bg); +} + +void DisplayST7735::getScrolBbounds(const char* text, const char* separator, byte textsize, uint16_t &tWidth, uint16_t &tHeight, uint16_t &sWidth) { + int16_t x1, y1; + uint16_t w, h; + setTextSize(textsize); + getTextBounds(text, 0, 0, &x1, &y1, &w, &h); + tWidth = w; + tHeight = h; + getTextBounds(separator, 0, 0, &x1, &y1, &w, &h); + sWidth = w; +} + +void DisplayST7735::clearScroll(uint16_t texttop, uint16_t textheight, uint16_t bg) { + fillRect(0, texttop, swidth, textheight, bg); +} + +void DisplayST7735::centerText(const char* text, byte y, uint16_t fg, uint16_t bg) { + int16_t x1, y1; + uint16_t w, h; + const char* txt = text; + getTextBounds(txt, 0, 0, &x1, &y1, &w, &h); + setTextColor(fg); + setCursor((swidth - w) / 2, y); + fillRect(0, y, swidth, h, bg); + print(txt); +} + +void DisplayST7735::rightText(const char* text, byte y, uint16_t fg, uint16_t bg) { + int16_t x1, y1; + uint16_t w, h; + getTextBounds(text, 0, 0, &x1, &y1, &w, &h); + setTextColor(fg); + setCursor(swidth - w - TFT_FRAMEWDT, y); + fillRect(swidth - w - TFT_FRAMEWDT, y, w, h, bg); + print(text); +} + +void DisplayST7735::displayHeapForDebug() { + int16_t vTop = sheight - TFT_FRAMEWDT * 2 - TFT_LINEHGHT * 2 - 2; + setTextSize(1); + setTextColor(DARK_GRAY, TFT_BG); + setCursor(TFT_FRAMEWDT, vTop); + fillRect(TFT_FRAMEWDT, vTop, swidth - TFT_FRAMEWDT / 2, 7, TFT_BG); + print(ESP.getFreeHeap()); + print(" / "); + print(ESP.getMaxAllocHeap()); + + // audio buffer; + fillRect(0, sheight - 2, swidth, 2, TFT_BG); + int astored = player.inBufferFilled(); + int afree = player.inBufferFree(); + int aprcnt = 100 * astored / (astored + afree); + byte sbw = map(aprcnt, 0, 100 , 0, swidth); + fillRect(0, sheight - 2, sbw, 2, SILVER); +} + +void DisplayST7735::printClock(const char* timestr) { + gclock.print(timestr); +} + +void DisplayST7735::drawVolumeBar(bool withNumber) { + int16_t vTop = sheight - TFT_FRAMEWDT * 2; + int16_t vWidth = swidth - TFT_FRAMEWDT - 4; + uint8_t ww = map(config.store.volume, 0, 254, 0, vWidth - 2); + fillRect(TFT_FRAMEWDT, vTop - 2, vWidth, 6, TFT_BG); + drawRect(TFT_FRAMEWDT, vTop - 2, vWidth, 6, TFT_LOGO); + fillRect(TFT_FRAMEWDT + 1, vTop - 1, ww, 5, TFT_LOGO); + if (withNumber) { + setTextSize(1); + setTextColor(TFT_FG); + setFont(&DS_DIGI28pt7b); + char volstr[4]; + uint16_t wv, hv; + int16_t x1, y1; + sprintf(volstr, "%d", config.store.volume); + getTextBounds(volstr, 0, 0, &x1, &y1, &wv, &hv); + fillRect(TFT_FRAMEWDT, 48, swidth - TFT_FRAMEWDT / 2, hv + 3, TFT_BG); + setCursor((swidth - wv) / 2, 48 + hv); + print(volstr); + setFont(); + } +} + +void DisplayST7735::frameTitle(const char* str) { + setTextSize(2); + centerText(str, TFT_FRAMEWDT, TFT_LOGO, TFT_BG); +} + +void DisplayST7735::rssi(const char* str) { + int16_t vTop = sheight - TFT_FRAMEWDT * 2 - TFT_LINEHGHT - 2; + setTextSize(1); + rightText(str, vTop, SILVER, TFT_BG); +} + +void DisplayST7735::ip(const char* str) { + int16_t vTop = sheight - TFT_FRAMEWDT * 2 - TFT_LINEHGHT - 2; + setTextSize(1); + setTextColor(SILVER, TFT_BG); + setCursor(4, vTop); + print(str); +} + +void DisplayST7735::set_TextSize(uint8_t s) { + setTextSize(s); +} + +void DisplayST7735::set_TextColor(uint16_t fg, uint16_t bg) { + setTextColor(fg, bg); +} + +void DisplayST7735::set_Cursor(int16_t x, int16_t y) { + setCursor(x, y); +} + +void DisplayST7735::printText(const char* txt) { + print(txt); +} diff --git a/yoRadio/displayST7735.h b/yoRadio/displayST7735.h new file mode 100644 index 0000000..6b78865 --- /dev/null +++ b/yoRadio/displayST7735.h @@ -0,0 +1,83 @@ +#ifndef displayST7735_h +#define displayST7735_h + +#include "Arduino.h" +#include +#include +#include "options.h" +#include "fonts/DS_DIGI28pt7b.h" + +#define TFT_ROTATE 3 +#define TFT_LINEHGHT 10 +#define TFT_FRAMEWDT 4 + +#define PLMITEMS 7 +#define PLMITEMLENGHT 40 +#define PLMITEMHEIGHT 22 + +class DisplayST7735: public Adafruit_ST7735 { + public: + DisplayST7735(); + char plMenu[PLMITEMS][PLMITEMLENGHT]; + uint16_t clockY; + void initD(uint16_t &screenwidth, uint16_t &screenheight); + void apScreen(); + void drawLogo(); + void clearDsp(); + void centerText(const char* text, byte y, uint16_t fg, uint16_t bg); + void rightText(const char* text, byte y, uint16_t fg, uint16_t bg); + void set_TextSize(uint8_t s); + void set_TextColor(uint16_t fg, uint16_t bg); + void set_Cursor(int16_t x, int16_t y); + void printText(const char* txt); + void printClock(const char* timestr); + void displayHeapForDebug(); + void drawVolumeBar(bool withNumber); + char* utf8Rus(const char* str, bool uppercase); + void drawScrollFrame(uint16_t texttop, uint16_t textheight, uint16_t bg); + void getScrolBbounds(const char* text, const char* separator, byte textsize, uint16_t &tWidth, uint16_t &tHeight, uint16_t &sWidth); + void clearScroll(uint16_t texttop, uint16_t textheight, uint16_t bg); + void frameTitle(const char* str); + void rssi(const char* str); + void ip(const char* str); + void drawPlaylist(uint16_t currentItem, char* currentItemText); + private: + uint16_t swidth, sheight; + +}; + +extern DisplayST7735 dsp; + +/* + * TFT COLORS + */ +#define BLACK 0x0000 +#define BLUE 0x001F +#define RED 0xF800 +#define GREEN 0x07E0 +#define MAGENTA 0xF81F +#define YELLOW 0xFFE0 +#define WHITE 0xFFFF +#define GRAY 0x7BEF +#define DARK_GRAY 0x2945 +#define LIGHT_GRAY 0xC618 +#define LIME 0x87E0 +#define AQUA 0x5D1C +#define CYAN 0x07FF +#define DARK_CYAN 0x03EF +#define ORANGE 0xFCA0 +#define PINK 0xF97F +#define BROWN 0x8200 +#define VIOLET 0x9199 +#define SILVER 0xA510 +#define GOLD 0xA508 +#define NAVY 0x000F +#define MAROON 0x7800 +#define PURPLE 0x780F +#define OLIVE 0x7BE0 + +#define TFT_BG BLACK +#define TFT_FG WHITE +#define TFT_LOGO 0xE68B // 224, 209, 92 + +#endif diff --git a/yoRadio/fonts/DS_DIGI28pt7b.h b/yoRadio/fonts/DS_DIGI28pt7b.h new file mode 100644 index 0000000..f29219d --- /dev/null +++ b/yoRadio/fonts/DS_DIGI28pt7b.h @@ -0,0 +1,109 @@ +const uint8_t DS_DIGI28pt7bBitmaps[] PROGMEM = { + 0x13, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x20, 0x27, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF7, 0x31, 0x00, 0xFF, 0xFF, 0x7F, 0xFF, 0xF5, 0xFF, + 0xFF, 0x77, 0xFF, 0xF7, 0xDF, 0xFF, 0x7F, 0x00, 0x07, 0xF8, 0x00, 0x3F, + 0xC0, 0x01, 0xFE, 0x00, 0x0F, 0xF0, 0x00, 0x7F, 0x80, 0x03, 0xFC, 0x00, + 0x1F, 0xE0, 0x00, 0xFF, 0x00, 0x07, 0xF8, 0x00, 0x3F, 0xC0, 0x01, 0xFC, + 0x00, 0x07, 0x40, 0x00, 0x10, 0x00, 0x00, 0x10, 0x00, 0x05, 0xC0, 0x00, + 0x7F, 0x00, 0x07, 0xF8, 0x00, 0x3F, 0xC0, 0x01, 0xFE, 0x00, 0x0F, 0xF0, + 0x00, 0x7F, 0x80, 0x03, 0xFC, 0x00, 0x1F, 0xE0, 0x00, 0xFF, 0x00, 0x07, + 0xF8, 0x00, 0x3F, 0xC0, 0x01, 0xFD, 0xFF, 0xF7, 0xDF, 0xFF, 0xDD, 0xFF, + 0xFF, 0x5F, 0xFF, 0xFC, 0x00, 0x01, 0x37, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF7, 0x20, 0x27, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x31, 0x00, 0x7F, + 0xFF, 0xF1, 0xFF, 0xFF, 0x47, 0xFF, 0xF6, 0x1F, 0xFF, 0x70, 0x00, 0x07, + 0x80, 0x00, 0x3C, 0x00, 0x01, 0xE0, 0x00, 0x0F, 0x00, 0x00, 0x78, 0x00, + 0x03, 0xC0, 0x00, 0x1E, 0x00, 0x00, 0xF0, 0x00, 0x07, 0x80, 0x00, 0x3C, + 0x00, 0x01, 0xE0, 0x00, 0x07, 0x0F, 0xFF, 0x90, 0xFF, 0xFE, 0x17, 0xFF, + 0xF1, 0xDF, 0xFF, 0x0F, 0x00, 0x00, 0x78, 0x00, 0x03, 0xC0, 0x00, 0x1E, + 0x00, 0x00, 0xF0, 0x00, 0x07, 0x80, 0x00, 0x3C, 0x00, 0x01, 0xE0, 0x00, + 0x0F, 0x00, 0x00, 0x78, 0x00, 0x03, 0xC0, 0x00, 0x1D, 0xFF, 0xF0, 0xDF, + 0xFF, 0xC5, 0xFF, 0xFF, 0x1F, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xE7, 0xFF, + 0xFD, 0x3F, 0xFF, 0xB1, 0xFF, 0xF7, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, + 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, + 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x07, + 0x0F, 0xFF, 0x21, 0xFF, 0xF8, 0x1F, 0xFF, 0xA0, 0xFF, 0xF7, 0x00, 0x00, + 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, + 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, + 0x00, 0xF1, 0xFF, 0xF7, 0x3F, 0xFF, 0xB7, 0xFF, 0xFD, 0xFF, 0xFF, 0xE0, + 0x00, 0x00, 0x04, 0x00, 0x00, 0x70, 0x00, 0x07, 0xC0, 0x00, 0x7F, 0x00, + 0x07, 0xF8, 0x00, 0x3F, 0xC0, 0x01, 0xFE, 0x00, 0x0F, 0xF0, 0x00, 0x7F, + 0x80, 0x03, 0xFC, 0x00, 0x1F, 0xE0, 0x00, 0xFF, 0x00, 0x07, 0xF8, 0x00, + 0x3F, 0xC0, 0x01, 0xFC, 0x00, 0x07, 0x4F, 0xFF, 0x90, 0xFF, 0xFE, 0x07, + 0xFF, 0xF4, 0x1F, 0xFF, 0x70, 0x00, 0x07, 0x80, 0x00, 0x3C, 0x00, 0x01, + 0xE0, 0x00, 0x0F, 0x00, 0x00, 0x78, 0x00, 0x03, 0xC0, 0x00, 0x1E, 0x00, + 0x00, 0xF0, 0x00, 0x07, 0x80, 0x00, 0x3C, 0x00, 0x01, 0xE0, 0x00, 0x07, + 0x00, 0x00, 0x18, 0x00, 0x00, 0x40, 0x7F, 0xFF, 0xF5, 0xFF, 0xFF, 0x37, + 0xFF, 0xF1, 0xDF, 0xFF, 0x0F, 0x00, 0x00, 0x78, 0x00, 0x03, 0xC0, 0x00, + 0x1E, 0x00, 0x00, 0xF0, 0x00, 0x07, 0x80, 0x00, 0x3C, 0x00, 0x01, 0xE0, + 0x00, 0x0F, 0x00, 0x00, 0x78, 0x00, 0x03, 0xC0, 0x00, 0x1C, 0x00, 0x00, + 0x4F, 0xFF, 0x80, 0xFF, 0xFE, 0x07, 0xFF, 0xF4, 0x1F, 0xFF, 0x70, 0x00, + 0x07, 0x80, 0x00, 0x3C, 0x00, 0x01, 0xE0, 0x00, 0x0F, 0x00, 0x00, 0x78, + 0x00, 0x03, 0xC0, 0x00, 0x1E, 0x00, 0x00, 0xF0, 0x00, 0x07, 0x80, 0x00, + 0x3C, 0x00, 0x01, 0xE1, 0xFF, 0xF7, 0x1F, 0xFF, 0xD9, 0xFF, 0xFF, 0x5F, + 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0xF5, 0xFF, 0xFF, 0x37, 0xFF, 0xF1, 0xDF, + 0xFF, 0x0F, 0x00, 0x00, 0x78, 0x00, 0x03, 0xC0, 0x00, 0x1E, 0x00, 0x00, + 0xF0, 0x00, 0x07, 0x80, 0x00, 0x3C, 0x00, 0x01, 0xE0, 0x00, 0x0F, 0x00, + 0x00, 0x78, 0x00, 0x03, 0xC0, 0x00, 0x1C, 0x00, 0x00, 0x4F, 0xFF, 0x80, + 0xFF, 0xFE, 0x17, 0xFF, 0xF5, 0xDF, 0xFF, 0x7F, 0x00, 0x07, 0xF8, 0x00, + 0x3F, 0xC0, 0x01, 0xFE, 0x00, 0x0F, 0xF0, 0x00, 0x7F, 0x80, 0x03, 0xFC, + 0x00, 0x1F, 0xE0, 0x00, 0xFF, 0x00, 0x07, 0xF8, 0x00, 0x3F, 0xC0, 0x01, + 0xFD, 0xFF, 0xF7, 0xDF, 0xFF, 0xDD, 0xFF, 0xFF, 0x5F, 0xFF, 0xFC, 0x00, + 0xFF, 0xFF, 0xE7, 0xFF, 0xFD, 0x3F, 0xFF, 0xB1, 0xFF, 0xF7, 0x00, 0x00, + 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, + 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, + 0x00, 0xF0, 0x00, 0x07, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x20, + 0x00, 0x07, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, + 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, + 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x07, 0x00, 0x00, 0x30, 0x00, + 0x01, 0x7F, 0xFF, 0xF5, 0xFF, 0xFF, 0x77, 0xFF, 0xF7, 0xDF, 0xFF, 0x7F, + 0x00, 0x07, 0xF8, 0x00, 0x3F, 0xC0, 0x01, 0xFE, 0x00, 0x0F, 0xF0, 0x00, + 0x7F, 0x80, 0x03, 0xFC, 0x00, 0x1F, 0xE0, 0x00, 0xFF, 0x00, 0x07, 0xF8, + 0x00, 0x3F, 0xC0, 0x01, 0xFC, 0x00, 0x07, 0x4F, 0xFF, 0x90, 0xFF, 0xFE, + 0x17, 0xFF, 0xF5, 0xDF, 0xFF, 0x7F, 0x00, 0x07, 0xF8, 0x00, 0x3F, 0xC0, + 0x01, 0xFE, 0x00, 0x0F, 0xF0, 0x00, 0x7F, 0x80, 0x03, 0xFC, 0x00, 0x1F, + 0xE0, 0x00, 0xFF, 0x00, 0x07, 0xF8, 0x00, 0x3F, 0xC0, 0x01, 0xFD, 0xFF, + 0xF7, 0xDF, 0xFF, 0xDD, 0xFF, 0xFF, 0x5F, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, + 0xF5, 0xFF, 0xFF, 0x77, 0xFF, 0xF7, 0xDF, 0xFF, 0x7F, 0x00, 0x07, 0xF8, + 0x00, 0x3F, 0xC0, 0x01, 0xFE, 0x00, 0x0F, 0xF0, 0x00, 0x7F, 0x80, 0x03, + 0xFC, 0x00, 0x1F, 0xE0, 0x00, 0xFF, 0x00, 0x07, 0xF8, 0x00, 0x3F, 0xC0, + 0x01, 0xFC, 0x00, 0x07, 0x4F, 0xFF, 0x90, 0xFF, 0xFE, 0x07, 0xFF, 0xF4, + 0x1F, 0xFF, 0x70, 0x00, 0x07, 0x80, 0x00, 0x3C, 0x00, 0x01, 0xE0, 0x00, + 0x0F, 0x00, 0x00, 0x78, 0x00, 0x03, 0xC0, 0x00, 0x1E, 0x00, 0x00, 0xF0, + 0x00, 0x07, 0x80, 0x00, 0x3C, 0x00, 0x01, 0xE1, 0xFF, 0xF7, 0x1F, 0xFF, + 0xD9, 0xFF, 0xFF, 0x5F, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xF0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xF0, 0x00, 0x00, 0x00 +}; + +const GFXglyph DS_DIGI28pt7bGlyphs[] PROGMEM = { + { 0, 0, 0, 12, 0, 1 }, // 0x20 ' ' + { 0, 0, 0, 0, 0, 0 }, // 0x21 '!' + { 0, 0, 0, 0, 0, 0 }, // 0x22 '"' + { 0, 0, 0, 0, 0, 0 }, // 0x23 '#' + { 0, 0, 0, 0, 0, 0 }, // 0x24 '$' + { 0, 0, 0, 0, 0, 0 }, // 0x25 '%' + { 0, 0, 0, 0, 0, 0 }, // 0x26 '&' + { 0, 0, 0, 0, 0, 0 }, // 0x27 ''' + { 0, 0, 0, 0, 0, 0 }, // 0x28 '(' + { 0, 0, 0, 0, 0, 0 }, // 0x29 ')' + { 0, 0, 0, 0, 0, 0 }, // 0x2A '*' + { 0, 0, 0, 0, 0, 0 }, // 0x2B '+' + { 0, 0, 0, 0, 0, 0 }, // 0x2C ',' + { 0, 0, 0, 0, 0, 0 }, // 0x2D '-' + { 0, 0, 0, 0, 0, 0 }, // 0x2E '.' + { 0, 0, 0, 0, 0, 0 }, // 0x2F '/' + { 20, 21, 35, 27, 3, -34 }, // 0x30 '0' + { 113, 4, 35, 14, 5, -34 }, // 0x31 '1' + { 131, 21, 35, 27, 3, -34 }, // 0x32 '2' + { 224, 20, 35, 27, 4, -34 }, // 0x33 '3' + { 312, 21, 34, 27, 3, -34 }, // 0x34 '4' + { 402, 21, 35, 27, 3, -34 }, // 0x35 '5' + { 495, 21, 35, 27, 3, -34 }, // 0x36 '6' + { 588, 20, 34, 27, 4, -34 }, // 0x37 '7' + { 673, 21, 35, 27, 3, -34 }, // 0x38 '8' + { 766, 21, 35, 27, 3, -34 }, // 0x39 '9' + { 859, 4, 29, 12, 4, -28 } // 0x3A ':' +}; + +const GFXfont DS_DIGI28pt7b PROGMEM = { + (uint8_t *)DS_DIGI28pt7bBitmaps, + (GFXglyph *)DS_DIGI28pt7bGlyphs, 0x20, 0x3A, 55 }; diff --git a/yoRadio/fonts/bootlogo.h b/yoRadio/fonts/bootlogo.h new file mode 100644 index 0000000..ddd1cef --- /dev/null +++ b/yoRadio/fonts/bootlogo.h @@ -0,0 +1,157 @@ +#ifndef bootlogo_h +#define bootlogo_h + + +/******************************************************************************* +* generated by lcd-image-converter rev.030b30d from 2019-03-17 01:38:34 +0500 +* image +* filename: unsaved +* name: bootlogo +* +* preset name: Color R5G6B5 +* data block size: 16 bit(s), uint16_t +* RLE compression enabled: no +* conversion type: Color, not_used not_used +* split to rows: yes +* bits per pixel: 16 +* +* preprocess: +* main scan direction: top_to_bottom +* line scan direction: forward +* inverse: no +*******************************************************************************/ + +#include + +static const uint16_t bootlogo2[6336] PROGMEM = { + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▓▓▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙░▒▓▓▓▒░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▓▓▓▓▓▓▓▓░∙∙∙∙∙∙∙∙∙▓▓▓▓▓▒▒▒▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░▓▓▓▓▓▓▓▓▓▓░∙∙∙∙∙∙∙▓▓▓▓▓▓▓▓▒▒▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▓▓▓▓▓▓▓▓▓▓▓▓∙∙∙∙∙∙▒▓▓▓▓▓▓▓▓▓▓▓░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▓▓▓▓▓▓▓▓▓▓▓▓∙∙∙∙∙∙▓▓▓▓▓▓▓▓▓▓▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▓▓▓▓▓▓▓▓▓▓▓▓▒∙∙∙∙∙▓▓▓▓▓▓▓▓▓▓▓▓▓∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▓▓▓▓▓▓▓▓▓▓▓▓▒∙∙∙∙∙▓▓▓▓▓▓▓▓▓▓▓▓▓∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▓▓▓▓▓▓▓▓▓▓▓▓▒∙∙∙∙∙▓▓▓▓▓▓▓▓▓▓▓▓▓∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░▓▓▓▓▓▓▓▓▓▓▓▓∙∙∙∙∙∙▓▓▓▓▓▓▓▓▓▓▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▓▒▒▓▓▓▓▓▓▓▓▓∙∙∙∙∙∙░▓▓▓▓▓▓▓▓▓▓▓░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░▓▒▒▒▓▓▓▓▓▓∙∙∙∙∙∙∙∙▓▓▓▓▓▓▓▓▓▓▓∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░▓▓▒▒▒▓▓▓░∙∙∙∙∙∙∙∙∙▒▓▓▓▓▓▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▓▓▓▓░∙∙∙∙∙∙∙∙∙∙∙∙∙▓▓▓▓▓∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙░∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░▒▓▓▓▓▓▓▓▓▓▒░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙░∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙░░░∙∙░▓▓▓▒▒▒▒▒▒▒▒▒▒▓▓▓▓░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙∙░∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙░░▒▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙∙░░∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙▒░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙░░░∙∙∙∙∙░∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙∙∙∙∙∙∙░∙∙∙▓▓░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙∙░░∙∙∙∙∙∙░∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙∙∙∙∙∙░∙░█▓▓▒░░▒▒▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙∙░░∙∙∙∙░░░∙∙∙∙∙░∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙∙∙∙∙░∙∙█▓▓▓▒░░▒▒▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓∙∙∙∙∙∙∙∙░░∙∙∙∙∙░░∙∙∙∙░░░∙∙∙∙∙∙░∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙∙∙░∙∙▓▓▓▓▓▒░░▒▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒∙∙∙∙∙∙∙░░∙∙∙∙░░░∙∙∙∙∙░░∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙∙░░∙▓▓▓▓▓▓░░░▒▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░∙∙∙∙∙∙░∙∙∙∙∙░░∙∙∙∙∙∙░∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙∙░∙░█▓▓▓▓▓░░▒▒▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒░░∙∙∙∙∙░░∙∙∙∙░░∙∙∙∙∙∙∙░∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙░░∙▓▓▓▓▓▓▒░░▒▓▓▓▓▓▓▓▒▒▒▓▓▓▒▒▒▒▒░▒▒▒▒░░▒∙∙∙∙░░∙∙∙∙░░░∙∙∙∙∙∙∙░∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙░∙░█████▓░░▒▒▓▓▓█░∙∙∙∙∙∙∙▒▓▓▒▒░▒▒▒▒░░░░▒∙∙∙░░∙∙∙∙░░∙∙∙∙∙∙∙∙░░∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙░░∙▓█████▒░▒▒▓▓▓▓∙∙∙∙∙∙∙∙∙∙░▓▒░▒▒▒▒▒░░░░▓░∙░░∙∙∙∙∙░░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙░░∙░█████▓░▒▒▓▓▓█∙∙∙∙∙∙░∙∙∙∙∙░░▒▒▒▒▒░░░░▒▒░∙░░∙∙∙∙∙░░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙░░∙▒█████▒▒▒▒▓▓█░∙∙∙∙∙░∙∙∙∙∙░░▒▒▒▒▓▒░░░░▒▒░░░∙∙∙∙∙∙░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙░░░∙▓████▒▒▓▓▓██▓∙░∙∙∙░░∙∙∙∙∙░░░▒▓▓▓░░░░▒▒▒▒░░∙∙∙∙∙∙∙░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙░░∙∙███▓▒▒▓▓▓██▓▒∙∙∙∙∙░∙∙∙∙∙░░∙∙▓▓▓▒░░░▒▒▒▒▒░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙░░░∙░▒▒▒▓▓▓▓▓██▓▒░∙∙∙∙░░∙∙∙∙░░░∙∙▓▓▓▒░░░▒▒▒▒▓░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙░░∙∙▓▓▓▓▓▓▓███▓░▓▒▒▒▒░░░░▒▒░░░░░░▓▓▒▒░▒▒▒▒▒▒▓∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙░░░∙∙▓▓▓▓▓▓███▓░▒▒▓██▒░▒▒▓▓▓▓░░▒▒▓▓▒▒░░▒▒▒▒▓▓▓░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙░░∙∙∙▓▓▓▓████▓░░▒▓▓█▓░░▒▓▓▓▓▓░░▒▓▓▓▒▒░▒▒▒▓▓▓▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙░░░∙∙∙▓▓▓▓▓▓█▓░░▒▒▓██▒░▒▒▓█▒▓▓░▒▒▓▓▒▓░░▒▒▓▓▓▓▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙░░∙∙∙∙▓▓▓▓▓▓▓▒░░▒▓▓█▓░░▒▓█▓▓▓▓░▒▓▓▓▓▒░▒▒▒▓▓▓▓▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙∙░░∙∙∙∙▓▓▓▓▓▓▒░░▒▒▓██▒░░▒▓▓▓▓██▒▒▒▓▓▓▒░▒▒▓▓▓▓▓▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙∙░░∙∙∙∙∙▓▓▓▓▓▓▒░░▒▓▓█▓▒░▒▒▓▓▓▓███▓▓▓▓▒░▒▒▓▓▓▓▓▓▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙∙∙∙░░∙∙∙∙∙▓▓▓▓▓▓▒░▒▒▓▓▓▓▒░▒▒▓▓▓██████▓▓▒░▒▒▓▓▓▓▓▓▓▓░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙░∙∙∙∙∙∙∙░░∙∙∙∙∙∙▓▓▓▓▓▓▒░▒▒▓▓▓▓▓░▒▓▓▓███████▓█▒▒▓▓██████▓█∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙∙∙∙▓▓▓▓▓▓▒░▒▒▓▒▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙∙∙∙▒▓▒▓▓▓▒░▒▒▒▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙∙∙∙∙░▓▒▒▒▓▒▒▒▒▒▓▓▓∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙∙∙∙∙∙∙▓▒▒▒▒▒▒▒▒▒▓▓▓∙∙∙∙∙∙∙∙∙░∙∙∙░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙∙∙∙∙∙∙∙▓▒▒▒▒▒▒▒▒▓▓▓▓▓∙∙∙∙∙∙∙░∙∙∙∙░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙∙∙∙∙∙∙∙∙▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓∙∙∙∙∙░∙∙∙∙░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░▓▒▒▒▒▒▒▒▒▒▓▓▓▓▓░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙█∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▓▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓░░∙∙∙∙░∙∙∙∙░░▒▓███░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙░░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓░▓▓▓▓▒▓▓▓█████████▓∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙░░░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▒▒▓▒▒▓▓▓▓▓▓▓▓▓████∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙░░░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓██∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙░░░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓█∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙░░░░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░▒▒▒▒▒▒▒▒▒▒▒▒▒░░▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓█░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▒░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░▒▒▒▒▒▒▒▒▒▒▒▒░░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x3183, 0xace9, 0xde4a, 0xe68a, 0xde6a, 0xd609, 0x9427, 0x2101, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0861, 0x5a84, 0xb4e7, 0xcd46, 0xc546, 0xc526, 0xac66, 0x5202, 0x0840, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x9c68, 0xde4a, 0xde4a, 0xde4a, 0xde4a, 0xde4a, 0xde2a, 0xde2a, 0xde4a, 0x7345, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0840, 0xc568, 0xcd67, 0xc547, 0xc527, 0xbd06, 0xbce6, 0xbce6, 0xbce6, 0xac85, 0x0840, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x7346, 0xde29, 0xd5e9, 0xd60a, 0xd62a, 0xde2a, 0xde4a, 0xde4a, 0xde2a, 0xd62a, 0xde6a, 0x4a23, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1081, 0xcdc9, 0xcda8, 0xcd68, 0xc567, 0xc547, 0xc527, 0xc527, 0xbd06, 0xbce6, 0xbce6, 0xb4c6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0861, 0xd5c9, 0xd5c9, 0xd5c9, 0xd5e9, 0xd5ea, 0xd60a, 0xd62a, 0xde2a, 0xde4a, 0xde4a, 0xde2a, 0xd609, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x7b86, 0xde09, 0xcda8, 0xcda8, 0xcd88, 0xcd68, 0xc567, 0xc547, 0xc527, 0xc527, 0xbd06, 0xc546, 0x7324, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8387, 0xd5e8, 0xcd88, 0xcda9, 0xd5c9, 0xd5c9, 0xd5e9, 0xd60a, 0xd60a, 0xd62a, 0xde4a, 0xde4a, 0xe68a, 0x3982, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xcda9, 0xd5e9, 0xd5e9, 0xcdc9, 0xcda8, 0xcda8, 0xcd88, 0xc568, 0xc567, 0xc547, 0xc527, 0xbd07, 0xaca5, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xac87, 0xcd88, 0xcd68, 0xcd88, 0xcda9, 0xcda9, 0xd5c9, 0xd5c9, 0xd5e9, 0xd60a, 0xd60a, 0xd62a, 0xe68a, 0x8be6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xde2a, 0xd609, 0xd5e9, 0xd5e9, 0xd5c9, 0xcdc9, 0xcda8, 0xcda8, 0xcd88, 0xcd68, 0xc547, 0xc547, 0xcd67, 0x0820, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xb4c6, 0xc567, 0xc548, 0xcd68, 0xcd68, 0xcd88, 0xcda9, 0xcda9, 0xd5c9, 0xd5e9, 0xd5e9, 0xd60a, 0xde4a, 0x8c07, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xde4a, 0xd609, 0xd609, 0xd5e9, 0xd5e9, 0xd5e9, 0xd5e9, 0xcdc9, 0xcda8, 0xcda8, 0xcd88, 0xc567, 0xcd88, 0x0820, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xa468, 0xc547, 0xc527, 0xc547, 0xc548, 0xcd68, 0xcd68, 0xcd88, 0xcda9, 0xcda9, 0xd5c9, 0xd5e9, 0xde4a, 0x83a6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xde4a, 0xd62a, 0xd62a, 0xd609, 0xd609, 0xd5e9, 0xd5e9, 0xd5e9, 0xd5c9, 0xcdc8, 0xcda8, 0xcd88, 0xcda8, 0x0020, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x6b06, 0xcd67, 0xbd07, 0xbd07, 0xc527, 0xc547, 0xc548, 0xcd68, 0xcd88, 0xcd88, 0xcda9, 0xcda9, 0xde49, 0x18a0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xc5aa, 0xde4a, 0xd62a, 0xd62a, 0xd60a, 0xd609, 0xd609, 0xd5e9, 0xd5e9, 0xd5e9, 0xd5c9, 0xcdc8, 0xace7, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xcd88, 0xbce6, 0xbce6, 0xbd07, 0xbd27, 0xc527, 0xc547, 0xc548, 0xcd68, 0xcd88, 0xcda8, 0xbd48, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x7346, 0xe6ab, 0xde4a, 0xde2a, 0xd62a, 0xd62a, 0xd60a, 0xd609, 0xd609, 0xd5e9, 0xd5e9, 0xde49, 0x62e4, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x4a03, 0xcd86, 0xb4c6, 0xb4c6, 0xbce7, 0xbd07, 0xbd27, 0xc527, 0xc548, 0xcd68, 0xe629, 0x3162, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xd60c, 0xe68a, 0xde4a, 0xde4a, 0xde4a, 0xd62a, 0xd62a, 0xd60a, 0xd609, 0xde4a, 0xb508, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x5a85, 0xcd67, 0xc526, 0xbce6, 0xb4c6, 0xbce7, 0xc527, 0xcda7, 0xc588, 0x41c3, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xace9, 0xe68b, 0xde8a, 0xde4a, 0xde4a, 0xde4a, 0xde6a, 0xe68a, 0x9c67, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x9407, 0xcd87, 0xd5a7, 0xd5c7, 0xcda8, 0x7325, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2943, 0xcdca, 0xe6cb, 0xe6cb, 0xeecb, 0xc5aa, 0x2922, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1000, 0x2000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1800, 0x2800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2800, 0x8000, 0x8800, 0x9800, 0x7800, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2800, 0x8000, 0x0800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x7000, 0xb000, 0x2000, 0x0000, 0x1800, 0xe000, 0x1000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0800, 0x2800, 0x3800, 0x4800, 0x4800, 0x4800, 0x8000, 0x0800, 0x0000, 0x0000, 0x0000, 0x0000, 0x2963, 0x5264, 0x8c06, 0xcdc9, 0xcdc8, 0xcda7, 0xcd87, 0xcda7, 0xd5c8, 0xd5e8, 0xde09, 0xcdca, 0x8be6, 0x5aa4, 0x2963, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0800, 0xa800, 0xd000, 0x1800, 0x0000, 0x0000, 0x1000, 0xe800, 0x1800, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0800, 0x5800, 0xa000, 0xa800, 0x8000, 0x6000, 0x5800, 0x8800, 0xd000, 0xe000, 0xc000, 0x3000, 0x0000, 0x4a25, 0xde2a, 0xde07, 0xc546, 0xb4c5, 0xaca5, 0xac85, 0xac85, 0xac85, 0xac65, 0xac85, 0xaca5, 0xb4c6, 0xb4c6, 0xbd07, 0xcd87, 0xe648, 0xde4a, 0x4204, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0800, 0x4000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0800, 0xc000, 0xe800, 0x2000, 0x0000, 0x0000, 0x0000, 0x2800, 0xd000, 0x0800, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1000, 0x5800, 0x7800, 0x7000, 0x4000, 0x1000, 0x0000, 0x0000, 0x0000, 0x2000, 0x9800, 0x1000, 0x2800, 0xb000, 0xe800, 0xd365, 0xcda7, 0xbd06, 0xb4c6, 0xb4a6, 0xb4a5, 0xb4a5, 0xb4a5, 0xac85, 0xac85, 0xac85, 0xac65, 0xac65, 0xac85, 0xaca5, 0xb4a6, 0xb4c6, 0xb4e6, 0xc547, 0xd5e8, 0xc5a9, 0x5a85, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xa800, 0xe800, 0x6000, 0x0000, 0x0000, 0x0000, 0x0000, 0xb000, 0xf800, 0x5800, 0x0000, 0x0000, 0x0000, 0x0000, 0x6000, 0x8000, 0x0000, 0x0000, 0x5000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0800, 0x0000, 0x0000, 0x0800, 0x5800, 0x8800, 0x2800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1000, 0xc000, 0x1800, 0x0000, 0x0020, 0xb4a9, 0xf8a0, 0xf040, 0xbb84, 0xb4a6, 0xbcc6, 0xbcc6, 0xb4c6, 0xb4c6, 0xb4a6, 0xb4a5, 0xb4a5, 0xac85, 0xac85, 0xac85, 0xac65, 0xac65, 0xac85, 0xaca5, 0xb4a6, 0xb4c6, 0xbce6, 0xbd07, 0xde49, 0x9c88, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x6800, 0xf800, 0xd800, 0x1000, 0x0000, 0x0000, 0x0000, 0x9800, 0xf800, 0xb800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xa000, 0x2000, 0x0000, 0x4800, 0x2000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x4000, 0x8000, 0x8800, 0x7800, 0x7800, 0x8800, 0xa000, 0x7000, 0x2000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xa800, 0x4800, 0x0000, 0x3184, 0xde4b, 0xd5c8, 0xd962, 0xf800, 0xd1c2, 0xa486, 0xbce6, 0xbce6, 0xbce6, 0xbcc6, 0xb4c6, 0xb4c6, 0xb4a5, 0xb4a5, 0xb4a5, 0xac85, 0xac85, 0xac85, 0xac65, 0xac85, 0xac85, 0xb4a6, 0xb4c6, 0xb4c6, 0xbce6, 0xcda8, 0xcdc9, 0x0860, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1800, 0x8800, 0x5800, 0x0000, 0x0000, 0x0000, 0x1000, 0xe800, 0xf800, 0x5800, 0x0000, 0x0000, 0x0000, 0x5800, 0xf800, 0xf000, 0x3000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2800, 0x9000, 0x0000, 0x5000, 0x3800, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0800, 0x4000, 0x9000, 0x6800, 0x1000, 0x0000, 0x0000, 0x3000, 0x8000, 0x5800, 0xa800, 0x1000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x9000, 0xb000, 0x0000, 0x39c4, 0xef0c, 0xd609, 0xcda8, 0xd243, 0xf800, 0xe8a1, 0xa405, 0xbce7, 0xbd27, 0xbd06, 0xbce6, 0xbcc6, 0xbcc6, 0xb4c6, 0xb4c6, 0xb4a5, 0xb4a5, 0xaca5, 0xac85, 0xac85, 0xac85, 0xac65, 0xac85, 0xac85, 0xb4a6, 0xb4c6, 0xb4c6, 0xc547, 0xe68b, 0x0860, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2800, 0xf800, 0xe800, 0x1000, 0x0000, 0x0000, 0x0000, 0x7800, 0xf800, 0xb000, 0x0000, 0x0000, 0x0000, 0x2800, 0xf000, 0xf800, 0xa000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xa800, 0x3000, 0x5800, 0x4800, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2800, 0x9000, 0x3800, 0x0800, 0x0000, 0x0000, 0x0000, 0x7000, 0x4000, 0x0800, 0x0000, 0xa800, 0x7000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x4000, 0xe800, 0x2000, 0x3184, 0xef0c, 0xde2a, 0xd609, 0xd5e9, 0xda63, 0xf800, 0xf060, 0x9bc5, 0xbd07, 0xc547, 0xc527, 0xc507, 0xbd06, 0xbce6, 0xbcc6, 0xbcc6, 0xb4c6, 0xb4c6, 0xb4a5, 0xb4a5, 0xaca5, 0xac85, 0xac85, 0xac65, 0xac65, 0xac85, 0xac85, 0xb4a6, 0xb4c6, 0xc547, 0xc5a9, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xc000, 0xf800, 0x8000, 0x0000, 0x0000, 0x0000, 0x1800, 0xf000, 0xf800, 0x3800, 0x0000, 0x0000, 0x1000, 0xa800, 0xf800, 0xf800, 0x4800, 0x0000, 0x0000, 0x0000, 0x0000, 0x4000, 0xc800, 0x8000, 0x2800, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x4800, 0x7000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1800, 0x7000, 0x2000, 0x0000, 0x0000, 0x0000, 0x8800, 0xb800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1800, 0xe800, 0x7800, 0x0881, 0xdeac, 0xde6b, 0xde4a, 0xd62a, 0xd609, 0xd9e3, 0xf800, 0xf080, 0x9bc6, 0xbd27, 0xc568, 0xc567, 0xc547, 0xc527, 0xbd06, 0xbd06, 0xbce6, 0xbcc6, 0xbcc6, 0xb4c6, 0xb4a6, 0xb4a5, 0xb4a5, 0xac85, 0xac85, 0xac85, 0xac85, 0xac65, 0xac85, 0xac85, 0xb4a6, 0xcd87, 0x8c07, 0x2000, 0x7800, 0x0800, 0x0000, 0x0000, 0x0000, 0x7000, 0xd000, 0xe800, 0x2000, 0x0000, 0x0000, 0x0000, 0xb800, 0xf800, 0x9800, 0x0000, 0x0000, 0x0000, 0x6000, 0x7000, 0xf800, 0xd800, 0x0800, 0x0000, 0x0000, 0x0000, 0x0800, 0x9000, 0x2800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x6800, 0x3800, 0x0000, 0x0000, 0x0000, 0x0000, 0x2800, 0x7000, 0x0800, 0x0000, 0x0000, 0x0000, 0x0000, 0x7000, 0xd800, 0x0800, 0x0000, 0x0000, 0x0000, 0x0000, 0xb000, 0xe800, 0x1000, 0xb58b, 0xe68b, 0xde6b, 0xde6b, 0xde4a, 0xd60a, 0xe921, 0xf800, 0xe142, 0x9426, 0xbd68, 0xcda8, 0xcd88, 0xc568, 0xc547, 0xc547, 0xc527, 0xbd07, 0xbce6, 0xbce6, 0xbcc6, 0xbcc6, 0xb4c6, 0xb4a6, 0xb4a5, 0xb4a5, 0xac85, 0xac85, 0xac85, 0xac85, 0xac65, 0xac85, 0xac85, 0xeaa3, 0xf800, 0x6000, 0x0000, 0x0000, 0x0000, 0x4000, 0x8000, 0xf800, 0x7000, 0x0000, 0x0000, 0x0000, 0x4800, 0xf800, 0xe000, 0x2000, 0x0000, 0x0000, 0x4800, 0x2800, 0x8800, 0xf800, 0x9000, 0x0000, 0x0000, 0x0000, 0x0000, 0x9000, 0x1800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x6800, 0x3800, 0x0000, 0x0000, 0x0000, 0x0000, 0x3800, 0x5800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x7800, 0xe000, 0x1000, 0x0000, 0x0000, 0x0000, 0x5000, 0xf800, 0x8000, 0x5ac6, 0xeeec, 0xe68b, 0xe6ab, 0xe68b, 0xde8b, 0xddc9, 0xf040, 0xf800, 0xba84, 0x9467, 0xcda9, 0xd5e9, 0xcdc9, 0xcda8, 0xcda8, 0xd5c8, 0xd5c8, 0xd5c8, 0xcd87, 0xc547, 0xbce6, 0xbcc6, 0xbcc6, 0xb4c6, 0xb4c6, 0xb4a5, 0xb4a5, 0xb465, 0xcaa3, 0xd262, 0xc242, 0xb3a4, 0xb364, 0xf800, 0xf0a1, 0x1000, 0x0000, 0x0000, 0x3000, 0x6800, 0xf000, 0xd000, 0x0800, 0x0000, 0x0000, 0x1000, 0xd800, 0xf800, 0x8000, 0x0000, 0x0000, 0x1000, 0x4800, 0x0000, 0x8800, 0xf800, 0x5000, 0x0000, 0x0000, 0x0000, 0x7000, 0x2800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x6800, 0x3000, 0x0000, 0x0000, 0x0000, 0x0000, 0x4000, 0x5800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x9000, 0xd800, 0x0800, 0x0000, 0x0000, 0x1800, 0xe800, 0xe000, 0x2040, 0xce4c, 0xe6ac, 0xe6cc, 0xe6cc, 0xe6ab, 0xe6cc, 0xd3a6, 0xf800, 0xf080, 0x8ba6, 0xb508, 0xd609, 0xd609, 0xd609, 0xe6aa, 0xeecb, 0xcdeb, 0x9469, 0x7367, 0xace9, 0xd60b, 0xe669, 0xcd87, 0xbce6, 0xbce6, 0xbcc6, 0xb4c6, 0xbbe4, 0xe8e1, 0xc303, 0x9be4, 0x9b44, 0xbaa3, 0xc961, 0xf800, 0xd282, 0x10a1, 0x0000, 0x0800, 0x5000, 0xa000, 0xf800, 0x6800, 0x0000, 0x0000, 0x0000, 0xa000, 0xf800, 0xe000, 0x1800, 0x0000, 0x0000, 0x7800, 0x0800, 0x0000, 0x6800, 0xf800, 0x4800, 0x0000, 0x0800, 0x8800, 0x3000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x5000, 0x4800, 0x0000, 0x0000, 0x0000, 0x0000, 0x3800, 0x5800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xb800, 0xc000, 0x0000, 0x0000, 0x0000, 0x9000, 0xf800, 0x7800, 0x5ae6, 0xe6cc, 0xeeed, 0xef0c, 0xeeec, 0xeecc, 0xde6b, 0xf081, 0xf800, 0xc2c4, 0x9c67, 0xcdc9, 0xde4a, 0xe68b, 0xeeec, 0x5264, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x20e2, 0x7b86, 0xde29, 0xc506, 0xbd06, 0xd283, 0xf080, 0xb344, 0x93c5, 0x9c05, 0xaba4, 0xc1e2, 0xf800, 0xf040, 0x8b03, 0xa4a6, 0x0000, 0x6000, 0x6000, 0xf800, 0xc800, 0x1000, 0x0000, 0x0000, 0x4000, 0xe800, 0xf800, 0x7000, 0x0000, 0x0000, 0x5800, 0x1800, 0x0000, 0x0000, 0x1800, 0xe000, 0xa800, 0x7800, 0x7800, 0x2000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x1000, 0x6000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2800, 0x6800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0800, 0xe800, 0x8800, 0x0000, 0x0000, 0x2800, 0xf800, 0xf000, 0x2000, 0xdeac, 0xef0d, 0xf72d, 0xef2d, 0xef0d, 0xef0c, 0xe3a6, 0xf800, 0xe1e3, 0x9427, 0xbd69, 0xde4b, 0xe6ab, 0xd62b, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x62e6, 0xe628, 0xda02, 0xf820, 0xb344, 0x8ba4, 0xa425, 0xb4a6, 0xbac3, 0xf020, 0xf800, 0xb9c2, 0x7303, 0xc566, 0x78e1, 0x3800, 0xd000, 0xf800, 0x4800, 0x0000, 0x0000, 0x1800, 0x9000, 0xd800, 0xd800, 0x1800, 0x0000, 0x3800, 0x3800, 0x0000, 0x0000, 0x0000, 0x0000, 0x2800, 0x6800, 0x3000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x8000, 0x0800, 0x0000, 0x0000, 0x0000, 0x1000, 0x7000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x4800, 0xf800, 0x4000, 0x0000, 0x0000, 0xc000, 0xf800, 0x9000, 0x5aa5, 0xef2d, 0xf72d, 0xf74e, 0xf74d, 0xf74d, 0xe62b, 0xf040, 0xf142, 0xa427, 0xad29, 0xde4b, 0xe6ac, 0xf72d, 0x0860, 0x0000, 0x0000, 0x0000, 0x0000, 0x4000, 0x9800, 0x0800, 0x0000, 0x0000, 0x0000, 0x0000, 0xc122, 0xf800, 0xc263, 0x8b85, 0xa445, 0xb4c6, 0xb4c6, 0xe0a0, 0xf800, 0xe860, 0x7aa3, 0x8ba4, 0xbce5, 0x7921, 0x3800, 0xf800, 0xc000, 0x0800, 0x0000, 0x0000, 0x7800, 0x2000, 0xe800, 0x9000, 0x0000, 0x3800, 0x4800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x4800, 0x2000, 0x0000, 0x0000, 0x0000, 0x0800, 0x8000, 0x0800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xc000, 0xd000, 0x0800, 0x0000, 0x4000, 0xf800, 0xe800, 0x2000, 0x9cc9, 0xeeed, 0xf72d, 0xf74d, 0xf76e, 0xf6cc, 0xf102, 0xf162, 0xbca8, 0xad29, 0xd64b, 0xe6cc, 0xef2d, 0x4204, 0x0000, 0x1800, 0x0000, 0x0000, 0x8800, 0xf800, 0x5000, 0x0000, 0x0000, 0x0000, 0x0000, 0x9800, 0xf800, 0xf242, 0x8bc5, 0x9c46, 0xbce7, 0xbd07, 0xc344, 0xf800, 0xf800, 0xb982, 0x7b24, 0xa445, 0xc2c3, 0x7b24, 0xa000, 0xf800, 0x4800, 0x0000, 0x0000, 0x7000, 0x3000, 0x1000, 0xe000, 0x6000, 0x4800, 0x6000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x7000, 0x0000, 0x0000, 0x0000, 0x0000, 0x5800, 0x2000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x3000, 0xf800, 0x7800, 0x0000, 0x0000, 0xc000, 0xf800, 0xa000, 0x0000, 0xe6ad, 0xef0d, 0xf72d, 0xf74d, 0xeeed, 0xf183, 0xf284, 0xbd29, 0xb56a, 0xd66c, 0xef0d, 0xef0d, 0xde8c, 0x5820, 0x9000, 0x7000, 0x6000, 0x2000, 0xf000, 0xc800, 0x0800, 0x0000, 0x0000, 0x0000, 0x7000, 0xf800, 0xe000, 0x8ae5, 0xacc6, 0xbd07, 0xc547, 0xc527, 0xe8e1, 0xf800, 0xf040, 0x8ac3, 0x93e5, 0xbb44, 0xbbe5, 0xaca6, 0xd800, 0xb800, 0x0000, 0x0000, 0x5800, 0x3800, 0x0000, 0x0000, 0x7000, 0xb800, 0x4800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x7000, 0x2800, 0x0000, 0x0000, 0x0000, 0x3000, 0x6800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x9800, 0xe800, 0x2000, 0x0000, 0x5000, 0xf800, 0xf800, 0x3800, 0x28c2, 0xef0d, 0xef0d, 0xeeed, 0xf509, 0xf203, 0xebe7, 0xc5aa, 0xbdaa, 0xdeac, 0xef2d, 0xf74d, 0xee2b, 0xe263, 0x5800, 0x0000, 0x0000, 0x7000, 0x8800, 0xf800, 0x5800, 0x0000, 0x0000, 0x0000, 0x4800, 0xf800, 0xf800, 0x8000, 0x1081, 0xd5e8, 0xcd88, 0xcda8, 0xcb85, 0xf040, 0xf800, 0xb9e2, 0x7b64, 0xb3e5, 0xcb04, 0xac65, 0xbcc6, 0xe000, 0x6800, 0x0000, 0x5000, 0x5800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x1000, 0x6800, 0x0000, 0x0000, 0x0000, 0x0000, 0x7800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2800, 0xf800, 0x7800, 0x0000, 0x0800, 0xc800, 0xf800, 0xb000, 0x0000, 0xb9e4, 0xf345, 0xf366, 0xf224, 0xec07, 0xddeb, 0xc5aa, 0xce0b, 0xe6cc, 0xef2d, 0xf76e, 0xed6a, 0xf8e1, 0x8a44, 0x0000, 0x0000, 0x0000, 0x9000, 0xf000, 0xb800, 0x0800, 0x0000, 0x0000, 0x0800, 0xd800, 0xf800, 0xe000, 0x1000, 0x0000, 0xde4b, 0xcdc9, 0xcd07, 0xda03, 0xf040, 0xf060, 0x8304, 0x9be5, 0xd263, 0xb4a6, 0xac66, 0xcd66, 0xc000, 0x8800, 0x8000, 0x4800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x3000, 0x2000, 0x0000, 0x0000, 0x0000, 0x4000, 0x3800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xc800, 0xd000, 0x0800, 0x0000, 0x3000, 0xf800, 0xf000, 0x4000, 0x0000, 0xcdcb, 0xde0b, 0xd58a, 0xd60b, 0xce2b, 0xce0b, 0xde6c, 0xeeec, 0xef0d, 0xf72d, 0xec88, 0xf840, 0xf4e8, 0x83e7, 0x7ba7, 0x83e7, 0x8b66, 0xf000, 0xf820, 0x9a63, 0x6b25, 0x7ba6, 0x7ba6, 0xb9c2, 0xf0a1, 0xf800, 0xb9c2, 0x5ae4, 0x7345, 0xe6ab, 0xd60a, 0xdb45, 0xd223, 0xf800, 0xc1e3, 0x93c5, 0xcae4, 0xbca6, 0xaca6, 0xb4e6, 0xd5a7, 0x48e1, 0x6800, 0x3000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x4000, 0x0000, 0x0000, 0x0000, 0x0000, 0x9000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x5800, 0xf800, 0x4800, 0x0000, 0x0000, 0xa800, 0xf800, 0xc000, 0x0000, 0x0000, 0xe6ac, 0xe68c, 0xde6b, 0xde6c, 0xe68c, 0xe6cc, 0xeeed, 0xef0d, 0xef2d, 0xed09, 0xf800, 0xea24, 0xad09, 0xce0b, 0xef0d, 0xf76e, 0xe326, 0xf800, 0xe922, 0x9c68, 0xc5ea, 0xe6cc, 0xe4a8, 0xdd89, 0xe860, 0xf840, 0xa365, 0xad08, 0xd62a, 0xde4a, 0xd3e6, 0xd3c6, 0xf020, 0xf080, 0x9b65, 0xc263, 0xcc86, 0xb4e7, 0xbd07, 0xc547, 0xd5a7, 0x7324, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x4000, 0x3800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2000, 0xf000, 0x8000, 0x0000, 0x0000, 0x3000, 0xf800, 0xf800, 0x5000, 0x0000, 0x0000, 0xeecc, 0xe68b, 0xe68c, 0xe6ac, 0xeecc, 0xeeec, 0xeeec, 0xef0d, 0xed09, 0xf820, 0xf8e1, 0xb488, 0xbd8a, 0xe6cc, 0xf72d, 0xee4c, 0xf040, 0xf800, 0xc386, 0xad49, 0xdeac, 0xe5ea, 0xec68, 0xcd69, 0xf800, 0xe8e1, 0x9c47, 0xc5ca, 0xde8b, 0xdcc8, 0xe427, 0xd305, 0xf800, 0xc264, 0xc263, 0xcc67, 0xbd48, 0xbd48, 0xc588, 0xcd88, 0xd5c8, 0x8bc6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x6800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0800, 0xd000, 0xb000, 0x0800, 0x0000, 0x0000, 0x9800, 0xf800, 0xc000, 0x0800, 0x0000, 0x10a1, 0xeecb, 0xde4b, 0xe66b, 0xe68b, 0xe6ac, 0xeecc, 0xeecc, 0xe62b, 0xf0c1, 0xf800, 0xcb05, 0xa4c9, 0xd64c, 0xef0d, 0xf72d, 0xeb46, 0xf800, 0xe983, 0x9ca8, 0xce2b, 0xeecc, 0xeb86, 0xde8c, 0xd58a, 0xf000, 0xda03, 0xa4e8, 0xd64b, 0xe549, 0xe386, 0xcdca, 0xe901, 0xf840, 0xd1e3, 0xc3e6, 0xbd48, 0xc589, 0xcdc9, 0xcdc9, 0xcda8, 0xd5e8, 0x9406, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x6000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xa800, 0xc000, 0x1800, 0x0000, 0x0000, 0x1000, 0xe800, 0xf800, 0x5000, 0x0000, 0x0000, 0x18c2, 0xe68a, 0xde0a, 0xde2a, 0xde4b, 0xe66b, 0xe68b, 0xe66b, 0xe962, 0xf800, 0xf0c1, 0x9c27, 0xc5aa, 0xe6cc, 0xef0d, 0xe60b, 0xf020, 0xf800, 0xbb46, 0xad49, 0xe70d, 0xec88, 0xee4c, 0xde8c, 0xe68c, 0xe8a1, 0xe224, 0xc58a, 0xe427, 0xec07, 0xd60a, 0xcc68, 0xf800, 0xe922, 0xa3a6, 0xad08, 0xc5a9, 0xd60a, 0xd60a, 0xd609, 0xd5e9, 0xde29, 0x9427, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x5800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x9800, 0xc800, 0x1800, 0x0000, 0x0000, 0x0000, 0x8000, 0xf800, 0xc000, 0x0800, 0x0000, 0x0000, 0x18e2, 0xe66a, 0xd5ea, 0xd60a, 0xde2a, 0xde2a, 0xde6b, 0xe3a6, 0xf800, 0xf800, 0xcac5, 0xa4c9, 0xde4b, 0xeeec, 0xeecc, 0xe9a3, 0xf800, 0xf0e1, 0xa448, 0xce0b, 0xe4e9, 0xed29, 0xde8c, 0xe6cd, 0xef2d, 0xe3a7, 0xe902, 0xea44, 0xe447, 0xd62b, 0xce0a, 0xe1a3, 0xf820, 0xb3c6, 0x9ca8, 0xcdca, 0xde4a, 0xde4a, 0xde4a, 0xd62a, 0xd60a, 0xde4a, 0x9427, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x6000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xa000, 0xa800, 0x1000, 0x0000, 0x0000, 0x0000, 0x1800, 0xf000, 0xf800, 0x3800, 0x0000, 0x0000, 0x0000, 0x18c2, 0xe669, 0xcda9, 0xcdc9, 0xd5ea, 0xd60a, 0xd60a, 0xda23, 0xf800, 0xf0a1, 0x9bc6, 0xbd69, 0xe68c, 0xeeed, 0xe488, 0xdaa5, 0xf800, 0xcae5, 0xa509, 0xe5aa, 0xed09, 0xde8c, 0xdeac, 0xef2d, 0xf74d, 0xeeed, 0xcdcb, 0xbd6a, 0xcdeb, 0xde6b, 0xe325, 0xf800, 0xe1a3, 0x9468, 0xc5ca, 0xde6b, 0xe68b, 0xde6b, 0xde6a, 0xde4a, 0xde4a, 0xe68a, 0x8c06, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x7000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2000, 0xa800, 0x8000, 0x0800, 0x0000, 0x0000, 0x0000, 0x0000, 0x7800, 0xf800, 0x9000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0861, 0xde29, 0xc588, 0xcd89, 0xcda9, 0xd5c9, 0xd60a, 0xda23, 0xf800, 0xd1a3, 0x9427, 0xcdca, 0xe68b, 0xe509, 0xe468, 0xcb46, 0xf840, 0xac27, 0xcc68, 0xec07, 0xde6c, 0xde6c, 0xeeed, 0xf72d, 0xf72d, 0xf72d, 0xef0d, 0xe6ed, 0xe68c, 0xec68, 0xe326, 0xf800, 0xbb66, 0xad09, 0xde6b, 0xe6ac, 0xe6ab, 0xe68b, 0xe68b, 0xde6b, 0xde6a, 0xe6ab, 0x7345, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x6000, 0x4000, 0x0000, 0x0000, 0x0800, 0x7000, 0xb000, 0x2800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0800, 0xe800, 0xe800, 0x2000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xde09, 0xc548, 0xc568, 0xc588, 0xcda9, 0xcdc9, 0xd9c3, 0xf800, 0xb284, 0xa4a8, 0xd5ea, 0xddaa, 0xe407, 0xde6b, 0xd529, 0xf0c1, 0xea64, 0xec27, 0xe68c, 0xde8c, 0xeeed, 0xf72d, 0xf74d, 0xf74e, 0xf74e, 0xf74e, 0xf72d, 0xf447, 0xeeed, 0xe962, 0xf1c3, 0xb529, 0xd64b, 0xef0d, 0xef0d, 0xef0c, 0xeeec, 0xeeec, 0xeecc, 0xe6cb, 0xef0c, 0x20e1, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x4800, 0x6000, 0x6800, 0x6800, 0x3800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x9800, 0xf800, 0x5800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xd5e9, 0xc527, 0xbd27, 0xc548, 0xc568, 0xc588, 0xda03, 0xf800, 0xa305, 0xace8, 0xd508, 0xdb86, 0xe66b, 0x62e5, 0x4a24, 0x5162, 0x5142, 0x4a04, 0x4a24, 0x4a44, 0x5265, 0x5265, 0x5265, 0x5265, 0x5265, 0x6265, 0x8962, 0x5224, 0x7a04, 0xf000, 0x6962, 0x39e3, 0x4a44, 0x5265, 0x5265, 0x5265, 0x5265, 0x5265, 0x5265, 0x5265, 0x5265, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2800, 0xf800, 0xa000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xacea, 0xbd26, 0xbce7, 0xbd07, 0xbd27, 0xc548, 0xcb04, 0xf800, 0xbaa4, 0xcbe6, 0xdb05, 0xc589, 0xd60a, 0x7366, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1800, 0x7800, 0x0800, 0x0000, 0xb000, 0xa800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xb000, 0xd000, 0x1800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x5aa6, 0xc526, 0xb4c6, 0xb4e6, 0xbce7, 0xbd07, 0xbca7, 0xca23, 0xd1a2, 0xcb45, 0xb4c8, 0xbd28, 0xcdc9, 0xb528, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1800, 0x7000, 0x0000, 0x0000, 0x2800, 0xe800, 0x2800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x5800, 0xf000, 0x3000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xc567, 0xac85, 0xaca5, 0xb4c6, 0xb4e6, 0xb4e7, 0xacc7, 0x9446, 0x9c66, 0xacc7, 0xc548, 0xcd89, 0xe68b, 0x2101, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1800, 0x9000, 0x0800, 0x0000, 0x0000, 0x9800, 0x7800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1800, 0xe000, 0x5000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xc567, 0xa444, 0xa465, 0xac85, 0xaca6, 0xb4c6, 0xb4c6, 0xb4c6, 0xb4e7, 0xbd27, 0xc548, 0xc568, 0xcdc9, 0xe68b, 0x0840, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0800, 0x9000, 0x1000, 0x0000, 0x0000, 0x3000, 0xd000, 0x1000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xb000, 0x7000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8385, 0xac84, 0xa424, 0xa445, 0xa465, 0xac85, 0xaca6, 0xb4c6, 0xb4e6, 0xbd07, 0xbd27, 0xc548, 0xc548, 0xd5e9, 0xc5a9, 0x18c1, 0x0000, 0x0000, 0x0000, 0x0800, 0xa800, 0x3000, 0x0000, 0x0000, 0x0000, 0xb000, 0x4800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x9000, 0x8000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x4a25, 0xc524, 0x9c04, 0xa424, 0xa444, 0xa445, 0xac65, 0xac85, 0xaca6, 0xb4c6, 0xb4e6, 0xbd07, 0xbd27, 0xc548, 0xcda8, 0xe6aa, 0x5a85, 0x0000, 0x0000, 0x7800, 0x4800, 0x0000, 0x0000, 0x0000, 0x3800, 0x8000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x31a4, 0xe6ce, 0x10a1, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x6800, 0x7000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xc569, 0x9c04, 0x9be4, 0x9c04, 0xa424, 0xa444, 0xa465, 0xac65, 0xac85, 0xaca6, 0xb4c6, 0xb4e7, 0xbd07, 0xbd27, 0xc568, 0xde09, 0xcdea, 0x8a23, 0xc8a1, 0x0800, 0x0000, 0x0000, 0x0800, 0xa800, 0x0800, 0x0000, 0x0000, 0x0020, 0x41e4, 0x6b26, 0x8408, 0xbd8b, 0xf74e, 0xf74e, 0xf76e, 0x62e5, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x5800, 0xe800, 0xc800, 0x1000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x6000, 0x6800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2102, 0xb4a4, 0x93c3, 0x93e4, 0x9be4, 0x9c04, 0xa424, 0xa444, 0xa465, 0xac85, 0xac85, 0xb4a6, 0xb4c6, 0xb4e7, 0xbd07, 0xbd27, 0xc507, 0xe8e1, 0xdcc7, 0xcdc9, 0xe66a, 0xe68b, 0xeb25, 0xd509, 0xcdca, 0xe68b, 0xef0d, 0xf72d, 0xf74d, 0xf72d, 0xef0d, 0xeeec, 0xeeec, 0xeeed, 0xf72d, 0xbd8b, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x2000, 0xf000, 0xf800, 0xf800, 0x2800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0800, 0x7800, 0x4000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xa466, 0x9c03, 0x93a3, 0x93c3, 0x9be4, 0x9c04, 0x9c04, 0xa424, 0xa445, 0xa465, 0xac85, 0xac86, 0xb4a6, 0xb4e6, 0xbd07, 0xbbe5, 0xe901, 0x9c26, 0xaca7, 0xc5a9, 0xd3a5, 0xdbc6, 0xb528, 0xc589, 0xd60a, 0xde4a, 0xde4b, 0xde6b, 0xe68b, 0xe6ac, 0xeecc, 0xeecc, 0xeeec, 0xef0d, 0xef2e, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x3800, 0xf800, 0xf800, 0xd800, 0x0800, 0x0000, 0x0000, 0x0000, 0x2800, 0x7800, 0x1800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x18e2, 0xbce5, 0x93a3, 0x93a3, 0x93a3, 0x93c3, 0x9be4, 0x9c04, 0x9c24, 0xa424, 0xa445, 0xa465, 0xac85, 0xaca5, 0xb4c6, 0xbac3, 0xe162, 0x93e5, 0xb4c7, 0xcbe6, 0xdae4, 0xb4e8, 0xb4e8, 0xcd89, 0xd5e9, 0xd60a, 0xde2a, 0xde2a, 0xde4b, 0xe66b, 0xe68b, 0xe6ac, 0xeecc, 0xeecc, 0xf76e, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x2800, 0xf000, 0xf800, 0xe000, 0x3000, 0x1800, 0x5800, 0x8000, 0x3800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x39a3, 0xb463, 0x8b63, 0x8b83, 0x93a3, 0x93a3, 0x93c3, 0x9be4, 0x9c04, 0x9c24, 0xa424, 0xa445, 0xa465, 0xaca5, 0xbaa3, 0xe8c1, 0xa364, 0xca83, 0xcb44, 0xaca7, 0xacc7, 0xbd48, 0xcd89, 0xcda9, 0xcdc9, 0xd5ea, 0xd60a, 0xde2a, 0xde2b, 0xde4b, 0xe66b, 0xe68b, 0xe6ac, 0xf74e, 0x2962, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x6800, 0xd000, 0xe000, 0xc800, 0x9800, 0x4800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x7305, 0xac23, 0x8b62, 0x8b63, 0x8b83, 0x93a3, 0x93c3, 0x93c3, 0x9be4, 0x9c04, 0x9c24, 0xa444, 0xa445, 0xa3e4, 0xc981, 0xd161, 0xb344, 0xa466, 0xa466, 0xb4e7, 0xbd27, 0xc568, 0xc568, 0xcd89, 0xcda9, 0xd5c9, 0xd5ea, 0xd60a, 0xde2a, 0xde4b, 0xde4b, 0xe66b, 0xef0c, 0x5284, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0800, 0x0800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x5a64, 0xac23, 0x8b42, 0x8b42, 0x8b83, 0x8b83, 0x93a3, 0x93c3, 0x93c4, 0x9be4, 0x9c04, 0xa424, 0xa424, 0x93e4, 0x8ba4, 0x93e5, 0xa445, 0xaca6, 0xb4e6, 0xbd07, 0xbd27, 0xc548, 0xc568, 0xc588, 0xcda9, 0xcda9, 0xd5c9, 0xd5ea, 0xd60a, 0xde2a, 0xde4b, 0xe6ab, 0x7366, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x4a24, 0xac44, 0x9382, 0x8342, 0x8b62, 0x8b83, 0x8b83, 0x93a3, 0x93c3, 0x9be4, 0x9be4, 0x9c04, 0x9c04, 0x9c04, 0xa425, 0xa465, 0xac85, 0xaca6, 0xb4c6, 0xb4e7, 0xbd07, 0xbd27, 0xc548, 0xc568, 0xcd88, 0xcda9, 0xcda9, 0xd5c9, 0xd5ea, 0xd60a, 0xde4b, 0x9447, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0841, 0xa466, 0x9be2, 0x8b42, 0x8342, 0x8b62, 0x8b83, 0x9383, 0x93a3, 0x93c3, 0x9be4, 0x9be4, 0x9c04, 0xa424, 0xa444, 0xa465, 0xac85, 0xac85, 0xb4a6, 0xb4c6, 0xb4e7, 0xbd07, 0xbd27, 0xc548, 0xc568, 0xcd88, 0xcda9, 0xcdc9, 0xd5e9, 0xd60a, 0xacc8, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x3982, 0x93c5, 0x9be3, 0x9382, 0x8342, 0x8b63, 0x8b83, 0x9383, 0x93a3, 0x93c3, 0x9be4, 0x9c04, 0x9c04, 0xa424, 0xa444, 0xa465, 0xac85, 0xaca5, 0xb4a6, 0xb4c6, 0xbce7, 0xbd07, 0xbd27, 0xc548, 0xc568, 0xd5c9, 0xde29, 0xde2a, 0x7325, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2942, 0x93c5, 0x9be3, 0x9bc3, 0x9382, 0x8b83, 0x9383, 0x93a3, 0x93a3, 0x93c3, 0x9be4, 0x9c04, 0x9c04, 0xa424, 0xa444, 0xa465, 0xac85, 0xaca5, 0xb4e6, 0xbd06, 0xc547, 0xcd88, 0xc587, 0xacc7, 0x4a03, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1081, 0x2901, 0x4a02, 0x7b44, 0x9be5, 0xa403, 0xac23, 0xac23, 0xac43, 0xac64, 0xb484, 0xb4a4, 0xb4a4, 0xb4a5, 0xac86, 0xa446, 0x6ae4, 0x5223, 0x2921, 0x18c1, 0x0820, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 +}; + +#endif diff --git a/yoRadio/fonts/glcdfont.c b/yoRadio/fonts/glcdfont.c new file mode 100644 index 0000000..f00553c --- /dev/null +++ b/yoRadio/fonts/glcdfont.c @@ -0,0 +1,273 @@ +#ifndef FONT5X7_H +#define FONT5X7_H + +#ifdef __AVR__ + #include + #include +#elif defined(ESP8266) + #include +#else + #define PROGMEM +#endif + +// Standard ASCII 5x7 font + +static const unsigned char font[] PROGMEM = { +0x00, 0x00, 0x00, 0x00, 0x00, +0x3E, 0x55, 0x51, 0x55, 0x3E, +0x3E, 0x6B, 0x6F, 0x6B, 0x3E, +0x0C, 0x1E, 0x3C, 0x1E, 0x0C, +0x08, 0x1C, 0x3E, 0x1C, 0x08, +0x1C, 0x4A, 0x7F, 0x4A, 0x1C, +0x18, 0x5C, 0x7F, 0x5C, 0x18, +0x00, 0x1C, 0x1C, 0x1C, 0x00, +0x7F, 0x63, 0x63, 0x63, 0x7F, +0x00, 0x1C, 0x14, 0x1C, 0x00, +0x7F, 0x63, 0x6B, 0x63, 0x7F, +0x30, 0x48, 0x4D, 0x33, 0x07, +0x06, 0x29, 0x79, 0x29, 0x06, +0x20, 0x50, 0x3F, 0x02, 0x0C, +0x60, 0x7F, 0x05, 0x35, 0x3F, +0x2A, 0x1C, 0x77, 0x1C, 0x2A, +0x00, 0x7F, 0x3E, 0x1C, 0x08, +0x08, 0x1C, 0x3E, 0x7F, 0x00, +0x14, 0x22, 0x7F, 0x22, 0x14, +0x00, 0x5F, 0x00, 0x5F, 0x00, +0x06, 0x09, 0x7F, 0x01, 0x7F, +0x4A, 0x55, 0x55, 0x55, 0x29, +0x60, 0x60, 0x60, 0x60, 0x60, +0x54, 0x62, 0x7F, 0x62, 0x54, +0x08, 0x04, 0x7E, 0x04, 0x08, +0x08, 0x10, 0x3F, 0x10, 0x08, +0x08, 0x08, 0x2A, 0x1C, 0x08, +0x08, 0x1C, 0x2A, 0x08, 0x08, +0x1C, 0x10, 0x10, 0x10, 0x10, +0x1C, 0x3E, 0x08, 0x3E, 0x1C, +0x30, 0x3C, 0x3F, 0x3C, 0x30, +0x06, 0x1E, 0x7E, 0x1E, 0x06, +0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x5F, 0x00, 0x00, +0x00, 0x07, 0x00, 0x07, 0x00, +0x14, 0x7F, 0x14, 0x7F, 0x14, +0x24, 0x2A, 0x7F, 0x2A, 0x12, +0x23, 0x13, 0x08, 0x64, 0x62, +0x36, 0x49, 0x56, 0x20, 0x50, +0x00, 0x00, 0x07, 0x00, 0x00, +0x00, 0x1C, 0x22, 0x41, 0x00, +0x00, 0x41, 0x22, 0x1C, 0x00, +0x14, 0x08, 0x3E, 0x08, 0x14, +0x08, 0x08, 0x3E, 0x08, 0x08, +0x00, 0xA0, 0x60, 0x00, 0x00, +0x08, 0x08, 0x08, 0x08, 0x08, +0x00, 0x60, 0x60, 0x00, 0x00, +0x20, 0x10, 0x08, 0x04, 0x02, +0x3E, 0x51, 0x49, 0x45, 0x3E, +0x44, 0x42, 0x7F, 0x40, 0x40, +0x42, 0x61, 0x51, 0x49, 0x46, +0x21, 0x41, 0x45, 0x4B, 0x31, +0x18, 0x14, 0x12, 0x7F, 0x10, +0x27, 0x45, 0x45, 0x45, 0x39, +0x3C, 0x4A, 0x49, 0x49, 0x30, +0x01, 0x71, 0x09, 0x05, 0x03, +0x36, 0x49, 0x49, 0x49, 0x36, +0x06, 0x49, 0x49, 0x29, 0x1E, +0x00, 0x6C, 0x6C, 0x00, 0x00, +0x00, 0xAC, 0x6C, 0x00, 0x00, +0x08, 0x14, 0x22, 0x41, 0x00, +0x14, 0x14, 0x14, 0x14, 0x14, +0x00, 0x41, 0x22, 0x14, 0x08, +0x02, 0x01, 0x51, 0x09, 0x06, +0x3E, 0x41, 0x5D, 0x55, 0x5E, +0x7C, 0x12, 0x11, 0x12, 0x7C, +0x7F, 0x49, 0x49, 0x49, 0x36, +0x3E, 0x41, 0x41, 0x41, 0x22, +0x7F, 0x41, 0x41, 0x22, 0x1C, +0x7F, 0x49, 0x49, 0x49, 0x41, +0x7F, 0x09, 0x09, 0x09, 0x01, +0x3E, 0x41, 0x49, 0x49, 0x7A, +0x7F, 0x08, 0x08, 0x08, 0x7F, +0x00, 0x41, 0x7F, 0x41, 0x00, +0x20, 0x40, 0x41, 0x3F, 0x01, +0x7F, 0x08, 0x14, 0x22, 0x41, +0x7F, 0x40, 0x40, 0x40, 0x60, +0x7F, 0x02, 0x0C, 0x02, 0x7F, +0x7F, 0x04, 0x08, 0x10, 0x7F, +0x3E, 0x41, 0x41, 0x41, 0x3E, +0x7F, 0x09, 0x09, 0x09, 0x06, +0x3E, 0x41, 0x51, 0x21, 0x5E, +0x7F, 0x09, 0x19, 0x29, 0x46, +0x46, 0x49, 0x49, 0x49, 0x31, +0x03, 0x01, 0x7F, 0x01, 0x03, +0x3F, 0x40, 0x40, 0x40, 0x3F, +0x1F, 0x20, 0x40, 0x20, 0x1F, +0x3F, 0x40, 0x3C, 0x40, 0x3F, +0x63, 0x14, 0x08, 0x14, 0x63, +0x07, 0x08, 0x70, 0x08, 0x07, +0x61, 0x51, 0x49, 0x45, 0x43, +0x00, 0x7F, 0x41, 0x41, 0x00, +0x02, 0x04, 0x08, 0x10, 0x20, +0x00, 0x41, 0x41, 0x7F, 0x00, +0x04, 0x02, 0x01, 0x02, 0x04, +0x40, 0x40, 0x40, 0x40, 0x40, +0x00, 0x01, 0x02, 0x04, 0x00, +0x20, 0x54, 0x54, 0x54, 0x78, +0x7F, 0x48, 0x44, 0x44, 0x38, +0x38, 0x44, 0x44, 0x44, 0x48, +0x38, 0x44, 0x44, 0x48, 0x7F, +0x38, 0x54, 0x54, 0x54, 0x18, +0x08, 0x7E, 0x09, 0x01, 0x02, +0x08, 0x54, 0x54, 0x58, 0x3C, +0x7F, 0x08, 0x04, 0x04, 0x78, +0x00, 0x44, 0x7D, 0x40, 0x00, +0x20, 0x40, 0x44, 0x3D, 0x00, +0x7F, 0x10, 0x10, 0x28, 0x44, +0x00, 0x41, 0x7F, 0x40, 0x00, +0x7C, 0x04, 0x78, 0x04, 0x78, +0x7C, 0x08, 0x04, 0x04, 0x78, +0x38, 0x44, 0x44, 0x44, 0x38, +0x7C, 0x14, 0x14, 0x14, 0x08, +0x08, 0x14, 0x14, 0x0C, 0x7C, +0x7C, 0x08, 0x04, 0x04, 0x08, +0x48, 0x54, 0x54, 0x54, 0x24, +0x04, 0x3F, 0x44, 0x40, 0x20, +0x3C, 0x40, 0x40, 0x20, 0x7C, +0x1C, 0x20, 0x40, 0x20, 0x1C, +0x3C, 0x40, 0x38, 0x40, 0x3C, +0x44, 0x28, 0x10, 0x28, 0x44, +0x0C, 0x50, 0x50, 0x50, 0x3C, +0x44, 0x64, 0x54, 0x4C, 0x44, +0x00, 0x08, 0x36, 0x41, 0x00, +0x00, 0x00, 0x7F, 0x00, 0x00, +0x00, 0x41, 0x36, 0x08, 0x00, +0x02, 0x01, 0x02, 0x04, 0x02, +0x70, 0x48, 0x44, 0x48, 0x70, +0x00, 0x0E, 0x11, 0x0E, 0x00, +0x00, 0x12, 0x1F, 0x10, 0x00, +0x00, 0x12, 0x19, 0x16, 0x00, +0x00, 0x11, 0x15, 0x0B, 0x00, +0x00, 0x07, 0x04, 0x1F, 0x00, +0x00, 0x17, 0x15, 0x09, 0x00, +0x00, 0x0E, 0x15, 0x09, 0x00, +0x00, 0x01, 0x1D, 0x03, 0x00, +0x00, 0x0A, 0x15, 0x0A, 0x00, +0x00, 0x12, 0x15, 0x0E, 0x00, +0x00, 0x04, 0x04, 0x04, 0x00, +0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +0x3E, 0x00, 0x00, 0x00, 0x00, +0x3E, 0x3E, 0x00, 0x00, 0x00, +0x3E, 0x3E, 0x00, 0x3E, 0x00, +0x3E, 0x3E, 0x00, 0x3E, 0x3E, +0x58, 0x64, 0x04, 0x64, 0x58, +0x7F, 0x3E, 0x1C, 0x08, 0x7F, +0x7F, 0x08, 0x1C, 0x3E, 0x7F, +0x7F, 0x7F, 0x00, 0x7F, 0x7F, +0x08, 0x3E, 0x22, 0x22, 0x22, +0x22, 0x22, 0x22, 0x3E, 0x08, +0x40, 0x00, 0x40, 0x00, 0x40, +0x60, 0x00, 0x40, 0x00, 0x40, +0x60, 0x00, 0x70, 0x00, 0x40, +0x60, 0x00, 0x70, 0x00, 0x78, +0x7C, 0x00, 0x40, 0x00, 0x40, +0x7C, 0x00, 0x7E, 0x00, 0x40, +0x7C, 0x00, 0x7E, 0x00, 0x7F, +0x1C, 0x77, 0x41, 0x41, 0x41, +0x41, 0x41, 0x41, 0x41, 0x41, +0x41, 0x41, 0x41, 0x7F, 0x00, +0x1C, 0x77, 0x41, 0x5D, 0x5D, +0x41, 0x41, 0x41, 0x5D, 0x5D, +0x5D, 0x5D, 0x41, 0x5D, 0x5D, +0x5D, 0x5D, 0x41, 0x7F, 0x00, +0x22, 0x1C, 0x14, 0x1C, 0x22, +0x00, 0x08, 0x1C, 0x08, 0x00, +0x00, 0x00, 0x77, 0x00, 0x00, +0x46, 0x5D, 0x55, 0x5D, 0x31, +0x7C, 0x55, 0x54, 0x55, 0x44, +0x08, 0x08, 0x2A, 0x08, 0x08, +0x00, 0x14, 0x08, 0x14, 0x00, +0x08, 0x14, 0x22, 0x08, 0x14, +0x7F, 0x41, 0x71, 0x31, 0x1F, +0x03, 0x05, 0x7F, 0x05, 0x03, +0x22, 0x14, 0x7F, 0x55, 0x22, +0x02, 0x55, 0x7D, 0x05, 0x02, +0x06, 0x09, 0x09, 0x06, 0x00, +0x44, 0x44, 0x5F, 0x44, 0x44, +0x1C, 0x14, 0x1C, 0x22, 0x7F, +0x20, 0x3E, 0x61, 0x3E, 0x20, +0x20, 0x50, 0x3F, 0x02, 0x0C, +0x80, 0x7C, 0x20, 0x3C, 0x40, +0x44, 0x3C, 0x04, 0x7C, 0x44, +0x00, 0x00, 0x08, 0x00, 0x00, +0x38, 0x55, 0x54, 0x55, 0x18, +0x7E, 0x08, 0x10, 0x7F, 0x01, +0x08, 0x10, 0x08, 0x04, 0x02, +0x14, 0x08, 0x22, 0x14, 0x08, +0x0E, 0x06, 0x0A, 0x10, 0x20, +0x20, 0x10, 0x0A, 0x06, 0x0E, +0x38, 0x30, 0x28, 0x04, 0x02, +0x02, 0x04, 0x28, 0x30, 0x38, +0x7E, 0x11, 0x11, 0x11, 0x7E, +0x7F, 0x49, 0x49, 0x49, 0x31, +0x7F, 0x49, 0x49, 0x49, 0x36, +0x7F, 0x01, 0x01, 0x01, 0x03, +0xC0, 0x7F, 0x41, 0x7F, 0xC0, +0x7F, 0x49, 0x49, 0x49, 0x41, +0x77, 0x08, 0x7F, 0x08, 0x77, +0x41, 0x49, 0x49, 0x49, 0x36, +0x7F, 0x10, 0x08, 0x04, 0x7F, +0x7C, 0x21, 0x12, 0x09, 0x7C, +0x7F, 0x08, 0x14, 0x22, 0x41, +0x40, 0x3E, 0x01, 0x01, 0x7F, +0x7F, 0x02, 0x0C, 0x02, 0x7F, +0x7F, 0x08, 0x08, 0x08, 0x7F, +0x3E, 0x41, 0x41, 0x41, 0x3E, +0x7F, 0x01, 0x01, 0x01, 0x7F, +0x7F, 0x09, 0x09, 0x09, 0x06, +0x3E, 0x41, 0x41, 0x41, 0x22, +0x01, 0x01, 0x7F, 0x01, 0x01, +0x07, 0x48, 0x48, 0x48, 0x3F, +0x0E, 0x11, 0x7F, 0x11, 0x0E, +0x63, 0x14, 0x08, 0x14, 0x63, +0x7F, 0x40, 0x40, 0x7F, 0xC0, +0x07, 0x08, 0x08, 0x08, 0x7F, +0x7F, 0x40, 0x7F, 0x40, 0x7F, +0x7F, 0x40, 0x7F, 0x40, 0xFF, +0x01, 0x7F, 0x48, 0x48, 0x30, +0x7F, 0x48, 0x48, 0x30, 0x7F, +0x7F, 0x48, 0x48, 0x48, 0x30, +0x22, 0x41, 0x49, 0x49, 0x3E, +0x7F, 0x08, 0x3E, 0x41, 0x3E, +0x46, 0x29, 0x19, 0x09, 0x7F, +0x20, 0x54, 0x54, 0x54, 0x78, +0x3C, 0x4A, 0x4A, 0x49, 0x31, +0x7C, 0x54, 0x54, 0x54, 0x28, +0x7C, 0x04, 0x04, 0x04, 0x0C, +0xC0, 0x78, 0x44, 0x7C, 0xC0, +0x38, 0x54, 0x54, 0x54, 0x18, +0x6C, 0x10, 0x7C, 0x10, 0x6C, +0x44, 0x54, 0x54, 0x54, 0x28, +0x7C, 0x20, 0x10, 0x08, 0x7C, +0x7C, 0x40, 0x26, 0x10, 0x7C, +0x7C, 0x10, 0x10, 0x28, 0x44, +0x40, 0x38, 0x04, 0x04, 0x7C, +0x7C, 0x08, 0x10, 0x08, 0x7C, +0x7C, 0x10, 0x10, 0x10, 0x7C, +0x38, 0x44, 0x44, 0x44, 0x38, +0x7C, 0x04, 0x04, 0x04, 0x7C, +0x7C, 0x14, 0x14, 0x14, 0x08, +0x38, 0x44, 0x44, 0x44, 0x48, +0x04, 0x04, 0x7C, 0x04, 0x04, +0x0C, 0x50, 0x50, 0x50, 0x3C, +0x18, 0x24, 0xFC, 0x24, 0x18, +0x44, 0x28, 0x10, 0x28, 0x44, +0x7C, 0x40, 0x40, 0x7C, 0xC0, +0x0C, 0x10, 0x10, 0x10, 0x7C, +0x7C, 0x40, 0x7C, 0x40, 0x7C, +0x7C, 0x40, 0x7C, 0x40, 0xFC, +0x04, 0x7C, 0x50, 0x50, 0x20, +0x7C, 0x50, 0x50, 0x20, 0x7C, +0x7C, 0x50, 0x50, 0x50, 0x20, +0x28, 0x44, 0x54, 0x54, 0x38, +0x7C, 0x10, 0x38, 0x44, 0x38, +0x48, 0x34, 0x14, 0x14, 0x7C +}; +#endif // FONT5X7_H diff --git a/yoRadio/netserver.cpp b/yoRadio/netserver.cpp new file mode 100644 index 0000000..3757ada --- /dev/null +++ b/yoRadio/netserver.cpp @@ -0,0 +1,384 @@ +#include "netserver.h" +#include + +#include "config.h" +#include "player.h" +#include "display.h" +#include "options.h" +#include "network.h" + +NetServer netserver; + +AsyncWebServer webserver(80); +AsyncWebSocket websocket("/ws"); +AsyncUDP udp; + +String processor(const String& var); +void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); +void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len); +void handleHTTPPost(AsyncWebServerRequest * request); + +byte ssidCount; + +bool NetServer::begin() { + importRequest = false; + webserver.on("/", HTTP_GET, [](AsyncWebServerRequest * request) { + ssidCount = 0; + request->send(SPIFFS, "/www/index.html", String(), false, processor); + }); + webserver.serveStatic("/", SPIFFS, "/www/").setCacheControl("max-age=31536000"); + webserver.on("/", HTTP_POST, [](AsyncWebServerRequest * request) { + handleHTTPPost(request); + }); + webserver.on(PLAYLIST_PATH, HTTP_GET, [](AsyncWebServerRequest * request) { + request->send(SPIFFS, PLAYLIST_PATH, "application/octet-stream"); + }); + webserver.on(INDEX_PATH, HTTP_GET, [](AsyncWebServerRequest * request) { + request->send(SPIFFS, INDEX_PATH, "application/octet-stream"); + }); + webserver.on("/upload", HTTP_POST, [](AsyncWebServerRequest * request) { + //request->send(200); + }, handleUpload); + webserver.begin(); + websocket.onEvent(onWsEvent); + webserver.addHandler(&websocket); + + //echo -n "helle?" | socat - udp-datagram:255.255.255.255:44490,broadcast + if (udp.listen(44490)) { + udp.onPacket([](AsyncUDPPacket packet) { + if(strcmp((char*)packet.data(),"helle?")==0) + packet.println(WiFi.localIP()); + }); + } +} + +void NetServer::loop() { + websocket.cleanupClients(); + if (playlistrequest > 0) { + requestOnChange(PLAYLIST, playlistrequest); + playlistrequest = 0; + } + if (importRequest) { + if (importPlaylist()) { + requestOnChange(PLAYLIST, 0); + } + importRequest = false; + } + yield(); +} + +void NetServer::onWsMessage(void *arg, uint8_t *data, size_t len) { + AwsFrameInfo *info = (AwsFrameInfo*)arg; + if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) { + data[len] = 0; + char cmd[15], val[15]; + if (config.parseWsCommand((const char*)data, cmd, val, 15)) { + if (strcmp(cmd, "volume") == 0) { + byte v = atoi(val); + player.setVol(v, false); + } + } + } +} + +void NetServer::setRSSI(int val) { + rssi = val; + requestOnChange(NRSSI, 0); +} + +void NetServer::getPlaylist(uint8_t clientId) { + String dataString = ""; + File file = SPIFFS.open(PLAYLIST_PATH, "r"); + if (!file || file.isDirectory()) { + return; + } + char sName[BUFLEN], sUrl[BUFLEN], pOvol[30]; + int sOvol; + while (file.available()) { + String line = file.readStringUntil('\n'); + if (config.parseCSV(line.c_str(), sName, sUrl, sOvol)) { + sprintf(pOvol, "%d", sOvol); + dataString += "{\"name\":\"" + String(sName) + "\",\"url\":\"" + String(sUrl) + "\",\"ovol\":" + String(pOvol) + "},"; + } + } + if (dataString.length() > 0) { + if (clientId == 0) { + websocket.textAll("{\"file\": [" + dataString.substring(0, dataString.length() - 1) + "]}"); + } else { + websocket.text(clientId, "{\"file\": [" + dataString.substring(0, dataString.length() - 1) + "]}"); + } + } + file.close(); +} + +bool NetServer::savePlaylist(const char* post) { + File file = SPIFFS.open(PLAYLIST_PATH, "w"); + if (!file) { + return false; + } else { + file.print(post); + file.close(); + netserver.requestOnChange(PLAYLISTSAVED, 0); + } +} + +bool NetServer::importPlaylist() { + File tempfile = SPIFFS.open(TMP_PATH, "r"); + if (!tempfile) { + return false; + } + char sName[BUFLEN], sUrl[BUFLEN]; + int sOvol; + String line = tempfile.readStringUntil('\n'); + if (config.parseCSV(line.c_str(), sName, sUrl, sOvol)) { + File playlistfile = SPIFFS.open(PLAYLIST_PATH, "w"); + playlistfile.println(line); + while (tempfile.available()) { + line = tempfile.readStringUntil('\n'); + if (config.parseCSV(line.c_str(), sName, sUrl, sOvol)) { + playlistfile.println(line); + } + } + playlistfile.close(); + tempfile.close(); + SPIFFS.remove(TMP_PATH); + requestOnChange(PLAYLISTSAVED, 0); + return true; + } + if (config.parseJSON(line.c_str(), sName, sUrl, sOvol)) { + File playlistfile = SPIFFS.open(PLAYLIST_PATH, "w"); + String wline = String(sName) + "\t" + String(sUrl) + "\t" + String(sOvol); + playlistfile.println(wline); + while (tempfile.available()) { + line = tempfile.readStringUntil('\n'); + if (config.parseJSON(line.c_str(), sName, sUrl, sOvol)) { + wline = String(sName) + "\t" + String(sUrl) + "\t" + String(sOvol); + playlistfile.println(wline); + } + } + playlistfile.close(); + tempfile.close(); + SPIFFS.remove(TMP_PATH); + requestOnChange(PLAYLISTSAVED, 0); + return true; + } + tempfile.close(); + SPIFFS.remove(TMP_PATH); + return false; +} + +void NetServer::requestOnChange(requestType_e request, uint8_t clientId) { + char buf[BUFLEN + 50] = { 0 }; + switch (request) { + case PLAYLIST: { + getPlaylist(clientId); + break; + } + case PLAYLISTSAVED: { + config.indexPlaylist(); + config.initPlaylist(); + getPlaylist(clientId); + break; + } + case STATION: { + sprintf (buf, "{\"nameset\": \"%s\"}", config.station.name); + requestOnChange(ITEM, clientId); + break; + } + case ITEM: { + sprintf (buf, "{\"current\": %d}", config.store.lastStation); + break; + } + case TITLE: { + sprintf (buf, "{\"meta\": \"%s\"}", config.station.title); + break; + } + case VOLUME: { + sprintf (buf, "{\"vol\": %d}", config.store.volume); + break; + } + case NRSSI: { + sprintf (buf, "{\"rssi\": %d}", rssi); + break; + } + case BITRATE: { + sprintf (buf, "{\"bitrate\": %d}", config.station.bitrate); + break; + } + case MODE: { + sprintf (buf, "{\"mode\": \"%s\"}", player.mode == PLAYING ? "playing" : "stopped"); + break; + } + case EQUALIZER: { + sprintf (buf, "{\"bass\": %d, \"middle\": %d, \"trebble\": %d}", config.store.bass, config.store.middle, config.store.trebble); + break; + } + case BALANCE: { + sprintf (buf, "{\"balance\": %d}", config.store.balance); + break; + } + } + if (strlen(buf) > 0) { + if (clientId == 0) { + websocket.textAll(buf); + } else { + websocket.text(clientId, buf); + } + } +} + +String processor(const String& var) { // %Templates% + if (var == "VERSION") { + return VERSION; + } + if (var == "SSID") { + ssidCount++; + return String(config.ssids[ssidCount - 1].ssid); + } + if (var == "PASS") { + return String(config.ssids[ssidCount - 1].password); + } + if (var == "APMODE") { + return network.status == CONNECTED ? "" : " style=\"display: none!important\""; + } + if (var == "NOTAPMODE") { + return network.status == CONNECTED ? " hidden" : ""; + } + return String(); +} + +void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { + if (!index) { + request->_tempFile = SPIFFS.open(TMP_PATH , "w"); + } + if (len) { + request->_tempFile.write(data, len); + //TODO check index+len size + } + if (final) { + request->_tempFile.close(); + netserver.importRequest = true; + request->send(200); + } +} + +void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) { + switch (type) { + case WS_EVT_CONNECT: + Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str()); + netserver.requestOnChange(STATION, client->id()); + netserver.requestOnChange(TITLE, client->id()); + netserver.requestOnChange(VOLUME, client->id()); + netserver.requestOnChange(EQUALIZER, client->id()); + netserver.requestOnChange(BALANCE, client->id()); + netserver.requestOnChange(BITRATE, client->id()); + netserver.requestOnChange(MODE, client->id()); + netserver.playlistrequest = client->id(); + break; + case WS_EVT_DISCONNECT: + Serial.printf("WebSocket client #%u disconnected\n", client->id()); + break; + case WS_EVT_DATA: + netserver.onWsMessage(arg, data, len); + break; + case WS_EVT_PONG: + case WS_EVT_ERROR: + break; + } +} + +void handleHTTPPost(AsyncWebServerRequest * request) { + if (request->hasParam("wifisettings", true)) { + AsyncWebParameter* p = request->getParam("wifisettings", true); + if (p->value() != "") { + config.saveWifi(p->value().c_str()); + } + request->send(200); + return; + } + if (request->hasParam("playlist", true)) { + AsyncWebParameter* p = request->getParam("playlist", true); + netserver.savePlaylist(p->value().c_str()); + request->send(200); + return; + } + if (network.status != CONNECTED) { + request->send(404); + return; + } + if (request->hasParam("start", true)) { + player.request.station = config.store.lastStation; + request->send(200); + return; + } + if (request->hasParam("stop", true)) { + player.mode = STOPPED; + display.title("[stopped]"); + request->send(200); + return; + } + if (request->hasParam("prev", true)) { + player.prev(); + request->send(200); + return; + } + if (request->hasParam("next", true)) { + player.next(); + request->send(200); + return; + } + if (request->hasParam("volm", true)) { + player.stepVol(false); + request->send(200); + return; + } + if (request->hasParam("volp", true)) { + player.stepVol(true); + request->send(200); + return; + } + if (request->hasParam("vol", true)) { + AsyncWebParameter* p = request->getParam("vol", true); + int v = atoi(p->value().c_str()); + if (v < 0) v = 0; + if (v > 254) v = 254; + player.setVol(v, false); + request->send(200); + return; + } + if (request->hasParam("trebble", true)) { + AsyncWebParameter* pt = request->getParam("trebble", true); + AsyncWebParameter* pm = request->getParam("middle", true); + AsyncWebParameter* pb = request->getParam("bass", true); + int t = atoi(pt->value().c_str()); + int m = atoi(pm->value().c_str()); + int b = atoi(pb->value().c_str()); + //setTone(int8_t gainLowPass, int8_t gainBandPass, int8_t gainHighPass) + player.setTone(b, m, t); + config.setTone(b, m, t); + netserver.requestOnChange(EQUALIZER, 0); + request->send(200); + return; + } + if (request->hasParam("ballance", true)) { + AsyncWebParameter* p = request->getParam("ballance", true); + int b = atoi(p->value().c_str()); + player.setBalance(b); + config.setBalance(b); + netserver.requestOnChange(BALANCE, 0); + request->send(200); + return; + } + if (request->hasParam("playstation", true)) { + AsyncWebParameter* p = request->getParam("playstation", true); + int id = atoi(p->value().c_str()); + if (id < 1) id = 1; + if (id > config.store.countStation) id = config.store.countStation; + player.request.station = id; + player.request.doSave = true; + request->send(200); + return; + } + + request->send(404); +} diff --git a/yoRadio/netserver.h b/yoRadio/netserver.h new file mode 100644 index 0000000..c87c882 --- /dev/null +++ b/yoRadio/netserver.h @@ -0,0 +1,31 @@ +#ifndef netserver_h +#define netserver_h +#include "Arduino.h" + +#include "ESPAsyncWebServer.h" +#include "AsyncUDP.h" + +enum requestType_e { PLAYLIST, STATION, ITEM, TITLE, VOLUME, NRSSI, BITRATE, MODE, EQUALIZER, BALANCE, PLAYLISTSAVED }; + +class NetServer { + public: + uint8_t playlistrequest; // ClientId want the playlist + bool importRequest; + public: + NetServer() {}; + bool begin(); + void loop(); + void requestOnChange(requestType_e request, uint8_t clientId); + void setRSSI(int val); + void onWsMessage(void *arg, uint8_t *data, size_t len); + bool savePlaylist(const char* post); + private: + requestType_e request; + int rssi; + void getPlaylist(uint8_t clientId); + bool importPlaylist(); +}; + +extern NetServer netserver; + +#endif diff --git a/yoRadio/network.cpp b/yoRadio/network.cpp new file mode 100644 index 0000000..dcfab54 --- /dev/null +++ b/yoRadio/network.cpp @@ -0,0 +1,55 @@ +#include "network.h" +#include "WiFi.h" +#include "display.h" +#include "options.h" + +Network network; + +void Network::begin() { + config.initNetwork(); + if (config.ssidsCount == 0) { + raiseSoftAP(); + return; + } + byte ls = (config.store.lastSSID == 0 || config.store.lastSSID > config.ssidsCount) ? 0 : config.store.lastSSID - 1; + byte startedls = ls; + byte errcnt = 0; + WiFi.mode(WIFI_STA); + char buf[40] = { 0 }; + while (true) { + Serial.printf("Attempt to connect to %s\n", config.ssids[ls].ssid); + snprintf(buf, sizeof(buf) - 1, "ATTEMPT TO %s", config.ssids[ls].ssid); + display.bootString(buf, 110); + WiFi.begin(config.ssids[ls].ssid, config.ssids[ls].password); + strcpy(buf, "."); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); + strcat(buf, "."); + display.bootString(buf, 90); + errcnt++; + if (errcnt > 16) { + errcnt = 0; + ls++; + if (ls > config.ssidsCount - 1) ls = 0; + break; + } + } + if(WiFi.status() != WL_CONNECTED && ls==startedls){ + raiseSoftAP(); + return; + } + if (WiFi.status() == WL_CONNECTED) { + config.setLastSSID(ls + 1); + break; + } + } + digitalWrite(LED_BUILTIN, LOW); + status = CONNECTED; +} + +void Network::raiseSoftAP() { + WiFi.mode(WIFI_AP); + WiFi.softAP(apSsid, apPassword); + status = SOFT_AP; +} diff --git a/yoRadio/network.h b/yoRadio/network.h new file mode 100644 index 0000000..6ec70d1 --- /dev/null +++ b/yoRadio/network.h @@ -0,0 +1,21 @@ +#ifndef network_h +#define network_h + +#define apSsid "yoRadioAP" +#define apPassword "12345987" + +enum n_Status_e { CONNECTED, SOFT_AP, FAILED }; + +class Network { + public: + n_Status_e status; + public: + Network() {}; + void begin(); + private: + void raiseSoftAP(); +}; + +extern Network network; + +#endif diff --git a/yoRadio/options.h b/yoRadio/options.h new file mode 100644 index 0000000..3e396c3 --- /dev/null +++ b/yoRadio/options.h @@ -0,0 +1,47 @@ +#ifndef options_h +#define options_h + +#define VERSION "0.4.170" + +/* + * TFT DISPLAY + */ +/************** + * GND | GND * + * VCC | +5v * + * SCL | D18 * + * SDA | D23 * + * ************ + */ +#define TFT_CS 5 +#define TFT_RST 15 // Or set to -1 and connect to Arduino RESET pin +//#define TFT_RST -1 // we use the seesaw for resetting to save a pin +#define TFT_DC 4 + +/* + * I2S DAC + */ +#define I2S_DOUT 27 // DIN connection +#define I2S_BCLK 26 // BCLK Bit clock +#define I2S_LRC 25 // WSEL Left Right Clock + +/* + * ENCODER + */ +#define ENC_BTNL 13 +#define ENC_BTNB 12 +#define ENC_BTNR 14 + +/* + * BUTTONS + */ +#define BTN_LEFT 32 +#define BTN_CENTER 12 +#define BTN_RIGHT 33 + +/* + * ESP DEVBOARD + */ +#define LED_BUILTIN 2 + +#endif diff --git a/yoRadio/player.cpp b/yoRadio/player.cpp new file mode 100644 index 0000000..cb3c0ee --- /dev/null +++ b/yoRadio/player.cpp @@ -0,0 +1,128 @@ +#include "player.h" +#include "config.h" +#include "telnet.h" +#include "display.h" +#include "options.h" +#include "netserver.h" + +Player player; + +void Player::init() { + setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT); + setVolume(0); + mode = STOPPED; + requesToStart = true; + setBalance(config.store.balance); + setTone(config.store.bass, config.store.middle, config.store.trebble); + zeroRequest(); +} + +void Player::stopInfo() { + config.setSmartStart(0); + telnet.info(); + netserver.requestOnChange(MODE, 0); + requesToStart = true; +} + +void Player::loop() { + if (mode == PLAYING) { + Audio::loop(); + } else { + if (isRunning()) { + digitalWrite(LED_BUILTIN, LOW); + stopSong(); + stopInfo(); + } + } + if (request.station > 0) { + if (request.doSave) { + config.setLastStation(request.station); + } + play(request.station); + zeroRequest(); + } + if (request.volume >= 0) { + config.setVolume(request.volume, request.doSave); + display.volume(); + telnet.printf("##CLI.VOL#: %d\n", config.store.volume); + Audio::setVolume(volToI2S(request.volume)); + zeroRequest(); + } + yield(); +} + +void Player::zeroRequest() { + request.station = 0; + request.volume = -1; + request.doSave = false; +} + +void Player::play(byte stationId) { + stopSong(); + digitalWrite(LED_BUILTIN, LOW); + display.title("[connecting]"); + telnet.printf("##CLI.META#: %s\n", config.station.title); + config.loadStation(stationId); + setVol(config.store.volume, true); + display.station(); + telnet.printf("##CLI.NAMESET#: %d %s\n", config.store.lastStation, config.station.name); + if (connecttohost(config.station.url)) { + mode = PLAYING; + config.setSmartStart(1); + netserver.requestOnChange(MODE, 0); + digitalWrite(LED_BUILTIN, HIGH); + requesToStart = true; + }else{ + Serial.println("Some Unknown Bug..."); + } + zeroRequest(); +} + +void Player::prev() { + if (config.store.lastStation == 1) config.store.lastStation = config.store.countStation; else config.store.lastStation--; + request.station = config.store.lastStation; + request.doSave = true; +} + +void Player::next() { + if (config.store.lastStation == config.store.countStation) config.store.lastStation = 1; else config.store.lastStation++; + request.station = config.store.lastStation; + request.doSave = true; +} + +void Player::toggle() { + if (mode == PLAYING) { + mode = STOPPED; + display.title("[stopped]"); + } else { + request.station = config.store.lastStation; + } +} + +void Player::stepVol(bool up) { + if (up) { + if (config.store.volume < 254) { + setVol(config.store.volume + 1, false); + } + } else { + if (config.store.volume > 0) { + setVol(config.store.volume - 1, false); + } + } +} + +byte Player::volToI2S(byte volume) { + int vol = map(volume, 0, 254 - config.station.ovol * 2 , 0, 254); + if (vol > 254) vol = 254; + if (vol < 0) vol = 0; + return vol; +} + +void Player::setVol(byte volume, bool inside) { + if (inside) { + setVolume(volToI2S(volume)); + } else { + request.volume = volume; + request.doSave = true; + } +} diff --git a/yoRadio/player.h b/yoRadio/player.h new file mode 100644 index 0000000..732632f --- /dev/null +++ b/yoRadio/player.h @@ -0,0 +1,36 @@ +#ifndef player_h +#define player_h + +#include "src/audioI2S/AudioEx.h" + +enum audioMode_e { PLAYING, STOPPED }; + +struct audiorequest_t +{ + uint16_t station; + int volume; + bool doSave; +}; + +class Player: public Audio { + public: + audioMode_e mode; + audiorequest_t request; + bool requesToStart; + public: + void init(); + void loop(); + void zeroRequest(); + void play(byte stationId); + void prev(); + void next(); + void toggle(); + void stepVol(bool up); + void setVol(byte volume, bool inside); + byte volToI2S(byte volume); + void stopInfo(); +}; + +extern Player player; + +#endif diff --git a/yoRadio/src/audioI2S/Audio.cpp b/yoRadio/src/audioI2S/Audio.cpp new file mode 100644 index 0000000..01a8ca4 --- /dev/null +++ b/yoRadio/src/audioI2S/Audio.cpp @@ -0,0 +1,4538 @@ +/* + * Audio.cpp + * + * Created on: Oct 26,2018 + * Updated on: Jan 05,2022 + * Author: Wolle (schreibfaul1) + * + */ +#include "AudioEx.h" +#include "mp3_decoder/mp3_decoder.h" +#include "aac_decoder/aac_decoder.h" +#include "flac_decoder/flac_decoder.h" + +#ifdef SDFATFS_USED +fs::SDFATFS SD_SDFAT; +#endif + +//--------------------------------------------------------------------------------------------------------------------- +AudioBuffer::AudioBuffer(size_t maxBlockSize) { + // if maxBlockSize isn't set use defaultspace (1600 bytes) is enough for aac and mp3 player + if(maxBlockSize) m_resBuffSizeRAM = maxBlockSize; + if(maxBlockSize) m_maxBlockSize = maxBlockSize; +} + +AudioBuffer::~AudioBuffer() { + if(m_buffer) + free(m_buffer); + m_buffer = NULL; +} + +size_t AudioBuffer::init() { + if(m_buffer) free(m_buffer); + m_buffer = NULL; + if(psramInit()) { + // PSRAM found, AudioBuffer will be allocated in PSRAM + m_buffSize = m_buffSizePSRAM; + if(m_buffer == NULL) { + m_buffer = (uint8_t*) ps_calloc(m_buffSize, sizeof(uint8_t)); + m_buffSize = m_buffSizePSRAM - m_resBuffSizePSRAM; + if(m_buffer == NULL) { + // not enough space in PSRAM, use ESP32 Flash Memory instead + m_buffer = (uint8_t*) calloc(m_buffSize, sizeof(uint8_t)); + m_buffSize = m_buffSizeRAM - m_resBuffSizeRAM; + } + } + } else { // no PSRAM available, use ESP32 Flash Memory" + m_buffSize = m_buffSizeRAM; + m_buffer = (uint8_t*) calloc(m_buffSize, sizeof(uint8_t)); + m_buffSize = m_buffSizeRAM - m_resBuffSizeRAM; + } + if(!m_buffer) + return 0; + resetBuffer(); + return m_buffSize; +} + +void AudioBuffer::changeMaxBlockSize(uint16_t mbs){ + m_maxBlockSize = mbs; + return; +} + +uint16_t AudioBuffer::getMaxBlockSize(){ + return m_maxBlockSize; +} + +size_t AudioBuffer::freeSpace() { + if(m_readPtr >= m_writePtr) { + m_freeSpace = (m_readPtr - m_writePtr); + } else { + m_freeSpace = (m_endPtr - m_writePtr) + (m_readPtr - m_buffer); + } + if(m_f_start) + m_freeSpace = m_buffSize; + return m_freeSpace - 1; +} + +size_t AudioBuffer::writeSpace() { + if(m_readPtr >= m_writePtr) { + m_writeSpace = (m_readPtr - m_writePtr - 1); // readPtr must not be overtaken + } else { + if(getReadPos() == 0) + m_writeSpace = (m_endPtr - m_writePtr - 1); + else + m_writeSpace = (m_endPtr - m_writePtr); + } + if(m_f_start) + m_writeSpace = m_buffSize - 1; + return m_writeSpace; +} + +size_t AudioBuffer::bufferFilled() { + if(m_writePtr >= m_readPtr) { + m_dataLength = (m_writePtr - m_readPtr); + } else { + m_dataLength = (m_endPtr - m_readPtr) + (m_writePtr - m_buffer); + } + return m_dataLength; +} + +void AudioBuffer::bytesWritten(size_t bw) { + m_writePtr += bw; + if(m_writePtr == m_endPtr) { + m_writePtr = m_buffer; + } + if(bw && m_f_start) + m_f_start = false; +} + +void AudioBuffer::bytesWasRead(size_t br) { + m_readPtr += br; + if(m_readPtr >= m_endPtr) { + size_t tmp = m_readPtr - m_endPtr; + m_readPtr = m_buffer + tmp; + } +} + +uint8_t* AudioBuffer::getWritePtr() { + return m_writePtr; +} + +uint8_t* AudioBuffer::getReadPtr() { + size_t len = m_endPtr - m_readPtr; + if(len < m_maxBlockSize) { // be sure the last frame is completed + memcpy(m_endPtr, m_buffer, m_maxBlockSize - len); // cpy from m_buffer to m_endPtr with len + } +return m_readPtr; +} + +void AudioBuffer::resetBuffer() { + m_writePtr = m_buffer; + m_readPtr = m_buffer; + m_endPtr = m_buffer + m_buffSize; + m_f_start = true; + // memset(m_buffer, 0, m_buffSize); //Clear Inputbuffer +} + +uint32_t AudioBuffer::getWritePos() { + return m_writePtr - m_buffer; +} + +uint32_t AudioBuffer::getReadPos() { + return m_readPtr - m_buffer; +} +//--------------------------------------------------------------------------------------------------------------------- +Audio::Audio(bool internalDAC /* = false */, i2s_dac_mode_t channelEnabled /* = I2S_DAC_CHANNEL_LEFT_EN */ ) { + clientsecure.setInsecure(); // if that can't be resolved update to ESP32 Arduino version 1.0.5-rc05 or higher + m_f_channelEnabled = channelEnabled; + m_f_internalDAC = internalDAC; + //i2s configuration + m_i2s_num = I2S_NUM_0; // i2s port number + m_i2s_config.sample_rate = 16000; + m_i2s_config.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT; + m_i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT; + m_i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; // high interrupt priority + m_i2s_config.dma_buf_count = 8; // max buffers + m_i2s_config.dma_buf_len = 1024; // max value + m_i2s_config.use_apll = APLL_DISABLE; // must be disabled in V2.0.1-RC1 + m_i2s_config.tx_desc_auto_clear = true; // new in V1.0.1 + m_i2s_config.fixed_mclk = I2S_PIN_NO_CHANGE; + if (internalDAC) { + log_i("internal DAC"); + m_i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN ); + + #if ESP_ARDUINO_VERSION_MAJOR >= 2 + m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S); // vers >= 2.0.0 + #else + m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S_MSB); + #endif + + i2s_driver_install((i2s_port_t)m_i2s_num, &m_i2s_config, 0, NULL); + i2s_set_dac_mode(m_f_channelEnabled); + if(m_f_channelEnabled != I2S_DAC_CHANNEL_BOTH_EN) { + m_f_forceMono = true; + } + } + else { + m_i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX); + + #if ESP_ARDUINO_VERSION_MAJOR >= 2 + m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S); // Arduino vers. > 2.0.0 + #else + m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB); + #endif + + i2s_driver_install((i2s_port_t)m_i2s_num, &m_i2s_config, 0, NULL); + m_f_forceMono = false; + } + + i2s_zero_dma_buffer((i2s_port_t) m_i2s_num); + + for(int i = 0; i <3; i++) { + m_filter[i].a0 = 1; + m_filter[i].a1 = 0; + m_filter[i].a2 = 0; + m_filter[i].b1 = 0; + m_filter[i].b2 = 0; + } +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::initInBuff() { + if(!m_f_initInbuffOnce) { + size_t size = InBuff.init(); + if(size == m_buffSizeRAM - m_resBuffSizeRAM) { + sprintf(chbuf, "PSRAM not found, inputBufferSize: %u bytes", size - 1); + if(audio_info) + audio_info(chbuf); + m_f_psram = false; + m_f_initInbuffOnce = true; + } + if(size == m_buffSizePSRAM - m_resBuffSizePSRAM) { + sprintf(chbuf, "PSRAM found, inputBufferSize: %u bytes", size - 1); + if(audio_info) + audio_info(chbuf); + m_f_psram = true; + m_f_initInbuffOnce = true; + } + } + changeMaxBlockSize(1600); // default size mp3 or aac +} +//--------------------------------------------------------------------------------------------------------------------- +esp_err_t Audio::I2Sstart(uint8_t i2s_num) { + // It is not necessary to call this function after i2s_driver_install() (it is started automatically), + // however it is necessary to call it after i2s_stop() + return i2s_start((i2s_port_t) i2s_num); +} + +esp_err_t Audio::I2Sstop(uint8_t i2s_num) { + return i2s_stop((i2s_port_t) i2s_num); +} +//--------------------------------------------------------------------------------------------------------------------- +esp_err_t Audio::i2s_mclk_pin_select(const uint8_t pin) { + if(pin != 0 && pin != 1 && pin != 3) { + ESP_LOGE(TAG, "Only support GPIO0/GPIO1/GPIO3, gpio_num:%d", pin); + return ESP_ERR_INVALID_ARG; + } + switch(pin){ + case 0: + PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1); + WRITE_PERI_REG(PIN_CTRL, 0xFFF0); + break; + case 1: + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD_CLK_OUT3); + WRITE_PERI_REG(PIN_CTRL, 0xF0F0); + break; + case 3: + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD_CLK_OUT2); + WRITE_PERI_REG(PIN_CTRL, 0xFF00); + break; + default: + break; + } + return ESP_OK; +} +//--------------------------------------------------------------------------------------------------------------------- +Audio::~Audio() { + //I2Sstop(m_i2s_num); + //InBuff.~AudioBuffer(); #215 the AudioBuffer is automatically destroyed by the destructor + m_f_initInbuffOnce = false; + setDefaults(); + if(m_playlistBuff) {free(m_playlistBuff); m_playlistBuff = NULL;} + i2s_driver_uninstall((i2s_port_t)m_i2s_num); // #215 free I2S buffer +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::setDefaults() { + stopSong(); + initInBuff(); // initialize InputBuffer if not already done + InBuff.resetBuffer(); + MP3Decoder_FreeBuffers(); + FLACDecoder_FreeBuffers(); + if(!m_f_m3u8data) AACDecoder_FreeBuffers(); + if(!m_f_m3u8data) if(m_playlistBuff) {free(m_playlistBuff); m_playlistBuff = NULL;} // free if not m3u8 + client.stop(); + client.flush(); // release memory + clientsecure.stop(); + clientsecure.flush(); + while(!playI2Sremains()){;} + + sprintf(chbuf, "buffers freed, free Heap: %u bytes", ESP.getFreeHeap()); + if(audio_info) audio_info(chbuf); + + m_f_chunked = false; // Assume not chunked + m_f_ctseen = false; // Contents type not seen yet + m_f_firstmetabyte = false; + m_f_localfile = false; // SPIFFS or SD? (onnecttoFS) + m_f_playing = false; + m_f_ssl = false; + m_f_swm = true; // Assume no metaint (stream without metadata) + m_f_webfile = false; // Assume radiostream (connecttohost) + m_f_webstream = false; + m_f_tts = false; + m_f_firstCall = true; // InitSequence for processWebstream and processLokalFile + m_f_running = false; + m_f_loop = false; // Set if audio file should loop + m_f_unsync = false; // set within ID3 tag but not used + m_f_exthdr = false; // ID3 extended header + m_f_rtsp = false; // RTSP (m3u8)stream + m_f_m3u8data = false; // set again in processM3U8entries() if necessary + m_f_Log = true; // logging always allowed + + m_codec = CODEC_NONE; + m_playlistFormat = FORMAT_NONE; + m_datamode = AUDIO_NONE; + m_audioCurrentTime = 0; // Reset playtimer + m_audioFileDuration = 0; + m_audioDataStart = 0; + m_audioDataSize = 0; + m_avr_bitrate = 0; // the same as m_bitrate if CBR, median if VBR + m_bitRate = 0; // Bitrate still unknown + m_bytesNotDecoded = 0; // counts all not decodable bytes + m_chunkcount = 0; // for chunked streams + m_contentlength = 0; // If Content-Length is known, count it + m_curSample = 0; + m_metaint = 0; // No metaint yet + m_LFcount = 0; // For end of header detection + m_st_remember = 0; // Delete the last streamtitle hash + m_controlCounter = 0; // Status within readID3data() and readWaveHeader() + m_channels = 2; // assume stereo #209 + + //TEST loop + m_file_size = 0; + //TEST loop +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::httpPrint(const char* url) { + + // call to a new subdomain or if no connection is present connect first + + char* host = NULL; + char* extension = NULL; + char resp[256 + 100]; + uint8_t p1 = 0, p2 = 0; + + if(startsWith(url, "http")){if(m_f_ssl) p1 = 8; else p1 = 7;} + + p2 = indexOf(url, "/", p1); + host = strndup(url + p1, p2 - p1); + + extension = strdup(url + p2 + 1); + +// log_i("host %s", host); +// log_i("extension %s", extension); + + resp[0] = '\0'; + strcat(resp, "GET /"); + strcat(resp, extension); + strcat(resp, " HTTP/1.1\r\n"); + strcat(resp, "Host: "); + strcat(resp, host); + strcat(resp, "\r\nUser-Agent: ESP32 audioI2S\r\n"); + strcat(resp, "icy-metadata: 1\r\n"); + strcat(resp, "Accept-Encoding: identity\r\n"); + strcat(resp, "Connection: Keep-Alive\r\n\r\n"); + + int pos_colon = indexOf(host, ":", 0); + int pos_ampersand = indexOf(host, "&", 0); + int port = 80; + if(m_f_ssl) port = 443; + + if((pos_colon >= 0) && ((pos_ampersand == -1) or (pos_ampersand > pos_colon))){ + port = atoi(host + pos_colon + 1);// Get portnumber as integer + host[pos_colon] = '\0';// Host without portnumber + } + + if(!m_f_ssl){ + if(!client.connected()){ + if(m_f_Log) sprintf(chbuf, "new connection, host=%s, extension=%s, port=%i", host, extension, port); + if(m_f_Log) if(audio_info) audio_info(chbuf); + client.connect(host, port); + if(m_f_m3u8data && m_playlistBuff) strcpy(m_playlistBuff, url); // save new m3u8 chunklist + } + client.print(resp); + + } + else{ + if(!clientsecure.connected()){ + if(m_f_Log) sprintf(chbuf, "new connection, host=%s, extension=%s, port=%i", host, extension, port); + if(m_f_Log) if(audio_info) audio_info(chbuf); + clientsecure.connect(host, port); + if(m_f_m3u8data && m_playlistBuff) strcpy(m_playlistBuff, url); // save new m3u8 chunklist + } + clientsecure.print(resp); + } + + + if(host) {free(host); host = NULL;} + if(extension) {free(extension); extension = NULL;} + + strcpy(m_lastHost, url); + + return; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::connecttohost(const char* host, const char* user, const char* pwd) { + // user and pwd for authentification only, can be empty + + char* l_host = NULL; // local copy of host + char* h_host = NULL; // pointer of l_host without http:// or https:// + + + if(strlen(host) == 0) { + if(audio_info) audio_info("Hostaddress is empty"); + return false; + } + + if(strlen(host) >= 512 - 10) { + if(audio_info) audio_info("Hostaddress is too long"); + return false; + } + + l_host = (char*)malloc(strlen(host) + 10); + strcpy(l_host, host); + trim(l_host); + int p = indexOf(l_host, "http", 0); + if(p > 0){ + if(audio_info) audio_info("Hostaddress is wrong"); + if(l_host) { free(l_host); l_host = NULL;} + return false; + } + if(p < 0){ // http not found, shift right +7, then insert http:// + for(int i = strlen(l_host) + 1; i >= 0; i--){ + l_host[i + 7] = l_host[i]; + } + memcpy(l_host, "http://", 7); + } + + setDefaults(); + + sprintf(chbuf, "Connect to new host: \"%s\"", l_host); + if(audio_info) audio_info(chbuf); + + // authentification + uint8_t auth = strlen(user) + strlen(pwd); + char toEncode[auth + 4]; + toEncode[0] = '\0'; + strcat(toEncode, user); + strcat(toEncode, ":"); + strcat(toEncode, pwd); + char authorization[base64_encode_expected_len(strlen(toEncode)) + 1]; + authorization[0] = '\0'; + b64encode((const char*)toEncode, strlen(toEncode), authorization); + + // initializationsequence + int16_t pos_slash; // position of "/" in hostname + int16_t pos_colon; // position of "/" in hostname + int16_t pos_ampersand; // position of "&" in hostname + uint16_t port = 80; // port number + m_f_webstream = true; + setDatamode(AUDIO_HEADER); // Handle header + + h_host = l_host; + + if(startsWith(l_host, "http://")) { + h_host += 7; + m_f_ssl = false; + } + + if(startsWith(l_host, "https://")) { + h_host += 8; + m_f_ssl = true; + port = 443; + } + + // Is it a playlist? + if(endsWith(h_host, ".m3u" )) {m_playlistFormat = FORMAT_M3U; m_datamode = AUDIO_PLAYLISTINIT;} + if(endsWith(h_host, ".pls" )) {m_playlistFormat = FORMAT_PLS; m_datamode = AUDIO_PLAYLISTINIT;} + if(endsWith(h_host, ".asx" )) {m_playlistFormat = FORMAT_ASX; m_datamode = AUDIO_PLAYLISTINIT;} + // if url ...=asx www.fantasyfoxradio.de/infusions/gr_radiostatus_panel/gr_radiostatus_player.php?id=2&p=asx + if(endsWith(h_host, "=asx" )) {m_playlistFormat = FORMAT_ASX; m_datamode = AUDIO_PLAYLISTINIT;} + if(endsWith(h_host, "=pls" )) {m_playlistFormat = FORMAT_PLS; m_datamode = AUDIO_PLAYLISTINIT;} + // if url "http://n3ea-e2.revma.ihrhls.com/zc7729/hls.m3u8?rj-ttl=5&rj-tok=AAABe8unpPAAyu36Dkm0J4mzJg" + if(indexOf(h_host, ".m3u8", 0) >10) {m_playlistFormat = FORMAT_M3U8; m_datamode = AUDIO_PLAYLISTINIT;} + + // In the URL there may be an extension, like noisefm.ru:8000/play.m3u&t=.m3u + pos_slash = indexOf(h_host, "/", 0); + pos_colon = indexOf(h_host, ":", 0); + pos_ampersand = indexOf(h_host, "&", 0); + + char *hostwoext = NULL; // "skonto.ls.lv:8002" in "skonto.ls.lv:8002/mp3" + char *extension = NULL; // "/mp3" in "skonto.ls.lv:8002/mp3" + + if(pos_slash > 1) { + uint8_t hostwoextLen = pos_slash; + hostwoext = (char*)malloc(hostwoextLen + 1); + memcpy(hostwoext, h_host, hostwoextLen); + hostwoext[hostwoextLen] = '\0'; + uint16_t extLen = urlencode_expected_len(h_host + pos_slash); + extension = (char *)malloc(extLen); + memcpy(extension, h_host + pos_slash, extLen); + trim(extension); + urlencode(extension, extLen, true); + } + else{ // url has no extension + hostwoext = strdup(h_host); + extension = strdup("/"); + } + + if((pos_colon >= 0) && ((pos_ampersand == -1) or (pos_ampersand > pos_colon))){ + port = atoi(h_host + pos_colon + 1);// Get portnumber as integer + hostwoext[pos_colon] = '\0';// Host without portnumber + } + + sprintf(chbuf, "Connect to \"%s\" on port %d, extension \"%s\"", hostwoext, port, extension); + if(audio_info) audio_info(chbuf); + + char resp[strlen(h_host) + strlen(authorization) + 100]; + resp[0] = '\0'; + + strcat(resp, "GET "); + strcat(resp, extension); + strcat(resp, " HTTP/1.0\r\n"); + strcat(resp, "Host: "); + strcat(resp, hostwoext); + strcat(resp, "\r\n"); + strcat(resp, "Icy-MetaData:1\r\n"); + strcat(resp, "Authorization: Basic "); + strcat(resp, authorization); + strcat(resp, "\r\n"); + strcat(resp, "User-Agent: ESP32 audioI2S\r\n"); +// strcat(resp, "Accept-Encoding: gzip;q=0\r\n"); // otherwise the server assumes gzip compression +// strcat(resp, "Transfer-Encoding: \r\n"); // otherwise the server assumes gzip compression + strcat(resp, "Connection: keep-alive\r\n\r\n"); + + const uint32_t TIMEOUT_MS{250}; + uint32_t wtf; + if(m_f_ssl == false) { + uint32_t t = millis(); + if(client.connect(hostwoext, port, TIMEOUT_MS)) { + client.setNoDelay(true); + client.print(resp); + uint32_t dt = millis() - t; + sprintf(chbuf, "Connected to server in %u ms", dt); + if(audio_info) audio_info(chbuf); + + strcpy(m_lastHost, l_host); + m_f_running = true; + if(hostwoext) {free(hostwoext); hostwoext = NULL;} + if(extension) {free(extension); extension = NULL;} + if(l_host ) {free(l_host); l_host = NULL;} + wtf = millis(); + while(!client.connected()){ + if(millis()-wtf>TIMEOUT_MS * 2){ + sprintf(chbuf, "Request %s failed! with WTF", m_lastHost); + if(audio_info) audio_info(chbuf); + return false; + break; + } + } // wait until the connection is established + return true; + } + } + + const uint32_t TIMEOUT_MS_SSL{3700}; + + if(m_f_ssl == true) { + uint32_t t = millis(); + if(clientsecure.connect(hostwoext, port, TIMEOUT_MS_SSL)) { +// clientsecure.setNoDelay(true); + // if(audio_info) audio_info("SSL/TLS Connected to server"); + clientsecure.print(resp); + uint32_t dt = millis() - t; + sprintf(chbuf, "SSL has been established in %u ms, free Heap: %u bytes", dt, ESP.getFreeHeap()); + if(audio_info) audio_info(chbuf); + + strcpy(m_lastHost, l_host); + m_f_running = true; + if(hostwoext) {free(hostwoext); hostwoext = NULL;} + if(extension) {free(extension); extension = NULL;} + if(l_host ) {free(l_host); l_host = NULL;} + //while(!clientsecure.connected()){;} // wait until the connection is established + wtf = millis(); + while(!clientsecure.connected()){ + /*wtf++; + if(wtf>1000){ + wtf=0; + sprintf(chbuf, "Request %s failed! with WTF", m_lastHost); + if(audio_info) audio_info(chbuf); + return false; + break; + }*/ + if(millis()-wtf>TIMEOUT_MS_SSL){ + sprintf(chbuf, "Request %s failed! with WTF", m_lastHost); + if(audio_info) audio_info(chbuf); + return false; + break; + } + } + return true; + } + } + sprintf(chbuf, "Request %s failed!", l_host); + if(audio_info) audio_info(chbuf); + if(audio_showstation) audio_showstation(""); + if(audio_showstreamtitle) audio_showstreamtitle(""); + if(audio_icydescription) audio_icydescription(""); + if(audio_icyurl) audio_icyurl(""); + m_lastHost[0] = 0; + if(hostwoext) {free(hostwoext); hostwoext = NULL;} + if(extension) {free(extension); extension = NULL;} + if(l_host ) {free(l_host); l_host = NULL;} + return false; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::setFileLoop(bool input){ + m_f_loop = input; + return input; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::UTF8toASCII(char* str){ + +#ifdef SDFATFS_USED + //UTF8->UTF16 (lowbyte) + const uint8_t ascii[60] = { + //129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148 // UTF8(C3) + // Ä Å Æ Ç É Ñ // CHAR + 000, 000, 000, 0xC4, 143, 0xC6,0xC7, 000,0xC9,000, 000, 000, 000, 000, 000, 000, 0xD1, 000, 000, 000, // ASCII (Latin1) + //149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168 + // Ö Ü ß à ä å æ è + 000, 0xD6,000, 000, 000, 000, 000, 0xDC, 000, 000, 0xDF,0xE0, 000, 000, 000,0xE4,0xE5,0xE6, 000,0xE8, + //169, 170, 171, 172. 173. 174. 175, 176, 177, 179, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188 + // ê ë ì î ï ñ ò ô ö ù û ü + 000, 0xEA, 0xEB,0xEC, 000,0xEE,0xEB, 000,0xF1,0xF2, 000,0xF4, 000,0xF6, 000, 000,0xF9, 000,0xFB,0xFC}; +#else + const uint8_t ascii[60] = { + //129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148 // UTF8(C3) + // Ä Å Æ Ç É Ñ // CHAR + 000, 000, 000, 142, 143, 146, 128, 000, 144, 000, 000, 000, 000, 000, 000, 000, 165, 000, 000, 000, // ASCII + //149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168 + // Ö Ü ß à ä å æ è + 000, 153, 000, 000, 000, 000, 000, 154, 000, 000, 225, 133, 000, 000, 000, 132, 134, 145, 000, 138, + //169, 170, 171, 172. 173. 174. 175, 176, 177, 179, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188 + // ê ë ì î ï ñ ò ô ö ù û ü + 000, 136, 137, 141, 000, 140, 139, 000, 164, 149, 000, 147, 000, 148, 000, 000, 151, 000, 150, 129}; +#endif + + uint16_t i = 0, j=0, s = 0; + bool f_C3_seen = false; + + while(str[i] != 0) { // convert UTF8 to ASCII + if(str[i] == 195){ // C3 + i++; + f_C3_seen = true; + continue; + } + str[j] = str[i]; + if(str[j] > 128 && str[j] < 189 && f_C3_seen == true) { + s = ascii[str[j] - 129]; + if(s != 0) str[j] = s; // found a related ASCII sign + f_C3_seen = false; + } + i++; j++; + } + str[j] = 0; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::connecttoSD(const char* path) { + return connecttoFS(SD, path); +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::connecttoFS(fs::FS &fs, const char* path) { + + if(strlen(path)>255) return false; + char audioName[256]; + setDefaults(); // free buffers an set defaults + memcpy(audioName, path, strlen(path)+1); + if(audioName[0] != '/'){ + for(int i = 255; i > 0; i--){ + audioName[i] = audioName[i-1]; + } + audioName[0] = '/'; + } + + sprintf(chbuf, "Reading file: \"%s\"", audioName); + if(audio_info) {vTaskDelay(2); audio_info(chbuf);} + + if(fs.exists(audioName)) { + audiofile = fs.open(audioName); // #86 + } + else { + UTF8toASCII(audioName); + if(fs.exists(audioName)) { + audiofile = fs.open(audioName); + } + } + + if(!audiofile) { + if(audio_info) {vTaskDelay(2); audio_info("Failed to open file for reading");} + return false; + } + + m_f_localfile = true; + m_file_size = audiofile.size();//TEST loop + + char* afn = NULL; // audioFileName + +#ifdef SDFATFS_USED + audiofile.getName(chbuf, sizeof(chbuf)); + afn = strdup(chbuf); +#else + afn = strdup(audiofile.name()); +#endif + + uint8_t dotPos = lastIndexOf(afn, "."); + for(uint8_t i = dotPos + 1; i < strlen(afn); i++){ + afn[i] = toLowerCase(afn[i]); + } + + if(endsWith(afn, ".mp3")){ // MP3 section + free(afn); + m_codec = CODEC_MP3; + if(!MP3Decoder_AllocateBuffers()){audiofile.close(); return false;} + InBuff.changeMaxBlockSize(m_frameSizeMP3); + sprintf(chbuf, "MP3Decoder has been initialized, free Heap: %u bytes", ESP.getFreeHeap()); + if(audio_info) audio_info(chbuf); + m_f_running = true; + return true; + } // end MP3 section + + if(endsWith(afn, ".m4a")){ // M4A section, iTunes + free(afn); + m_codec = CODEC_M4A; + if(!AACDecoder_IsInit()){ + if(!AACDecoder_AllocateBuffers()) {m_f_running = false; stopSong(); return false;} + sprintf(chbuf, "AACDecoder has been initialized, free Heap: %u bytes", ESP.getFreeHeap()); + InBuff.changeMaxBlockSize(m_frameSizeAAC); + if(audio_info) audio_info(chbuf); + }; + m_f_running = true; + return true; + } // end M4A section + + if(endsWith(afn, ".aac")){ // AAC section, without FileHeader + free(afn); + m_codec = CODEC_AAC; + if(!AACDecoder_IsInit()){ + if(!AACDecoder_AllocateBuffers()) {m_f_running = false; stopSong(); return false;} + sprintf(chbuf, "AACDecoder has been initialized, free Heap: %u bytes", ESP.getFreeHeap()); + InBuff.changeMaxBlockSize(m_frameSizeAAC); + if(audio_info) audio_info(chbuf); + } + m_f_running = true; + return true; + } // end AAC section + + if(endsWith(afn, ".wav")){ // WAVE section + free(afn); + m_codec = CODEC_WAV; + InBuff.changeMaxBlockSize(m_frameSizeWav); + m_f_running = true; + return true; + } // end WAVE section + + if(endsWith(afn, ".flac")) { // FLAC section + free(afn); + m_codec = CODEC_FLAC; + if(!psramFound()){ + if(audio_info) audio_info("FLAC works only with PSRAM!"); + m_f_running = false; + return false; + } + if(!FLACDecoder_AllocateBuffers()){audiofile.close(); return false;} + InBuff.changeMaxBlockSize(m_frameSizeFLAC); + sprintf(chbuf, "FLACDecoder has been initialized, free Heap: %u bytes", ESP.getFreeHeap()); + if(audio_info) audio_info(chbuf); + m_f_running = true; + return true; + } // end FLAC section + + sprintf(chbuf, "The %s format is not supported", afn + dotPos); + if(audio_info) audio_info(chbuf); + audiofile.close(); + if(afn) free(afn); + return false; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::connecttospeech(const char* speech, const char* lang){ + + setDefaults(); + char host[] = "translate.google.com.vn"; + char path[] = "/translate_tts"; + + uint16_t speechLen = strlen(speech); + uint16_t speechBuffLen = speechLen + 300; + memcpy(m_lastHost, speech, 256); + char* speechBuff = (char*)malloc(speechBuffLen); + if(!speechBuff) {log_e("out of memory"); return false;} + memcpy(speechBuff, speech, speechLen); + speechBuff[speechLen] = '\0'; + urlencode(speechBuff, speechBuffLen); + + char resp[strlen(speechBuff) + 200] = ""; + strcat(resp, "GET "); + strcat(resp, path); + strcat(resp, "?ie=UTF-8&tl="); + strcat(resp, lang); + strcat(resp, "&client=tw-ob&q="); + strcat(resp, speechBuff); + strcat(resp, " HTTP/1.1\r\n"); + strcat(resp, "Host: "); + strcat(resp, host); + strcat(resp, "\r\n"); + strcat(resp, "User-Agent: Mozilla/5.0 \r\n"); + strcat(resp, "Accept-Encoding: identity\r\n"); + strcat(resp, "Accept: text/html\r\n"); + strcat(resp, "Connection: close\r\n\r\n"); + + free(speechBuff); + + if(!client.connect(host, 80)) { + log_e("Connection failed"); + return false; + } + client.print(resp); + if(audio_info) audio_info(chbuf); + + m_f_webstream = true; + m_f_running = true; + m_f_ssl = false; + m_f_tts = true; + setDatamode(AUDIO_HEADER); + + return true; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::urlencode(char* buff, uint16_t buffLen, bool spacesOnly) { + + uint16_t len = strlen(buff); + uint8_t* tmpbuff = (uint8_t*)malloc(buffLen); + if(!tmpbuff) {log_e("out of memory"); return;} + char c; + char code0; + char code1; + uint16_t j = 0; + for(int i = 0; i < len; i++) { + c = buff[i]; + if(isalnum(c)) tmpbuff[j++] = c; + else if(spacesOnly){ + if(c == ' '){ + tmpbuff[j++] = '%'; + tmpbuff[j++] = '2'; + tmpbuff[j++] = '0'; + } + else{ + tmpbuff[j++] = c; + } + } + else { + code1 = (c & 0xf) + '0'; + if((c & 0xf) > 9) code1 = (c & 0xf) - 10 + 'A'; + c = (c >> 4) & 0xf; + code0 = c + '0'; + if(c > 9) code0 = c - 10 + 'A'; + tmpbuff[j++] = '%'; + tmpbuff[j++] = code0; + tmpbuff[j++] = code1; + } + if(j == buffLen - 1){ + log_e("out of memory"); + break; + } + } + memcpy(buff, tmpbuff, j); + buff[j] ='\0'; + free(tmpbuff); +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::showID3Tag(const char* tag, const char* value){ + + chbuf[0] = 0; + // V2.2 + if(!strcmp(tag, "CNT")) sprintf(chbuf, "Play counter: %s", value); + // if(!strcmp(tag, "COM")) sprintf(chbuf, "Comments: %s", value); + if(!strcmp(tag, "CRA")) sprintf(chbuf, "Audio encryption: %s", value); + if(!strcmp(tag, "CRM")) sprintf(chbuf, "Encrypted meta frame: %s", value); + if(!strcmp(tag, "ETC")) sprintf(chbuf, "Event timing codes: %s", value); + if(!strcmp(tag, "EQU")) sprintf(chbuf, "Equalization: %s", value); + if(!strcmp(tag, "IPL")) sprintf(chbuf, "Involved people list: %s", value); + if(!strcmp(tag, "PIC")) sprintf(chbuf, "Attached picture: %s", value); + if(!strcmp(tag, "SLT")) sprintf(chbuf, "Synchronized lyric/text: %s", value); + // if(!strcmp(tag, "TAL")) sprintf(chbuf, "Album/Movie/Show title: %s", value); + if(!strcmp(tag, "TBP")) sprintf(chbuf, "BPM (Beats Per Minute): %s", value); + if(!strcmp(tag, "TCM")) sprintf(chbuf, "Composer: %s", value); + if(!strcmp(tag, "TCO")) sprintf(chbuf, "Content type: %s", value); + if(!strcmp(tag, "TCR")) sprintf(chbuf, "Copyright message: %s", value); + if(!strcmp(tag, "TDA")) sprintf(chbuf, "Date: %s", value); + if(!strcmp(tag, "TDY")) sprintf(chbuf, "Playlist delay: %s", value); + if(!strcmp(tag, "TEN")) sprintf(chbuf, "Encoded by: %s", value); + if(!strcmp(tag, "TFT")) sprintf(chbuf, "File type: %s", value); + if(!strcmp(tag, "TIM")) sprintf(chbuf, "Time: %s", value); + if(!strcmp(tag, "TKE")) sprintf(chbuf, "Initial key: %s", value); + if(!strcmp(tag, "TLA")) sprintf(chbuf, "Language(s): %s", value); + if(!strcmp(tag, "TLE")) sprintf(chbuf, "Length: %s", value); + if(!strcmp(tag, "TMT")) sprintf(chbuf, "Media type: %s", value); + if(!strcmp(tag, "TOA")) sprintf(chbuf, "Original artist(s)/performer(s): %s", value); + if(!strcmp(tag, "TOF")) sprintf(chbuf, "Original filename: %s", value); + if(!strcmp(tag, "TOL")) sprintf(chbuf, "Original Lyricist(s)/text writer(s): %s", value); + if(!strcmp(tag, "TOR")) sprintf(chbuf, "Original release year: %s", value); + if(!strcmp(tag, "TOT")) sprintf(chbuf, "Original album/Movie/Show title: %s", value); + if(!strcmp(tag, "TP1")) sprintf(chbuf, "Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group: %s", value); + if(!strcmp(tag, "TP2")) sprintf(chbuf, "Band/Orchestra/Accompaniment: %s", value); + if(!strcmp(tag, "TP3")) sprintf(chbuf, "Conductor/Performer refinement: %s", value); + if(!strcmp(tag, "TP4")) sprintf(chbuf, "Interpreted, remixed, or otherwise modified by: %s", value); + if(!strcmp(tag, "TPA")) sprintf(chbuf, "Part of a set: %s", value); + if(!strcmp(tag, "TPB")) sprintf(chbuf, "Publisher: %s", value); + if(!strcmp(tag, "TRC")) sprintf(chbuf, "ISRC (International Standard Recording Code): %s", value); + if(!strcmp(tag, "TRD")) sprintf(chbuf, "Recording dates: %s", value); + if(!strcmp(tag, "TRK")) sprintf(chbuf, "Track number/Position in set: %s", value); + if(!strcmp(tag, "TSI")) sprintf(chbuf, "Size: %s", value); + if(!strcmp(tag, "TSS")) sprintf(chbuf, "Software/hardware and settings used for encoding: %s", value); + if(!strcmp(tag, "TT1")) sprintf(chbuf, "Content group description: %s", value); + if(!strcmp(tag, "TT2")) sprintf(chbuf, "Title/Songname/Content description: %s", value); + if(!strcmp(tag, "TT3")) sprintf(chbuf, "Subtitle/Description refinement: %s", value); + if(!strcmp(tag, "TXT")) sprintf(chbuf, "Lyricist/text writer: %s", value); + if(!strcmp(tag, "TXX")) sprintf(chbuf, "User defined text information frame: %s", value); + if(!strcmp(tag, "TYE")) sprintf(chbuf, "Year: %s", value); + if(!strcmp(tag, "UFI")) sprintf(chbuf, "Unique file identifier: %s", value); + if(!strcmp(tag, "ULT")) sprintf(chbuf, "Unsychronized lyric/text transcription: %s", value); + if(!strcmp(tag, "WAF")) sprintf(chbuf, "Official audio file webpage: %s", value); + if(!strcmp(tag, "WAR")) sprintf(chbuf, "Official artist/performer webpage: %s", value); + if(!strcmp(tag, "WAS")) sprintf(chbuf, "Official audio source webpage: %s", value); + if(!strcmp(tag, "WCM")) sprintf(chbuf, "Commercial information: %s", value); + if(!strcmp(tag, "WCP")) sprintf(chbuf, "Copyright/Legal information: %s", value); + if(!strcmp(tag, "WPB")) sprintf(chbuf, "Publishers official webpage: %s", value); + if(!strcmp(tag, "WXX")) sprintf(chbuf, "User defined URL link frame: %s", value); + + // V2.3 V2.4 tags + // if(!strcmp(tag, "COMM")) sprintf(chbuf, "Comment: %s", value); + if(!strcmp(tag, "OWNE")) sprintf(chbuf, "Ownership: %s", value); + // if(!strcmp(tag, "PRIV")) sprintf(chbuf, "Private: %s", value); + if(!strcmp(tag, "SYLT")) sprintf(chbuf, "SynLyrics: %s", value); + if(!strcmp(tag, "TALB")) sprintf(chbuf, "Album: %s", value); + if(!strcmp(tag, "TBPM")) sprintf(chbuf, "BeatsPerMinute: %s", value); + if(!strcmp(tag, "TCMP")) sprintf(chbuf, "Compilation: %s", value); + if(!strcmp(tag, "TCOM")) sprintf(chbuf, "Composer: %s", value); + if(!strcmp(tag, "TCON")) sprintf(chbuf, "ContentType: %s", value); + if(!strcmp(tag, "TCOP")) sprintf(chbuf, "Copyright: %s", value); + if(!strcmp(tag, "TDAT")) sprintf(chbuf, "Date: %s", value); + if(!strcmp(tag, "TEXT")) sprintf(chbuf, "Lyricist: %s", value); + if(!strcmp(tag, "TIME")) sprintf(chbuf, "Time: %s", value); + if(!strcmp(tag, "TIT1")) sprintf(chbuf, "Grouping: %s", value); + if(!strcmp(tag, "TIT2")) sprintf(chbuf, "Title: %s", value); + if(!strcmp(tag, "TIT3")) sprintf(chbuf, "Subtitle: %s", value); + if(!strcmp(tag, "TLAN")) sprintf(chbuf, "Language: %s", value); + if(!strcmp(tag, "TLEN")) sprintf(chbuf, "Length (ms): %s", value); + if(!strcmp(tag, "TMED")) sprintf(chbuf, "Media: %s", value); + if(!strcmp(tag, "TOAL")) sprintf(chbuf, "OriginalAlbum: %s", value); + if(!strcmp(tag, "TOPE")) sprintf(chbuf, "OriginalArtist: %s", value); + if(!strcmp(tag, "TORY")) sprintf(chbuf, "OriginalReleaseYear: %s", value); + if(!strcmp(tag, "TPE1")) sprintf(chbuf, "Artist: %s", value); + if(!strcmp(tag, "TPE2")) sprintf(chbuf, "Band: %s", value); + if(!strcmp(tag, "TPE3")) sprintf(chbuf, "Conductor: %s", value); + if(!strcmp(tag, "TPE4")) sprintf(chbuf, "InterpretedBy: %s", value); + if(!strcmp(tag, "TPOS")) sprintf(chbuf, "PartOfSet: %s", value); + if(!strcmp(tag, "TPUB")) sprintf(chbuf, "Publisher: %s", value); + if(!strcmp(tag, "TRCK")) sprintf(chbuf, "Track: %s", value); + if(!strcmp(tag, "TSSE")) sprintf(chbuf, "SettingsForEncoding: %s", value); + if(!strcmp(tag, "TRDA")) sprintf(chbuf, "RecordingDates: %s", value); + if(!strcmp(tag, "TXXX")) sprintf(chbuf, "UserDefinedText: %s", value); + if(!strcmp(tag, "TYER")) sprintf(chbuf, "Year: %s", value); + if(!strcmp(tag, "USER")) sprintf(chbuf, "TermsOfUse: %s", value); + if(!strcmp(tag, "USLT")) sprintf(chbuf, "Lyrics: %s", value); + if(!strcmp(tag, "WOAR")) sprintf(chbuf, "OfficialArtistWebpage: %s", value); + if(!strcmp(tag, "XDOR")) sprintf(chbuf, "OriginalReleaseTime: %s", value); + + latinToUTF8(chbuf, sizeof(chbuf)); + if(chbuf[0] != 0) if(audio_id3data) audio_id3data(chbuf); +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::unicode2utf8(char* buff, uint32_t len){ + // converts unicode in UTF-8, buff contains the string to be converted up to len + // range U+1 ... U+FFFF + uint8_t* tmpbuff = (uint8_t*)malloc(len * 2); + if(!tmpbuff) {log_e("out of memory"); return;} + bool bitorder = false; + uint16_t j = 0; + uint16_t k = 0; + uint16_t m = 0; + uint8_t uni_h = 0; + uint8_t uni_l = 0; + + while(m < len - 1) { + if((buff[m] == 0xFE) && (buff[m + 1] == 0xFF)) { + bitorder = true; + j = m + 2; + } // LSB/MSB + if((buff[m] == 0xFF) && (buff[m + 1] == 0xFE)) { + bitorder = false; + j = m + 2; + } // MSB/LSB + m++; + } // seek for last bitorder + m = 0; + if(j > 0) { + for(k = j; k < len; k += 2) { + if(bitorder == true) { + uni_h = (uint8_t)buff[k]; + uni_l = (uint8_t)buff[k + 1]; + } + else { + uni_l = (uint8_t)buff[k]; + uni_h = (uint8_t)buff[k + 1]; + } + + uint16_t uni_hl = ((uni_h << 8) | uni_l); + + if (uni_hl < 0X80){ + tmpbuff[m] = uni_l; + m++; + } + else if (uni_hl < 0X800) { + tmpbuff[m]= ((uni_hl >> 6) | 0XC0); + m++; + tmpbuff[m] =((uni_hl & 0X3F) | 0X80); + m++; + } + else { + tmpbuff[m] = ((uni_hl >> 12) | 0XE0); + m++; + tmpbuff[m] = (((uni_hl >> 6) & 0X3F) | 0X80); + m++; + tmpbuff[m] = ((uni_hl & 0X3F) | 0X80); + m++; + } + } + } + buff[m] = 0; + memcpy(buff, tmpbuff, m); + free(tmpbuff); +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::latinToUTF8(char* buff, size_t bufflen){ + // most stations send strings in UTF-8 but a few sends in latin. To standardize this, all latin strings are + // converted to UTF-8. If UTF-8 is already present, nothing is done and true is returned. + // A conversion to UTF-8 extends the string. Therefore it is necessary to know the buffer size. If the converted + // string does not fit into the buffer, false is returned + // utf8 bytelength: >=0xF0 3 bytes, >=0xE0 2 bytes, >=0xC0 1 byte, e.g. e293ab is ⓫ + + uint16_t pos = 0; + uint8_t ext_bytes = 0; + uint16_t len = strlen(buff); + uint8_t c; + + while(pos < len){ + c = buff[pos]; + if(c >= 0xC2) { // is UTF8 char + pos++; + if(c >= 0xC0 && buff[pos] < 0x80) {ext_bytes++; pos++;} + if(c >= 0xE0 && buff[pos] < 0x80) {ext_bytes++; pos++;} + if(c >= 0xF0 && buff[pos] < 0x80) {ext_bytes++; pos++;} + } + else pos++; + } + if(!ext_bytes) return true; // is UTF-8, do nothing + + pos = 0; + + while(buff[pos] != 0){ + len = strlen(buff); + if(buff[pos] >= 0x80 && buff[pos+1] < 0x80){ // is not UTF8, is latin? + for(int i = len+1; i > pos; i--){ + buff[i+1] = buff[i]; + } + uint8_t c = buff[pos]; + buff[pos++] = 0xc0 | ((c >> 6) & 0x1f); // 2+1+5 bits + buff[pos++] = 0x80 | ((char)c & 0x3f); // 1+1+6 bits + } + pos++; + if(pos > bufflen -3){ + buff[bufflen -1] = '\0'; + return false; // do not overwrite + } + } + return true; +} +//--------------------------------------------------------------------------------------------------------------------- +int Audio::read_WAV_Header(uint8_t* data, size_t len) { + static size_t headerSize; + static uint32_t cs = 0; + static uint8_t bts = 0; + + if(m_controlCounter == 0){ + m_controlCounter ++; + if((*data != 'R') || (*(data + 1) != 'I') || (*(data + 2) != 'F') || (*(data + 3) != 'F')) { + if(audio_info) audio_info("file has no RIFF tag"); + headerSize = 0; + return -1; //false; + } + else{ + headerSize = 4; + return 4; // ok + } + } + + if(m_controlCounter == 1){ + m_controlCounter ++; + cs = (uint32_t) (*data + (*(data + 1) << 8) + (*(data + 2) << 16) + (*(data + 3) << 24) - 8); + headerSize += 4; + return 4; // ok + } + + if(m_controlCounter == 2){ + m_controlCounter ++; + if((*data != 'W') || (*(data + 1) != 'A') || (*(data + 2) != 'V') || (*(data + 3) != 'E')) { + if(audio_info) audio_info("format tag is not WAVE"); + return -1;//false; + } + else { + headerSize += 4; + return 4; + } + } + + if(m_controlCounter == 3){ + if((*data == 'f') && (*(data + 1) == 'm') && (*(data + 2) == 't')) { + m_controlCounter ++; + headerSize += 4; + return 4; + } + else{ + headerSize += 4; + return 4; + } + } + + if(m_controlCounter == 4){ + m_controlCounter ++; + cs = (uint32_t) (*data + (*(data + 1) << 8)); + if(cs > 40) return -1; //false, something going wrong + bts = cs - 16; // bytes to skip if fmt chunk is >16 + headerSize += 4; + return 4; + } + + if(m_controlCounter == 5){ + m_controlCounter ++; + uint16_t fc = (uint16_t) (*(data + 0) + (*(data + 1) << 8)); // Format code + uint16_t nic = (uint16_t) (*(data + 2) + (*(data + 3) << 8)); // Number of interleaved channels + uint32_t sr = (uint32_t) (*(data + 4) + (*(data + 5) << 8) + + (*(data + 6) << 16) + (*(data + 7) << 24)); // Samplerate + uint32_t dr = (uint32_t) (*(data + 8) + (*(data + 9) << 8) + + (*(data + 10) << 16) + (*(data + 11) << 24)); // Datarate + uint16_t dbs = (uint16_t) (*(data + 12) + (*(data + 13) << 8)); // Data block size + uint16_t bps = (uint16_t) (*(data + 14) + (*(data + 15) << 8)); // Bits per sample + if(audio_info) { + sprintf(chbuf, "FormatCode: %u", fc); audio_info(chbuf); + // sprintf(chbuf, "Channel: %u", nic); audio_info(chbuf); + // sprintf(chbuf, "SampleRate: %u", sr); audio_info(chbuf); + sprintf(chbuf, "DataRate: %u", dr); audio_info(chbuf); + sprintf(chbuf, "DataBlockSize: %u", dbs); audio_info(chbuf); + // sprintf(chbuf, "BitsPerSample: %u", bps); audio_info(chbuf); + } + if((bps != 8) && (bps != 16)){ + sprintf(chbuf, "BitsPerSample is %u, must be 8 or 16" , bps); audio_info(chbuf); + stopSong(); + return -1; + } + if((nic != 1) && (nic != 2)){ + sprintf(chbuf, "num channels is %u, must be 1 or 2" , nic); audio_info(chbuf); + stopSong(); + return -1; + } + if(fc != 1) { + if(audio_info) audio_info("format code is not 1 (PCM)"); + stopSong(); + return -1 ; //false; + } + setBitsPerSample(bps); + setChannels(nic); + setSampleRate(sr); + setBitrate(nic * sr * bps); + // sprintf(chbuf, "BitRate: %u", m_bitRate); + // if(audio_info) audio_info(chbuf); + headerSize += 16; + return 16; // ok + } + + if(m_controlCounter == 6){ + m_controlCounter ++; + headerSize += bts; + return bts; // skip to data + } + + if(m_controlCounter == 7){ + if((*(data + 0) == 'd') && (*(data + 1) == 'a') && (*(data + 2) == 't') && (*(data + 3) == 'a')){ + m_controlCounter ++; + vTaskDelay(30); + headerSize += 4; + return 4; + } + else{ + headerSize ++; + return 1; + } + } + + if(m_controlCounter == 8){ + m_controlCounter ++; + size_t cs = *(data + 0) + (*(data + 1) << 8) + (*(data + 2) << 16) + (*(data + 3) << 24); //read chunkSize + headerSize += 4; + if(m_f_localfile) m_contentlength = getFileSize(); + if(cs){ + m_audioDataSize = cs - 44; + } + else { // sometimes there is nothing here + if(m_f_localfile) m_audioDataSize = getFileSize() - headerSize; + if(m_f_webfile) m_audioDataSize = m_contentlength - headerSize; + } + sprintf(chbuf, "Audio-Length: %u", m_audioDataSize); + if(audio_info) audio_info(chbuf); + return 4; + } + m_controlCounter = 100; // header succesfully read + m_audioDataStart = headerSize; + return 0; +} +//--------------------------------------------------------------------------------------------------------------------- +int Audio::read_FLAC_Header(uint8_t *data, size_t len) { + static size_t headerSize; + static size_t retvalue; + static bool f_lastMetaBlock; + + if(retvalue) { + if(retvalue > len) { // if returnvalue > bufferfillsize + if(len > InBuff.getMaxBlockSize()) len = InBuff.getMaxBlockSize(); + retvalue -= len; // and wait for more bufferdata + return len; + } + else { + size_t tmp = retvalue; + retvalue = 0; + return tmp; + } + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == FLAC_BEGIN) { // init + headerSize = 0; + retvalue = 0; + m_audioDataStart = 0; + f_lastMetaBlock = false; + m_controlCounter = FLAC_MAGIC; + if(m_f_localfile){ + m_contentlength = getFileSize(); + sprintf(chbuf, "Content-Length: %u", m_contentlength); + if(audio_info) audio_info(chbuf); + } + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == FLAC_MAGIC) { /* check MAGIC STRING */ + if(specialIndexOf(data, "fLaC", 10) != 0) { + log_e("Magic String 'fLaC' not found in header"); + stopSong(); + return -1; + } +// log_i("Magig String found"); + m_controlCounter = FLAC_MBH; + headerSize = 4; + retvalue = 4; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == FLAC_MBH) { /* METADATA_BLOCK_HEADER */ + uint8_t blockType = *data; + if(!f_lastMetaBlock){ + if(blockType & 128) {f_lastMetaBlock = true;} + blockType &= 127; + if(blockType == 0) m_controlCounter = FLAC_SINFO; + if(blockType == 1) m_controlCounter = FLAC_PADDING; + if(blockType == 2) m_controlCounter = FLAC_APP; + if(blockType == 3) m_controlCounter = FLAC_SEEK; + if(blockType == 4) m_controlCounter = FLAC_VORBIS; + if(blockType == 5) m_controlCounter = FLAC_CUESHEET; + if(blockType == 6) m_controlCounter = FLAC_PICTURE; + headerSize += 1; + retvalue = 1; + return 0; + } + m_controlCounter = FLAC_OKAY; + m_audioDataStart = headerSize; + m_audioDataSize = m_contentlength - m_audioDataStart; + sprintf(chbuf, "Audio-Length: %u", m_audioDataSize); + if(audio_info) audio_info(chbuf); + retvalue = 0; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == FLAC_SINFO) { /* Stream info block */ + size_t l = bigEndian(data, 3); + vTaskDelay(2); + m_flacMaxBlockSize = bigEndian(data + 5, 2); + sprintf(chbuf, "FLAC maxBlockSize: %u", m_flacMaxBlockSize); if(audio_info) audio_info(chbuf); + vTaskDelay(2); + m_flacMaxFrameSize = bigEndian(data + 10, 3); + if(m_flacMaxFrameSize){ + sprintf(chbuf, "FLAC maxFrameSize: %u", m_flacMaxFrameSize); if(audio_info) audio_info(chbuf); + } + else { + if(audio_info) audio_info("FLAC maxFrameSize: N/A"); + } + if(m_flacMaxFrameSize > InBuff.getMaxBlockSize()) { + log_e("FLAC maxFrameSize too large!"); + stopSong(); + return -1; + } +// InBuff.changeMaxBlockSize(m_flacMaxFrameSize); + vTaskDelay(2); + uint32_t nextval = bigEndian(data + 13, 3); + m_flacSampleRate = nextval >> 4; + sprintf(chbuf, "FLAC sampleRate: %u", m_flacSampleRate); if(audio_info) audio_info(chbuf); + vTaskDelay(2); + m_flacNumChannels = ((nextval & 0x06) >> 1) + 1; + sprintf(chbuf, "FLAC numChannels: %u", m_flacNumChannels); if(audio_info) audio_info(chbuf); + vTaskDelay(2); + uint8_t bps = (nextval & 0x01) << 4; + bps += (*(data +16) >> 4) + 1; + m_flacBitsPerSample = bps; + if((bps != 8) && (bps != 16)){ + log_e("bits per sample must be 8 or 16, is %i", bps); + stopSong(); + return -1; + } + sprintf(chbuf, "FLAC bitsPerSample: %u", m_flacBitsPerSample); if(audio_info) audio_info(chbuf); + m_flacTotalSamplesInStream = bigEndian(data + 17, 4); + if(m_flacTotalSamplesInStream){ + sprintf(chbuf, "total samples in stream: %u", m_flacTotalSamplesInStream); if(audio_info) audio_info(chbuf); + } + else{ + if(audio_info) audio_info("total samples in stream: N/A"); + } + if(bps != 0 && m_flacTotalSamplesInStream) { + sprintf(chbuf, "audio file duration: %u seconds", m_flacTotalSamplesInStream / m_flacSampleRate); + if(audio_info) audio_info(chbuf); + } + m_controlCounter = FLAC_MBH; // METADATA_BLOCK_HEADER + retvalue = l + 3; + headerSize += retvalue; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == FLAC_PADDING) { /* PADDING */ + size_t l = bigEndian(data, 3); + m_controlCounter = FLAC_MBH; + retvalue = l + 3; + headerSize += retvalue; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == FLAC_APP) { /* APPLICATION */ + size_t l = bigEndian(data, 3); + m_controlCounter = FLAC_MBH; + retvalue = l + 3; + headerSize += retvalue; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == FLAC_SEEK) { /* SEEKTABLE */ + size_t l = bigEndian(data, 3); + m_controlCounter = FLAC_MBH; + retvalue = l + 3; + headerSize += retvalue; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == FLAC_VORBIS) { /* VORBIS COMMENT */ // field names + const char fn[7][12] = {"TITLE", "VERSION", "ALBUM", "TRACKNUMBER", "ARTIST", "PERFORMER", "GENRE"}; + int offset; + size_t l = bigEndian(data, 3); + + for(int i = 0; i < 7; i++){ + offset = specialIndexOf(data, fn[i], len); + if(offset >= 0){ + sprintf(chbuf, "%s: %s", fn[i], data + offset + strlen(fn[i]) + 1); + chbuf[strlen(chbuf) - 1] = 0; + if(audio_id3data) audio_id3data(chbuf); + } + } + m_controlCounter = FLAC_MBH; + retvalue = l + 3; + headerSize += retvalue; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == FLAC_CUESHEET) { /* CUESHEET */ + size_t l = bigEndian(data, 3); + m_controlCounter = FLAC_MBH; + retvalue = l + 3; + headerSize += retvalue; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == FLAC_PICTURE) { /* PICTURE */ + size_t l = bigEndian(data, 3); + m_controlCounter = FLAC_MBH; + retvalue = l + 3; + headerSize += retvalue; + return 0; + } + return 0; +} +//--------------------------------------------------------------------------------------------------------------------- +int Audio::read_MP3_Header(uint8_t *data, size_t len) { + + static size_t id3Size; + static size_t headerSize; + static uint8_t ID3version; + static int ehsz = 0; + static char tag[5]; + static char frameid[5]; + static size_t framesize = 0; + static bool compressed = false; + static bool APIC_seen = false; + static size_t APIC_size = 0; + static uint32_t APIC_pos = 0; + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 0){ /* read ID3 tag and ID3 header size */ + if(m_f_localfile){ + ID3version = 0; + m_contentlength = getFileSize(); + sprintf(chbuf, "Content-Length: %u", m_contentlength); + if(audio_info) audio_info(chbuf); + } + m_controlCounter ++; + APIC_seen = false; + headerSize = 0; + ehsz = 0; + if(specialIndexOf(data, "ID3", 4) != 0) { // ID3 not found + if(audio_info) audio_info("file has no mp3 tag, skip metadata"); + m_audioDataSize = m_contentlength; + sprintf(chbuf, "Audio-Length: %u", m_audioDataSize); + if(audio_info) audio_info(chbuf); + return -1; // error, no ID3 signature found + } + ID3version = *(data + 3); + switch(ID3version){ + case 2: + m_f_unsync = (*(data + 5) & 0x80); + m_f_exthdr = false; + break; + case 3: + case 4: + m_f_unsync = (*(data + 5) & 0x80); // bit7 + m_f_exthdr = (*(data + 5) & 0x40); // bit6 extended header + break; + }; + id3Size = bigEndian(data + 6, 4, 7); // ID3v2 size 4 * %0xxxxxxx (shift left seven times!!) + id3Size += 10; + + // Every read from now may be unsync'd + sprintf(chbuf, "ID3 framesSize: %i", id3Size); + if(audio_info) audio_info(chbuf); + + sprintf(chbuf, "ID3 version: 2.%i", ID3version); + if(audio_info) audio_info(chbuf); + + if(ID3version == 2){ + m_controlCounter = 10; + } + headerSize = id3Size; + headerSize -= 10; + + return 10; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 1){ // compute extended header size if exists + m_controlCounter ++; + if(m_f_exthdr) { + if(audio_info) audio_info("ID3 extended header"); + ehsz = bigEndian(data, 4); + headerSize -= 4; + ehsz -= 4; + return 4; + } + else{ + if(audio_info) audio_info("ID3 normal frames"); + return 0; + } + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 2){ // skip extended header if exists + if(ehsz > 256) { + ehsz -=256; + headerSize -= 256; + return 256;} // Throw it away + else { + m_controlCounter ++; + headerSize -= ehsz; + return ehsz;} // Throw it away + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 3){ // read a ID3 frame, get the tag + if(headerSize == 0){ + m_controlCounter = 99; + return 0; + } + m_controlCounter ++; + frameid[0] = *(data + 0); + frameid[1] = *(data + 1); + frameid[2] = *(data + 2); + frameid[3] = *(data + 3); + frameid[4] = 0; + for(uint8_t i = 0; i < 4; i++) tag[i] = frameid[i]; // tag = frameid + + headerSize -= 4; + if(frameid[0] == 0 && frameid[1] == 0 && frameid[2] == 0 && frameid[3] == 0) { + // We're in padding + m_controlCounter = 98; // all ID3 metadata processed + } + return 4; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 4){ // get the frame size + m_controlCounter = 6; + + if(ID3version == 4){ + framesize = bigEndian(data, 4, 7); // << 7 + } + else { + framesize = bigEndian(data, 4); // << 8 + } + headerSize -= 4; + uint8_t flag = *(data + 4); // skip 1st flag + (void) flag; + headerSize--; + compressed = (*(data + 5)) & 0x80; // Frame is compressed using [#ZLIB zlib] with 4 bytes for 'decompressed + // size' appended to the frame header. + headerSize--; + uint32_t decompsize = 0; + if(compressed){ + log_i("iscompressed"); + decompsize = bigEndian(data + 6, 4); + headerSize -= 4; + (void) decompsize; + log_i("decompsize=%u", decompsize); + return 6 + 4; + } + return 6; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 5){ // If the frame is larger than 256 bytes, skip the rest + if(framesize > 256){ + framesize -= 256; + headerSize -= 256; + return 256; + } + else { + m_controlCounter = 3; // check next frame + headerSize -= framesize; + return framesize; + } + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 6){ // Read the value + m_controlCounter = 5; // only read 256 bytes + char value[256]; + char ch = *(data + 0); + bool isUnicode = (ch==1) ? true : false; + + if(startsWith(tag, "APIC")) { // a image embedded in file, passing it to external function + //log_i("framesize=%i", framesize); + isUnicode = false; + if(m_f_localfile){ + APIC_seen = true; + APIC_pos = id3Size - headerSize; + APIC_size = framesize; + } + return 0; + } + + size_t fs = framesize; + if(fs >255) fs = 255; + for(int i=0; i 1) { + unicode2utf8(value, fs); // convert unicode to utf-8 U+0020...U+07FF + } + if(!isUnicode){ + uint16_t j = 0, k = 0; + j = 0; + k = 0; + while(j < fs) { + if(value[j] == 0x0A) value[j] = 0x20; // replace LF by space + if(value[j] > 0x1F) { + value[k] = value[j]; + k++; + } + j++; + } //remove non printables + if(k>0) value[k] = 0; else value[0] = 0; // new termination + } + showID3Tag(tag, value); + return fs; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + // -- section V2.2 only , greater Vers above ---- + if(m_controlCounter == 10){ // frames in V2.2, 3bytes identifier, 3bytes size descriptor + frameid[0] = *(data + 0); + frameid[1] = *(data + 1); + frameid[2] = *(data + 2); + frameid[3] = 0; + for(uint8_t i = 0; i < 4; i++) tag[i] = frameid[i]; // tag = frameid + headerSize -= 3; + size_t len = bigEndian(data + 3, 3); + headerSize -= 3; + headerSize -= len; + char value[256]; + size_t tmp = len; + if(tmp > 254) tmp = 254; + memcpy(value, (data + 7), tmp); + value[tmp+1] = 0; + chbuf[0] = 0; + + showID3Tag(tag, value); + if(len == 0) m_controlCounter = 98; + + return 3 + 3 + len; + } + // -- end section V2.2 ----------- + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 98){ // skip all ID3 metadata (mostly spaces) + if(headerSize > 256) { + headerSize -=256; + return 256; + } // Throw it away + else { + m_controlCounter = 99; + return headerSize; + } // Throw it away + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 99){ // exist another ID3tag? + m_audioDataStart += id3Size; + vTaskDelay(30); + if((*(data + 0) == 'I') && (*(data + 1) == 'D') && (*(data + 2) == '3')) { + m_controlCounter = 0; + return 0; + } + else { + m_controlCounter = 100; // ok + m_audioDataSize = m_contentlength - m_audioDataStart; + sprintf(chbuf, "Audio-Length: %u", m_audioDataSize); + if(audio_info) audio_info(chbuf); + if(APIC_seen && audio_id3image){ + size_t pos = audiofile.position(); + audio_id3image(audiofile, APIC_pos, APIC_size); + audiofile.seek(pos); // the filepointer could have been changed by the user, set it back + } + return 0; + } + } + return 0; +} +//--------------------------------------------------------------------------------------------------------------------- +int Audio::read_M4A_Header(uint8_t *data, size_t len) { +/* + ftyp + | - moov -> trak -> ... -> mp4a contains raw block parameters + | L... -> ilst contains artist, composer .... + free (optional) + | + mdat contains the audio data */ + + + static size_t headerSize = 0; + static size_t retvalue = 0; + static size_t atomsize = 0; + static size_t audioDataPos = 0; + + if(retvalue) { + if(retvalue > len) { // if returnvalue > bufferfillsize + if(len > InBuff.getMaxBlockSize()) len = InBuff.getMaxBlockSize(); + retvalue -= len; // and wait for more bufferdata + return len; + } + else { + size_t tmp = retvalue; + retvalue = 0; + return tmp; + } + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == M4A_BEGIN) { // init + headerSize = 0; + retvalue = 0; + atomsize = 0; + audioDataPos = 0; + m_controlCounter = M4A_FTYP; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == M4A_FTYP) { /* check_m4a_file */ + atomsize = bigEndian(data, 4); // length of first atom + if(specialIndexOf(data, "ftyp", 10) != 4) { + log_e("atom 'type' not found in header"); + stopSong(); + return -1; + } + if(specialIndexOf(data, "M4A ", 20) != 8) { + log_e("subtype 'MA4 ' expected, but found '%s '", (data + 8)); + stopSong(); + return -1; + } + m_controlCounter = M4A_CHK; + retvalue = atomsize; + headerSize = atomsize; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == M4A_CHK) { /* check Tag */ + atomsize = bigEndian(data, 4); // length of this atom + if(specialIndexOf(data, "moov", 10) == 4) { + m_controlCounter = M4A_MOOV; + return 0; + } + else if(specialIndexOf(data, "free", 10) == 4) { + retvalue = atomsize; + headerSize += atomsize; + return 0; + } + else if(specialIndexOf(data, "mdat", 10) == 4) { + m_controlCounter = M4A_MDAT; + return 0; + } + else { + char atomName[5]; + (void)atomName; + atomName[0] = *data; + atomName[1] = *(data + 1); + atomName[2] = *(data + 2); + atomName[3] = *(data + 3); + atomName[4] = 0; + + log_i("atom %s found", atomName); + + retvalue = atomsize; + headerSize += atomsize; + return 0; + } + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == M4A_MOOV) { // moov + // we are looking for track and ilst + if(specialIndexOf(data, "trak", len) > 0){ + int offset = specialIndexOf(data, "trak", len); + retvalue = offset; + atomsize -= offset; + headerSize += offset; + m_controlCounter = M4A_TRAK; + return 0; + } + if(specialIndexOf(data, "ilst", len) > 0){ + int offset = specialIndexOf(data, "ilst", len); + retvalue = offset; + atomsize -= offset; + headerSize += offset; + m_controlCounter = M4A_ILST; + return 0; + + } + if (atomsize > len -10){atomsize -= (len -10); headerSize += (len -10); retvalue = (len -10);} + else {m_controlCounter = M4A_CHK; retvalue = atomsize; headerSize += atomsize;} + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == M4A_TRAK) { // trak + if(specialIndexOf(data, "esds", len) > 0){ + int esds = specialIndexOf(data, "esds", len); // Packaging/Encapsulation And Setup Data + uint8_t *pos = data + esds; + uint8_t len_of_OD = *(pos + 12); // length of this OD (which includes the next 2 tags) + (void)len_of_OD; + uint8_t len_of_ESD = *(pos + 20); // length of this Elementary Stream Descriptor + (void)len_of_ESD; + uint8_t audioType = *(pos + 21); + if (audioType == 0x40) sprintf(chbuf, "AudioType: MPEG4 / Audio"); // ObjectTypeIndication + else if(audioType == 0x66) sprintf(chbuf, "AudioType: MPEG2 / Audio"); + else if(audioType == 0x69) sprintf(chbuf, "AudioType: MPEG2 / Audio Part 3"); // Backward Compatible Audio + else if(audioType == 0x6B) sprintf(chbuf, "AudioType: MPEG1 / Audio"); + else sprintf(chbuf, "unknown Audio Type %x", audioType); + if(audio_info) audio_info(chbuf); + + uint8_t streamType = *(pos + 22); + streamType = streamType >> 2; // 6 bits + if(streamType!= 5) { log_e("Streamtype is not audio!"); } + + uint32_t maxBr = bigEndian(pos + 26, 4); // max bitrate + sprintf(chbuf, "max bitrate: %i", maxBr); if(audio_info) audio_info(chbuf); + + uint32_t avrBr = bigEndian(pos + 30, 4); // avg bitrate + sprintf(chbuf, "avr bitrate: %i", avrBr); if(audio_info) audio_info(chbuf); + + uint16_t ASC = bigEndian(pos + 39, 2); + + uint8_t objectType = ASC >> 11; // first 5 bits + if (objectType == 1) sprintf(chbuf, "AudioObjectType: AAC Main"); // Audio Object Types + else if(objectType == 2) sprintf(chbuf, "AudioObjectType: AAC Low Complexity"); + else if(objectType == 3) sprintf(chbuf, "AudioObjectType: AAC Scalable Sample Rate"); + else if(objectType == 4) sprintf(chbuf, "AudioObjectType: AAC Long Term Prediction"); + else if(objectType == 5) sprintf(chbuf, "AudioObjectType: AAC Spectral Band Replication"); + else if(objectType == 6) sprintf(chbuf, "AudioObjectType: AAC Scalable"); + else sprintf(chbuf, "unknown Audio Type %x", audioType); + if(audio_info) audio_info(chbuf); + + const uint32_t samplingFrequencies[13] = { + 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350 + }; + uint8_t sRate = (ASC & 0x0600) >> 7; // next 4 bits Sampling Frequencies + sprintf(chbuf, "Sampling Frequency: %u",samplingFrequencies[sRate]); + if(audio_info) audio_info(chbuf); + + uint8_t chConfig = (ASC & 0x78) >> 3; // next 4 bits + if(chConfig == 0) if(audio_info) audio_info("Channel Configurations: AOT Specifc Config"); + if(chConfig == 1) if(audio_info) audio_info("Channel Configurations: front-center"); + if(chConfig == 2) if(audio_info) audio_info("Channel Configurations: front-left, front-right"); + if(chConfig > 2) { log_e("Channel Configurations with more than 2 channels is not allowed!"); } + + uint8_t frameLengthFlag = (ASC & 0x04); + uint8_t dependsOnCoreCoder = (ASC & 0x02); + (void)dependsOnCoreCoder; + uint8_t extensionFlag = (ASC & 0x01); + (void)extensionFlag; + + if(frameLengthFlag == 0) if(audio_info) audio_info("AAC FrameLength: 1024 bytes"); + if(frameLengthFlag == 1) if(audio_info) audio_info("AAC FrameLength: 960 bytes"); + } + if(specialIndexOf(data, "mp4a", len) > 0){ + int offset = specialIndexOf(data, "mp4a", len); + int channel = bigEndian(data + offset + 20, 2); // audio parameter must be set before starting + int bps = bigEndian(data + offset + 22, 2); // the aac decoder. There are RAW blocks only in m4a + int srate = bigEndian(data + offset + 26, 4); // + setBitsPerSample(bps); + setChannels(channel); + setSampleRate(srate); + setBitrate(bps * channel * srate); + sprintf(chbuf, "ch; %i, bps: %i, sr: %i", channel, bps, srate); + if(audio_info) audio_info(chbuf); + if(audioDataPos && m_f_localfile) { + m_controlCounter = M4A_AMRDY; + setFilePos(audioDataPos); + return 0; + } + } + m_controlCounter = M4A_MOOV; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == M4A_ILST) { // ilst + const char info[12][6] = { "nam\0", "ART\0", "alb\0", "too\0", "cmt\0", "wrt\0", + "tmpo\0", "trkn\0","day\0", "cpil\0", "aART\0", "gen\0"}; + int offset; + for(int i=0; i < 12; i++){ + offset = specialIndexOf(data, info[i], len, true); // seek info[] with '\0' + if(offset>0) { + offset += 19; if(*(data + offset) == 0) offset ++; + char value[256]; + size_t tmp = strlen((const char*)data + offset); + if(tmp > 254) tmp = 254; + memcpy(value, (data + offset), tmp); + value[tmp] = 0; + chbuf[0] = 0; + if(i == 0) sprintf(chbuf, "Title: %s", value); + if(i == 1) sprintf(chbuf, "Artist: %s", value); + if(i == 2) sprintf(chbuf, "Album: %s", value); + if(i == 3) sprintf(chbuf, "Encoder: %s", value); + if(i == 4) sprintf(chbuf, "Comment: %s", value); + if(i == 5) sprintf(chbuf, "Composer: %s", value); + if(i == 6) sprintf(chbuf, "BPM: %s", value); + if(i == 7) sprintf(chbuf, "Track Number: %s", value); + if(i == 8) sprintf(chbuf, "Year: %s", value); + if(i == 9) sprintf(chbuf, "Compile: %s", value); + if(i == 10) sprintf(chbuf, "Album Artist: %s", value); + if(i == 11) sprintf(chbuf, "Types of: %s", value); + if(chbuf[0] != 0) { + if(audio_id3data) audio_id3data(chbuf); + } + } + } + m_controlCounter = M4A_MOOV; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == M4A_MDAT) { // mdat + m_audioDataSize = bigEndian(data, 4); // length of this atom + sprintf(chbuf, "Audio-Length: %u",m_audioDataSize); + if(audio_info) audio_info(chbuf); + retvalue = 8; + headerSize += 8; + m_controlCounter = M4A_AMRDY; // last step before starting the audio + return 0; + } + + if(m_controlCounter == M4A_AMRDY){ // almost ready + m_audioDataStart = headerSize; + m_contentlength = headerSize + m_audioDataSize; // after this mdat atom there may be other atoms + log_i("begin mdat %i", headerSize); + if(m_f_localfile){ + sprintf(chbuf, "Content-Length: %u", m_contentlength); + if(audio_info) audio_info(chbuf); + } + m_controlCounter = M4A_OKAY; // that's all + return 0; + } + // this section should never be reached + log_e("error"); + return 0; +} +//--------------------------------------------------------------------------------------------------------------------- +int Audio::read_OGG_Header(uint8_t *data, size_t len){ + static size_t retvalue = 0; + static size_t pageLen = 0; + static bool f_firstPacket = false; + + if(retvalue) { + if(retvalue > len) { // if returnvalue > bufferfillsize + if(len > InBuff.getMaxBlockSize()) len = InBuff.getMaxBlockSize(); + retvalue -= len; // and wait for more bufferdata + return len; + } + else { + size_t tmp = retvalue; + retvalue = 0; + return tmp; + } + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == OGG_BEGIN) { // init + retvalue = 0; + m_audioDataStart = 0; + f_firstPacket = true; + m_controlCounter = OGG_MAGIC; + if(m_f_localfile){ + m_contentlength = getFileSize(); + sprintf(chbuf, "Content-Length: %u", m_contentlength); + if(audio_info) audio_info(chbuf); + } + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == OGG_MAGIC) { /* check MAGIC STRING */ + if(specialIndexOf(data, "OggS", 10) != 0) { + log_e("Magic String 'OggS' not found in header"); + stopSong(); + return -1; + } + m_controlCounter = OGG_HEADER; + retvalue = 4; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == OGG_HEADER) { /* check OGG PAGE HEADER */ + uint8_t i = 0; + uint8_t ssv = *(data + i); // stream_structure_version + (void)ssv; + i++; + uint8_t htf = *(data + i); // header_type_flag + (void)htf; + i++; + uint32_t tmp = bigEndian(data + i, 4); // absolute granule position + uint64_t agp = (uint64_t) tmp << 32; + i += 4; + agp += bigEndian(data + i, 4); + i += 4; + uint32_t ssnr = bigEndian(data + i, 4); // stream serial number + (void)ssnr; + i += 4; + uint32_t psnr = bigEndian(data + i, 4); // page sequence no + (void)psnr; + i += 4; + uint32_t pchk = bigEndian(data + i, 4); // page checksum + (void)pchk; + i += 4; + uint8_t psegm = *(data + i); + i++; + uint8_t psegmBuff[256]; + pageLen = 0; + for(uint8_t j = 0; j < psegm; j++){ + psegmBuff[j] = *(data + i); + pageLen += psegmBuff[j]; + i++; + } + retvalue = i; + if(agp == 0){ + if(f_firstPacket == true){ + f_firstPacket = false; + m_controlCounter = OGG_FIRST; // ogg first pages + } + else{ + retvalue += pageLen; + m_controlCounter = OGG_MAGIC; + } + } + else{ + if(m_codec == CODEC_OGG_FLAC){ + m_controlCounter = OGG_AMRDY; + } + else { + if(audio_info) audio_info("unknown format"); + stopSong(); + return -1; + } + } + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == OGG_FIRST) { /* check OGG FIRST PAGES (has no streaming content) */ + uint8_t i = 0; + uint8_t obp = *(data + i); // oneBytePacket shold be 0x7F + (void)obp; + i++; + if(specialIndexOf(data + i, "FLAC", 10) == 0){ + } + else{ + log_i("ogg/flac support only"); // ogg/vorbis or ogg//opus not supported yet + stopSong(); + return -1; + } + i += 4; + uint8_t major_vers = *(data + i); + (void)major_vers; + i++; + uint8_t minor_vers = *(data + i); + (void)minor_vers; + i++; + uint16_t nonah = bigEndian(data + i, 2); // number of non audio headers (0x00 = unknown) + (void)nonah; + i += 2; + if(specialIndexOf(data + i, "fLaC", 10) == 0){ + m_codec = CODEC_OGG_FLAC; + } + i += 4; + // STREAMINFO metadata block begins + uint32_t mblen = bigEndian(data + i, 4); + (void)mblen; + i += 4; // skip metadata block header + length + i += 2; // skip minimun block size + m_flacMaxBlockSize = bigEndian(data + i, 2); + i += 2; + vTaskDelay(2); + sprintf(chbuf, "FLAC maxBlockSize: %u", m_flacMaxBlockSize); if(audio_info) audio_info(chbuf); + i += 3; // skip minimun frame size + vTaskDelay(2); + m_flacMaxFrameSize = bigEndian(data + i, 3); + i += 3; + if(m_flacMaxFrameSize){ + sprintf(chbuf, "FLAC maxFrameSize: %u", m_flacMaxFrameSize); if(audio_info) audio_info(chbuf); + } + else { + if(audio_info) audio_info("FLAC maxFrameSize: N/A"); + } + if(m_flacMaxFrameSize > InBuff.getMaxBlockSize()) { + log_e("FLAC maxFrameSize too large!"); + stopSong(); + return -1; + } + vTaskDelay(2); + uint32_t nextval = bigEndian(data + i, 3); + i += 3; + m_flacSampleRate = nextval >> 4; + sprintf(chbuf, "FLAC sampleRate: %u", m_flacSampleRate); if(audio_info) audio_info(chbuf); + vTaskDelay(2); + m_flacNumChannels = ((nextval & 0x06) >> 1) + 1; + sprintf(chbuf, "FLAC numChannels: %u", m_flacNumChannels); if(audio_info) audio_info(chbuf); + if(m_flacNumChannels != 1 && m_flacNumChannels != 2){ + vTaskDelay(2); + if(audio_info) audio_info("numChannels must be 1 or 2"); + stopSong(); + return -1; + } + vTaskDelay(2); + uint8_t bps = (nextval & 0x01) << 4; + bps += (*(data +i) >> 4) + 1; + i++; + m_flacBitsPerSample = bps; + if((bps != 8) && (bps != 16)){ + log_e("bits per sample must be 8 or 16, is %i", bps); + stopSong(); + return -1; + } + sprintf(chbuf, "FLAC bitsPerSample: %u", m_flacBitsPerSample); if(audio_info) audio_info(chbuf); + m_flacTotalSamplesInStream = bigEndian(data + i, 4); + i++; + if(m_flacTotalSamplesInStream) { + sprintf(chbuf, "total samples in stream: %u", m_flacTotalSamplesInStream); if(audio_info) audio_info(chbuf); + } + else { + if(audio_info) audio_info("total samples in stream: N/A"); + } + if(bps != 0 && m_flacTotalSamplesInStream) { + sprintf(chbuf, "audio file duration: %u seconds", m_flacTotalSamplesInStream / m_flacSampleRate); + if(audio_info) audio_info(chbuf); + } + m_controlCounter = OGG_MAGIC; + retvalue = pageLen; + return 0; + } + if(m_controlCounter == OGG_AMRDY){ // ogg almost ready + if(!psramFound()){ + if(audio_info) audio_info("FLAC works only with PSRAM!"); + m_f_running = false; stopSong(); + return -1; + } + if(!FLACDecoder_AllocateBuffers()) {m_f_running = false; stopSong(); return -1;} + InBuff.changeMaxBlockSize(m_frameSizeFLAC); + sprintf(chbuf, "FLACDecoder has been initialized, free Heap: %u bytes", ESP.getFreeHeap()); + + if(audio_info) audio_info(chbuf); + m_controlCounter = OGG_OKAY; // 100 + retvalue = 0; + return 0; + } + return 0; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::stopSong() { + if(m_f_running) { + m_f_running = false; + audiofile.close(); + } + memset(m_outBuff, 0, sizeof(m_outBuff)); //Clear OutputBuffer + i2s_zero_dma_buffer((i2s_port_t) m_i2s_num); +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::playI2Sremains() { // returns true if all dma_buffs flushed + static uint8_t dma_buf_count = 0; + // there is no function to see if dma_buff is empty. So fill the dma completely. + // As soon as all remains played this function returned. Or you can take this to create a short silence. + if(!getSampleRate()) setSampleRate(96000); + if(!getChannels()) setChannels(2); + if(getBitsPerSample() > 8) memset(m_outBuff, 0, sizeof(m_outBuff)); //Clear OutputBuffer (signed) + else memset(m_outBuff, 128, sizeof(m_outBuff)); //Clear OutputBuffer (unsigned, PCM 8u) + //play remains and then flush dmaBuff + m_validSamples = m_i2s_config.dma_buf_len; + while(m_validSamples) { + playChunk(); + } + if(dma_buf_count < m_i2s_config.dma_buf_count){ + dma_buf_count++; + return false; + } + dma_buf_count = 0; + return true; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::pauseResume() { + bool retVal = false; + if(m_f_localfile || m_f_webstream) { + m_f_running = !m_f_running; + retVal = true; + if(!m_f_running) { + memset(m_outBuff, 0, sizeof(m_outBuff)); //Clear OutputBuffer + i2s_zero_dma_buffer((i2s_port_t) m_i2s_num); + } + } + return retVal; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::playChunk() { + // If we've got data, try and pump it out.. + int16_t sample[2]; + if(getBitsPerSample() == 8) { + if(getChannels() == 1) { + while(m_validSamples) { + uint8_t x = m_outBuff[m_curSample] & 0x00FF; + uint8_t y = (m_outBuff[m_curSample] & 0xFF00) >> 8; + sample[LEFTCHANNEL] = x; + sample[RIGHTCHANNEL] = x; + while(1) { + if(playSample(sample)) break; + } // Can't send? + sample[LEFTCHANNEL] = y; + sample[RIGHTCHANNEL] = y; + while(1) { + if(playSample(sample)) break; + } // Can't send? + m_validSamples--; + m_curSample++; + } + } + if(getChannels() == 2) { + while(m_validSamples) { + uint8_t x = m_outBuff[m_curSample] & 0x00FF; + uint8_t y = (m_outBuff[m_curSample] & 0xFF00) >> 8; + if(!m_f_forceMono) { // stereo mode + sample[LEFTCHANNEL] = x; + sample[RIGHTCHANNEL] = y; + } + else { // force mono + uint8_t xy = (x + y) / 2; + sample[LEFTCHANNEL] = xy; + sample[RIGHTCHANNEL] = xy; + } + + while(1) { + if(playSample(sample)) break; + } // Can't send? + m_validSamples--; + m_curSample++; + } + } + m_curSample = 0; + return true; + } + if(getBitsPerSample() == 16) { + if(getChannels() == 1) { + while(m_validSamples) { + sample[LEFTCHANNEL] = m_outBuff[m_curSample]; + sample[RIGHTCHANNEL] = m_outBuff[m_curSample]; + if(!playSample(sample)) { + return false; + } // Can't send + m_validSamples--; + m_curSample++; + } + } + if(getChannels() == 2) { + while(m_validSamples) { + if(!m_f_forceMono) { // stereo mode + sample[LEFTCHANNEL] = m_outBuff[m_curSample * 2]; + sample[RIGHTCHANNEL] = m_outBuff[m_curSample * 2 + 1]; + } + else { // mono mode, #100 + int16_t xy = (m_outBuff[m_curSample * 2] + m_outBuff[m_curSample * 2 + 1]) / 2; + sample[LEFTCHANNEL] = xy; + sample[RIGHTCHANNEL] = xy; + } + if(!playSample(sample)) { + return false; + } // Can't send + m_validSamples--; + m_curSample++; + } + } + m_curSample = 0; + return true; + } + log_e("BitsPer Sample must be 8 or 16!"); + return false; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::loop() { + + // - localfile - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_f_localfile) { // Playing file fron SPIFFS or SD? + processLocalFile(); + } + // - webstream - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_f_webstream) { // Playing file from URL? + if(!m_f_running) return; + +// if(m_f_m3u8wait){ // timer, wait for fresh m3u8 playlist +// if(m_t1 < millis()){ +// log_i("wait zurückgesetzt"); +// m_f_m3u8wait = false; +// processPlayListData(); +// } +// } + if(m_datamode == AUDIO_PLAYLISTINIT || m_datamode == AUDIO_PLAYLISTHEADER || m_datamode == AUDIO_PLAYLISTDATA){ + processPlayListData(); + if(m_f_m3u8data) processWebStream(); + return; + } + if(m_datamode == AUDIO_HEADER){ + processAudioHeaderData(); + if(m_f_m3u8data) processWebStream(); + return; + } + if(m_datamode == AUDIO_DATA){ + processWebStream(); + return; + } + } + return; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::processPlayListData() { + + static bool f_entry = false; // entryflag for asx playlist + static bool f_title = false; // titleflag for asx playlist + static bool f_ref = false; // refflag for asx playlist + static bool f_begin = false; + static bool f_end = false; + static bool f_ct = false; + + (void)f_title; // is unused yet + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_datamode == AUDIO_PLAYLISTINIT) { // Initialize for receive .m3u file + // We are going to use metadata to read the lines from the .m3u file + // Sometimes this will only contain a single line + f_entry = false; + f_title = false; + f_ref = false; + f_begin = false; + f_end = false; + f_ct = false; + + m_datamode = AUDIO_PLAYLISTHEADER; // Handle playlist data + //if(audio_info) audio_info("Read from playlist"); + } // end AUDIO_PLAYLISTINIT + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + int av = 0; + if(!m_f_ssl) av = client.available(); + else av = clientsecure.available(); + if(av < 1){ + if(f_end) return; + if(f_begin) {f_end = true;} + else return; + } + + char pl[256]; // playlistline + uint8_t b = 0; + int16_t pos = 0; + + while(true){ + if(!m_f_ssl) b = client.read(); + else b = clientsecure.read(); + if(b == 0xff) b = '\n'; // no more to read? send new line + if(b == '\n') {pl[pos] = 0; break;} + if(b < 0x20 || b > 0x7E) continue; + pl[pos] = b; + if(pos < 255) pos++; + if(pos == 254){pl[pos] = '\0'; /*log_e("playlistline overflow");*/} + } + + if(strlen(pl) == 0 && m_datamode == AUDIO_PLAYLISTHEADER) { + if(m_f_Log) if(audio_info) audio_info("Switch to PLAYLISTDATA"); + m_datamode = AUDIO_PLAYLISTDATA; // Expecting data now + return; + } + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_datamode == AUDIO_PLAYLISTHEADER) { // Read header + + sprintf(chbuf, "Playlistheader: %s", pl); // Show playlistheader + if(m_f_Log) if(audio_info) audio_info(chbuf); + + if(indexOf(pl, "Content-Type:", 0)){ + f_ct = true; // found ContentType in pl + } + + if(indexOf(pl, "content-type:", 0)){ + f_ct = true; // found ContentType in pl + } + + if((indexOf(pl, "Connection:close", 0) >= 0) && !f_ct){ // #193 is not a playlist if no ct found + m_datamode = AUDIO_HEADER; + } + + int pos = indexOf(pl, "400 Bad Request", 0); + if(pos >= 0) { + m_datamode = AUDIO_NONE; + if(audio_info) audio_info("Error 400 Bad Request"); + stopSong(); + return; + } + + pos = indexOf(pl, "404 Not Found", 0); + if(pos >= 0) { + m_datamode = AUDIO_NONE; + if(audio_info) audio_info("Error 404 Not Found"); + stopSong(); + return; + } + + pos = indexOf(pl, "404 File Not Found", 0); + if(pos >= 0) { + m_datamode = AUDIO_NONE; + if(audio_info) audio_info("Error 404 File Not Found"); + stopSong(); + return; + } + + pos = indexOf(pl, "HTTP/1.0 404", 0); + if(pos >= 0) { + m_datamode = AUDIO_NONE; + if(audio_info) audio_info("Error 404 Not Available"); + stopSong(); + return; + } + + pos = indexOf(pl, "HTTP/1.1 401", 0); + if(pos >= 0) { + m_datamode = AUDIO_NONE; + if(audio_info) audio_info("Error 401 Unauthorized"); + stopSong(); + return; + } + + pos = indexOf(pl, "HTTP/1.1 403", 0); + if(pos >= 0) { + m_datamode = AUDIO_NONE; + if(audio_info) audio_info("Error 403 Forbidden"); + stopSong(); + return; + } + + pos = indexOf(pl, ":", 0); // lowercase all letters up to the colon + if(pos >= 0) { + for(int i=0; i < pos; i++) { + pl[i] = toLowerCase(pl[i]); + } + } + + if(startsWith(pl, "icy-")){ // icy-data in playlist? that can not be + m_datamode = AUDIO_HEADER; + if(audio_info) audio_info("playlist is not valid, switch to AUDIO_HEADER"); + return; + } + + + if(startsWith(pl, "location:") || startsWith(pl, "Location:")) { + char* host; + pos = indexOf(pl, "http", 0); + host = (pl + pos); +// sprintf(chbuf, "redirect to new host %s", host); +// if(m_f_Log) if(audio_info) audio_info(chbuf); + pos = indexOf(pl, "/", 10); + if(strncmp(host, m_lastHost, pos) == 0){ // same host? + if(!m_f_ssl) {client.stop(); client.flush();} + else {clientsecure.stop(); clientsecure.flush();} + httpPrint(host); + } + else connecttohost(host); // different host, + } + return; + } // end AUDIO_PLAYLISTHEADER + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_datamode == AUDIO_PLAYLISTDATA) { // Read next byte of .m3u file data + sprintf(chbuf, "Playlistdata: %s", pl); // Show playlistdata + if(m_f_Log) if(audio_info) audio_info(chbuf); + + pos = indexOf(pl, "= 0) { + m_datamode = AUDIO_NONE; + if(audio_info) audio_info("Not Found"); + stopSong(); + return; + } + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_playlistFormat == FORMAT_M3U) { + + if(!f_begin) f_begin = true; // first playlistdata received + if(indexOf(pl, "#EXTINF:", 0) >= 0) { // Info? + pos = indexOf(pl, ",", 0); // Comma in this line? + if(pos > 0) { + // Show artist and title if present in metadata + if(audio_info) audio_info(pl + pos + 1); + } + return; + } + if(startsWith(pl, "#")) { // Commentline? + return; + } + + pos = indexOf(pl, "http://:@", 0); // ":@"?? remove that! + if(pos >= 0) { + sprintf(chbuf, "Entry in playlist found: %s", (pl + pos + 9)); + if(audio_info) audio_info(chbuf); + connecttohost(pl + pos + 9); + return; + } + //sprintf(chbuf, "Entry in playlist found: %s", pl); + //if(audio_info) audio_info(chbuf); + pos = indexOf(pl, "http", 0); // Search for "http" + const char* host; + if(pos >= 0) { // Does URL contain "http://"? + host = (pl + pos); + connecttohost(host); + } // Yes, set new host + return; + } //m3u + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_playlistFormat == FORMAT_PLS) { + + if(!f_begin){ + if(strlen(pl) == 0) return; // empty line + if(strcmp(pl, "[playlist]") == 0){ // first entry in valid pls + f_begin = true; // we have first playlistdata received + return; + } + else{ + m_datamode = AUDIO_HEADER; // pls is not valid + if(audio_info) audio_info("pls is not valid, switch to AUDIO_HEADER"); + return; + } + } + + if(startsWith(pl, "File1")) { + pos = indexOf(pl, "http", 0); // File1=http://streamplus30.leonex.de:14840/; + if(pos >= 0) { // yes, URL contains "http"? + memcpy(m_lastHost, pl + pos, strlen(pl) + 1); // http://streamplus30.leonex.de:14840/; + // Now we have an URL for a stream in host. + f_ref = true; + } + } + if(startsWith(pl, "Title1")) { // Title1=Antenne Tirol + const char* plsStationName = (pl + 7); + if(audio_showstation) audio_showstation(plsStationName); + sprintf(chbuf, "StationName: \"%s\"", plsStationName); + if(audio_info) audio_info(chbuf); + f_title = true; + } + if(startsWith(pl, "Length1")) f_title = true; // if no Title is available + if((f_ref == true) && (strlen(pl) == 0)) f_title = true; + + if(indexOf(pl, "Invalid username", 0) >= 0){ // Unable to access account: Invalid username or password + m_f_running = false; + stopSong(); + m_datamode = AUDIO_NONE; + return; + } + + if(f_end) { // we have both StationName and StationURL + log_d("connect to new host %s", m_lastHost); + connecttohost(m_lastHost); // Connect to it + } + return; + } // pls + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_playlistFormat == FORMAT_ASX) { // Advanced Stream Redirector + if(!f_begin) f_begin = true; // first playlistdata received + int p1 = indexOf(pl, "<", 0); + int p2 = indexOf(pl, ">", 1); + if(p1 >= 0 && p2 > p1){ // #196 set all between "< ...> to lowercase + for(uint8_t i = p1; i < p2; i++){ + pl[i] = toLowerCase(pl[i]); + } + } + + if(indexOf(pl, "", 0) >= 0) f_entry = true; // found entry tag (returns -1 if not found) + + if(f_entry) { + if(indexOf(pl, "ref href", 0) > 0) { // + pos = indexOf(pl, "http", 0); + if(pos > 0) { + char* plsURL = (pl + pos); // http://87.98.217.63:24112/stream" /> + int pos1 = indexOf(plsURL, "\"", 0); // http://87.98.217.63:24112/stream + if(pos1 > 0) { + plsURL[pos1] = '\0'; + } + memcpy(m_lastHost, plsURL, strlen(plsURL) + 1); // save url in array + log_d("m_lastHost = %s",m_lastHost); + // Now we have an URL for a stream in host. + f_ref = true; + } + } + pos = indexOf(pl, "", 0); + if(pos < 0) pos = indexOf(pl, "<Title>", 0); + if(pos >= 0) { + char* plsStationName = (pl + pos + 7); // remove <Title> + pos = indexOf(plsStationName, "</", 0); + if(pos >= 0){ + *(plsStationName +pos) = 0; // remove + } + if(audio_showstation) audio_showstation(plsStationName); + sprintf(chbuf, "StationName: \"%s\"", plsStationName); + if(audio_info) audio_info(chbuf); + f_title = true; + } + } //entry + if(indexOf(pl, "http", 0) == 0 && !f_entry) { //url only in asx + memcpy(m_lastHost, pl, strlen(pl)); // save url in array + m_lastHost[strlen(pl)] = '\0'; + log_d("m_lastHost = %s",m_lastHost); + connecttohost(pl); + } + if(f_end) { //we have both StationName and StationURL + connecttohost(m_lastHost); // Connect to it + } + return; + } //asx + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_playlistFormat == FORMAT_M3U8) { + + static bool f_StreamInf = false; // set if #EXT-X-STREAM-INF in m3u8 + static bool f_ExtInf = false; // set if #EXTINF in m3u8 + static uint8_t plsEntry = 0; // used in m3u8, counts url entries + static uint8_t seqNrPos = 0; // position at which the SeqNr is found + + if(!f_begin){ + if(strlen(pl) == 0) return; // empty line + if(strcmp(pl, "#EXTM3U") == 0){ // what we expected + f_begin = true; + f_StreamInf = false; + f_ExtInf = false; + plsEntry = 0; + return; + } + else{ + m_datamode = AUDIO_HEADER; // m3u8 is not valid + m_playlistFormat = FORMAT_NONE; + if(audio_info) audio_info("m3u8 is not valid, switch to AUDIO_HEADER"); + return; + } + } + + // example: redirection + // #EXTM3U + // #EXT-X-STREAM-INF:BANDWIDTH=22050,CODECS="mp4a.40.2" + // http://ample.revma.ihrhls.com/zc7729/63_sdtszizjcjbz02/playlist.m3u8 + + // example: audio chunks + // #EXTM3U + // #EXT-X-TARGETDURATION:10 + // #EXT-X-MEDIA-SEQUENCE:163374040 + // #EXT-X-DISCONTINUITY + // #EXTINF:10,title="text=\"Spot Block End\" amgTrackId=\"9876543\"",artist=" ",url="length=\"00:00:00\"" + // http://n3fa-e2.revma.ihrhls.com/zc7729/63_sdtszizjcjbz02/main/163374038.aac + // #EXTINF:10,title="text=\"Spot Block End\" amgTrackId=\"9876543\"",artist=" ",url="length=\"00:00:00\"" + // http://n3fa-e2.revma.ihrhls.com/zc7729/63_sdtszizjcjbz02/main/163374039.aac + + if(startsWith(pl,"#EXT-X-STREAM-INF:")){ + int pos = indexOf(pl, "CODECS=\"mp4a", 18); + if(pos < 0){ // not found + m_m3u8codec = CODEC_NONE; + log_e("codec %s in m3u8 playlist not supportet", pl); + stopSong(); + return; + } + f_StreamInf = true; + m_m3u8codec = CODEC_M4A; + return; + } + + if(f_StreamInf){ // it's a redirection, a new m3u8 playlist + if(startsWith(pl, "http")){ + strcpy(m_lastHost, pl); + } + else{ + pos = lastIndexOf(m_lastHost, "/"); + strcpy(m_lastHost + pos + 1, pl); + } + f_StreamInf = false; + connecttohost(m_lastHost); + return; + } + + if(m_m3u8codec == CODEC_NONE){ // second guard + if(!f_end) return; + else {connecttohost(m_lastHost); return;} + } + + static uint32_t seqNr = 0; + if(startsWith(pl, "#EXT-X-MEDIA-SEQUENCE:")){ + // do nothing, because MEDIA-SECUENCE is not set sometimes + } + + static uint16_t targetDuration = 0; + if(startsWith(pl, "#EXT-X-TARGETDURATION:")) {targetDuration = atoi(pl + 22);} + + if(startsWith(pl,"#EXTINF")) { + f_ExtInf = true; + if(STfromEXTINF(pl)) showstreamtitle(pl); + return; + } + + if(f_ExtInf){ + f_ExtInf = false; +// log_i("ExtInf=%s", pl); + int16_t lastSlash = lastIndexOf(m_lastHost, "/"); + + if(!m_playlistBuff){ // will be freed in setDefaults() + m_playlistBuff = (char*)malloc(2 * m_plsBuffEntryLen); + strcpy(m_playlistBuff, m_lastHost); // save the m3u8 url at pos 0 + } + + if(plsEntry == 0){ + seqNrPos = 0; + char* entryPos = m_playlistBuff + m_plsBuffEntryLen; + if(startsWith(pl, "http")){ + strcpy(entryPos , pl); + } + else{ + strcpy(entryPos , m_lastHost); // if not start with http complete with host from m_lasthost + strcpy(entryPos + lastSlash + 1 , pl); + } + // now the url is completed, we have a look at the sequenceNumber + if(m_m3u8codec == CODEC_M4A){ + int p1 = lastIndexOf(entryPos, "/"); + int p2 = indexOf(entryPos, ".aac", 0); + if(p1<0 || p2<0){ + log_e("sequenceNumber not found"); + stopSong(); + return; + } + // seqNr must be between p1 and p2 + for(int i = p1; i < p2; i++){ + if(entryPos[i] >= 48 && entryPos[i] <=57){ // numbers only + if(!seqNrPos) seqNrPos = i; + } + else{ + seqNrPos = 0; // in case ...52397ae8f_1.aac?sid=5193 seqNr=1 + } + } + seqNr = atoi(&entryPos[seqNrPos]); + //log_i("entryPos=%s", entryPos); + //log_i("p1=%i, p2=%i, seqNrPos =%i, seqNr=%d", p1, p2, seqNrPos, seqNr); + } + } + plsEntry++; + return; + } + + if(f_end){ + if(plsEntry > 0){ // we have found some (url) entries + processM3U8entries(plsEntry, seqNr, seqNrPos, targetDuration); + } + else{ + connecttohost(m_lastHost); + } + } + } //end m3u8 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + return; + } // end AUDIO_PLAYLISTDATA +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::processM3U8entries(uint8_t _nrOfEntries, uint32_t _seqNr, uint8_t _seqNrpos, uint16_t _targetDuration){ + + // call up the entries in m3u8 sequentially, after the last we need a new playlist + static uint8_t nrOfEntries = 0; + static uint32_t currentSeqNr = 0; + static uint32_t sequenceNr = 0; + static uint32_t maxSeqNr = 0; + static uint8_t sequenceNrPos = 0; + static uint16_t targetDuration = 0; + + char resp[256 + 100]; + char* host = NULL; // e.g http://n31a-e2.revma.ihrhls.com + char* extension = NULL; // e.g hls.m3u8?rj-ttl=5&rj-tok=AAABe9D0_W0AmLAedXo_MfNpLw + char* entryaddr = NULL; + + if(!m_f_m3u8data){ + m_f_m3u8data = true; + nrOfEntries = _nrOfEntries; + currentSeqNr = _seqNr; + sequenceNr = _seqNr; + maxSeqNr = currentSeqNr + _nrOfEntries; + sequenceNrPos = _seqNrpos; + targetDuration = _targetDuration; + connecttohost(m_playlistBuff + m_plsBuffEntryLen); // connect the streamserver first + m_f_m3u8data = true; // connecttohost() will clear m_f_m3u8data, set it again + +// log_i("nrOfEntries=%d, currentSeqNr=%d, sequenceNrPos=%d targetDuration=%d", nrOfEntries, currentSeqNr, sequenceNrPos, targetDuration); +// log_i("m3u8_url=%s", m_playlistBuff); +// log_i("aac_url=%s", m_playlistBuff+ m_plsBuffEntryLen); + + (void)nrOfEntries; // suppress warning -Wunused-but-set-variable + (void)sequenceNr; + (void)targetDuration; + (void)resp; + (void)host; + (void)extension; + + currentSeqNr++; + return; + } + + if(_nrOfEntries == 0){ // processM3U8entries(0,0,0,0) + if(currentSeqNr < maxSeqNr){ // next entry in playlist + char sBuff[12]; itoa(currentSeqNr, sBuff, 10); + entryaddr = m_playlistBuff + m_plsBuffEntryLen; + memcpy(entryaddr + sequenceNrPos, sBuff, strlen(sBuff)); + // log_i("entryaddr=%s", entryaddr); + goto label1; + } + if(currentSeqNr == maxSeqNr){ // we need a new playlist + entryaddr = m_playlistBuff; + // log_i("entryaddr=%s", entryaddr); + + + goto label1; + } + if(currentSeqNr > maxSeqNr){ + return; // guard: in that case never goto label1: + } + } + + if(_nrOfEntries > 0){ // e.g. processM3U8entries(3,123456,55,10) + + while(currentSeqNr > _seqNr) {_seqNr++; _nrOfEntries--;} ; + nrOfEntries = _nrOfEntries; + maxSeqNr = currentSeqNr + _nrOfEntries; + sequenceNrPos = _seqNrpos; + targetDuration = _targetDuration; + char sBuff[12]; itoa(currentSeqNr, sBuff, 10); + entryaddr = m_playlistBuff + m_plsBuffEntryLen; + memcpy(entryaddr + sequenceNrPos, sBuff, strlen(sBuff)); + //log_i("entryaddr=%s", entryaddr); + + goto label1; + } + +label1: + + httpPrint(entryaddr); + + if(currentSeqNr < maxSeqNr){ + m_datamode = AUDIO_HEADER; + m_f_continue = true; + currentSeqNr++; + } + else{ + m_datamode = AUDIO_PLAYLISTINIT; + m_playlistFormat = FORMAT_M3U8; + } + m_f_Log = false; + return; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::STfromEXTINF(char* str){ + // extraxt StreamTitle from m3u #EXTINF line to icy-format + // orig: #EXTINF:10,title="text="TitleName",artist="ArtistName" + // conv: StreamTitle=TitleName - ArtistName + // orig: #EXTINF:10,title="text=\"Spot Block End\" amgTrackId=\"9876543\"",artist=" ",url="length=\"00:00:00\"" + // conv: StreamTitle=text=\"Spot Block End\" amgTrackId=\"9876543\" - + + if(!startsWith(str,"#EXTINF")) return false; + int t1, t2, t3, n0 = 0, n1 = 0, n2 = 0; + + t1 = indexOf(str, "title", 0); + if(t1 > 0){ + strcpy(chbuf, "StreamTitle="); n0 = 12; + t2 = t1 + 7; // title=" + t3 = indexOf(str, "\"", t2); + while(str[t3 - 1] == '\\'){ + t3 = indexOf(str, "\"", t3 + 1); + } + if(t2 < 0 || t2 > t3) return false; + n1 = t3 - t2; + strncpy(chbuf + n0, str + t2, n1); + } + + t1 = indexOf(str, "artist", 0); + if(t1 > 0){ + strcpy(chbuf + n0 + n1, " - "); n1 += 3; + t2 = indexOf(str, "=\"", t1); t2 += 2; + t3 = indexOf(str, "\"", t2); + if(t2 < 0 || t2 > t3) return false; + n2 = t3 - t2; + strncpy(chbuf + n0 + n1, str + t2, n2); + chbuf[n0 + n1 + n2] = '\0'; + } + strcpy(str, chbuf); + + return true; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::processLocalFile() { + + if(!(audiofile && m_f_running && m_f_localfile)) return; + + int bytesDecoded = 0; + uint32_t bytesCanBeWritten = 0; + uint32_t bytesCanBeRead = 0; + int32_t bytesAddedToBuffer = 0; + static bool f_stream; + + if(m_f_firstCall) { // runs only one time per connection, prepare for start + m_f_firstCall = false; + f_stream = false; + return; + } + bytesCanBeWritten = InBuff.writeSpace(); + //---------------------------------------------------------------------------------------------------- + // some files contain further data after the audio block (e.g. pictures). + // In that case, the end of the audio block is not the end of the file. An 'eof' has to be forced. + if((m_controlCounter == 100) && (m_contentlength > 0)) { // fileheader was read + if(bytesCanBeWritten + getFilePos() >= m_contentlength){ + if(m_contentlength > getFilePos()) bytesCanBeWritten = m_contentlength - getFilePos(); + else bytesCanBeWritten = 0; + } + } + //---------------------------------------------------------------------------------------------------- + + bytesAddedToBuffer = audiofile.read(InBuff.getWritePtr(), bytesCanBeWritten); + if(bytesAddedToBuffer > 0) { + InBuff.bytesWritten(bytesAddedToBuffer); + } + +// if(psramFound() && bytesAddedToBuffer >4096) +// vTaskDelay(2);// PSRAM has a bottleneck in the queue, so wait a little bit + + if(bytesAddedToBuffer == -1) bytesAddedToBuffer = 0; // read error? eof? + bytesCanBeRead = InBuff.bufferFilled(); + if(bytesCanBeRead > InBuff.getMaxBlockSize()) bytesCanBeRead = InBuff.getMaxBlockSize(); + if(bytesCanBeRead == InBuff.getMaxBlockSize()) { // mp3 or aac frame complete? + if(!f_stream) { + f_stream = true; + if(audio_info) audio_info("stream ready"); + } + if(m_controlCounter != 100){ + if(m_codec == CODEC_WAV){ + int res = read_WAV_Header(InBuff.getReadPtr(), bytesCanBeRead); + if(res >= 0) bytesDecoded = res; + else{ // error, skip header + m_controlCounter = 100; + } + } + if(m_codec == CODEC_MP3){ + int res = read_MP3_Header(InBuff.getReadPtr(), bytesCanBeRead); + if(res >= 0) bytesDecoded = res; + else{ // error, skip header + m_controlCounter = 100; + } + } + if(m_codec == CODEC_M4A){ + int res = read_M4A_Header(InBuff.getReadPtr(), bytesCanBeRead); + if(res >= 0) bytesDecoded = res; + else{ // error, skip header + m_controlCounter = 100; + } + } + if(m_codec == CODEC_AAC){ + // stream only, no header + m_audioDataSize = getFileSize(); + m_controlCounter = 100; + } + + if(m_codec == CODEC_FLAC){ + int res = read_FLAC_Header(InBuff.getReadPtr(), bytesCanBeRead); + if(res >= 0) bytesDecoded = res; + else{ // error, skip header + stopSong(); + m_controlCounter = 100; + } + } + } + else { + bytesDecoded = sendBytes(InBuff.getReadPtr(), bytesCanBeRead); + } + if(bytesDecoded > 0) {InBuff.bytesWasRead(bytesDecoded); return;} + if(bytesDecoded < 0) { // no syncword found or decode error, try next chunk + InBuff.bytesWasRead(200); // try next chunk + m_bytesNotDecoded += 200; + return; + } + return; + } + if(!bytesAddedToBuffer) { // eof + bytesCanBeRead = InBuff.bufferFilled(); + if(bytesCanBeRead > 200){ + if(bytesCanBeRead > InBuff.getMaxBlockSize()) bytesCanBeRead = InBuff.getMaxBlockSize(); + bytesDecoded = sendBytes(InBuff.getReadPtr(), bytesCanBeRead); // play last chunk(s) + if(bytesDecoded > 0){ + InBuff.bytesWasRead(bytesDecoded); + return; + } + } + InBuff.resetBuffer(); + if(!playI2Sremains()) return; + + if(m_f_loop && f_stream){ //eof + sprintf(chbuf, "loop from: %u to: %u", getFilePos(), m_audioDataStart); //TEST loop + if(audio_info) audio_info(chbuf); + setFilePos(m_audioDataStart); + if(m_codec == CODEC_FLAC) FLACDecoderReset(); + /* + The current time of the loop mode is not reset, + which will cause the total audio duration to be exceeded. + For example: current time ====progress bar====> total audio duration + 3:43 ====================> 3:33 + */ + m_audioCurrentTime = 0; + return; + } //TEST loop + f_stream = false; + m_f_localfile = false; + +#ifdef SDFATFS_USED + audiofile.getName(chbuf, sizeof(chbuf)); + char *afn =strdup(chbuf); +#else + char *afn =strdup(audiofile.name()); // store temporary the name +#endif + + stopSong(); + if(m_codec == CODEC_MP3) MP3Decoder_FreeBuffers(); + if(m_codec == CODEC_AAC) AACDecoder_FreeBuffers(); + if(m_codec == CODEC_M4A) AACDecoder_FreeBuffers(); + if(m_codec == CODEC_FLAC) FLACDecoder_FreeBuffers(); + sprintf(chbuf, "End of file \"%s\"", afn); + if(audio_info) audio_info(chbuf); + if(audio_eof_mp3) audio_eof_mp3(afn); + if(afn) free(afn); + } +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::processWebStream() { + + const uint16_t maxFrameSize = InBuff.getMaxBlockSize(); // every mp3/aac frame is not bigger + int32_t availableBytes; // available bytes in stream + static bool f_tmr_1s; + static bool f_stream; // first audio data received + static int bytesDecoded; + static uint32_t byteCounter; // count received data + static uint32_t chunksize; // chunkcount read from stream + static uint32_t tmr_1s; // timer 1 sec + static uint32_t loopCnt; // count loops if clientbuffer is empty + static uint32_t metacount; // counts down bytes between metadata + + + // first call, set some values to default - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_f_firstCall) { // runs only ont time per connection, prepare for start + m_f_firstCall = false; + f_stream = false; + byteCounter = 0; + chunksize = 0; + bytesDecoded = 0; + loopCnt = 0; + tmr_1s = millis(); + m_t0 = millis(); + metacount = m_metaint; + readMetadata(0, true); // reset all static vars + } + if(m_f_continue){ // next m3u8 chunk is available + byteCounter = 0; + metacount = m_metaint; + m_f_continue = false; + } + + // have we reached the end of the webfile? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_f_webfile && byteCounter == m_contentlength){ + if(InBuff.bufferFilled() < 10000){ + if(m_datamode == AUDIO_DATA && m_f_m3u8data) {processM3U8entries(); return;} // we need the next audioChunk + } + if(InBuff.bufferFilled() > 0){ + if(InBuff.bufferFilled() == 128){ // post tag? comes sometimes after podcasts + if(indexOf((const char*)InBuff.getReadPtr(), "TAG", 0) == 0){ + log_i("%s", InBuff.getReadPtr() + 3); + InBuff.bytesWasRead(128); + return; + } + else{ + log_v("%s", InBuff.getReadPtr()); + InBuff.bytesWasRead(InBuff.bufferFilled()); + return; + } + } + bytesDecoded = sendBytes(InBuff.getReadPtr(), InBuff.bufferFilled()); + if(bytesDecoded > 0) {InBuff.bytesWasRead(bytesDecoded); return;} + if(bytesDecoded == 0) return; // syncword at pos0 found + } + if(m_f_m3u8data) return; + + while(!playI2Sremains()){;} + stopSong(); // Correct close when play known length sound #74 and before callback #112 + + if(m_f_tts){ + sprintf(chbuf, "End of speech: \"%s\"", m_lastHost); + if(audio_info) audio_info(chbuf); + if(audio_eof_speech) audio_eof_speech(m_lastHost); + } + else{ + sprintf(chbuf, "End of webstream: \"%s\"", m_lastHost); + if(audio_info) audio_info(chbuf); + if(audio_eof_stream) audio_eof_stream(m_lastHost); + } + return; + } + + if(m_datamode != AUDIO_DATA) return; + + // timer, triggers every second - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if((tmr_1s + 1000) < millis()) { + f_tmr_1s = true; // flag will be set every second for one loop only + tmr_1s = millis(); + } + + if(m_f_ssl == false) availableBytes = client.available(); // available from stream + if(m_f_ssl == true) availableBytes = clientsecure.available(); // available from stream + + // if we have chunked data transfer: get the chunksize- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_f_chunked && !m_chunkcount && availableBytes) { // Expecting a new chunkcount? + int b; + if(!m_f_ssl) b = client.read(); + else b = clientsecure.read(); + + if(b == '\r') return; + if(b == '\n'){ + m_chunkcount = chunksize; + chunksize = 0; + if(m_f_tts){ + m_contentlength = m_chunkcount; // tts has one chunk only + m_f_webfile = true; + m_f_chunked = false; + } + return; + } + // We have received a hexadecimal character. Decode it and add to the result. + b = toupper(b) - '0'; // Be sure we have uppercase + if(b > 9) b = b - 7; // Translate A..F to 10..15 + chunksize = (chunksize << 4) + b; + return; + } + + // if we have metadata: get them - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(!metacount && !m_f_swm && availableBytes){ + int16_t b = 0; + if(!m_f_ssl) b = client.read(); + else b = clientsecure.read(); + if(b >= 0) { + if(m_f_chunked) m_chunkcount--; + if(readMetadata(b)) metacount = m_metaint; + } + return; + } + + // if the buffer is often almost empty issue a warning - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(InBuff.bufferFilled() < maxFrameSize && f_stream){ + static uint8_t cnt_slow = 0; + cnt_slow ++; + if(f_tmr_1s) { + if(cnt_slow > 25 && audio_info) audio_info("slow stream, dropouts are possible"); + f_tmr_1s = false; + cnt_slow = 0; + } + } + + // if the buffer can't filled for several seconds try a new connection - - - - - - - - - - - - - - - - - - - - - - + if(f_stream && !availableBytes){ + loopCnt++; + if(loopCnt > 200000) { // wait several seconds + loopCnt = 0; + if(audio_info) audio_info("Stream lost -> try new connection"); + connecttohost(m_lastHost); + } + } + if(availableBytes) loopCnt = 0; + + // buffer fill routine - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(true) { // statement has no effect + uint32_t bytesCanBeWritten = InBuff.writeSpace(); + if(!m_f_swm) bytesCanBeWritten = min(metacount, bytesCanBeWritten); + if(m_f_chunked) bytesCanBeWritten = min(m_chunkcount, bytesCanBeWritten); + + int16_t bytesAddedToBuffer = 0; + + if(m_f_psram) if(bytesCanBeWritten > 4096) bytesCanBeWritten = 4096; // PSRAM throttle + + if(m_f_webfile){ + // normally there is nothing to do here, if byteCounter == contentLength + // then the file is completely read, but: + // m4a files can have more data (e.g. pictures ..) after the audio Block + // therefore it is bad to read anything else (this can generate noise) + if(byteCounter + bytesCanBeWritten >= m_contentlength) bytesCanBeWritten = m_contentlength - byteCounter; + } + + if(m_f_ssl == false) bytesAddedToBuffer = client.read(InBuff.getWritePtr(), bytesCanBeWritten); + else bytesAddedToBuffer = clientsecure.read(InBuff.getWritePtr(), bytesCanBeWritten); + + if(bytesAddedToBuffer > 0) { + if(m_f_webfile) byteCounter += bytesAddedToBuffer; // Pull request #42 + if(!m_f_swm) metacount -= bytesAddedToBuffer; + if(m_f_chunked) m_chunkcount -= bytesAddedToBuffer; + InBuff.bytesWritten(bytesAddedToBuffer); + } + + if(InBuff.bufferFilled() > maxFrameSize && !f_stream) { // waiting for buffer filled + f_stream = true; // ready to play the audio data + uint16_t filltime = millis() - m_t0; + if(audio_info) audio_info("stream ready"); + sprintf(chbuf, "buffer filled in %d ms", filltime); + if(audio_info) audio_info(chbuf); + } + if(!f_stream) return; + } + + // if we have a webfile, read the file header first - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_f_webfile && m_controlCounter != 100 && !m_f_m3u8data){ // m3u8call, audiochunk has no header + if(InBuff.bufferFilled() < maxFrameSize) return; + if(m_codec == CODEC_WAV){ + int res = read_WAV_Header(InBuff.getReadPtr(), InBuff.bufferFilled()); + if(res >= 0) bytesDecoded = res; + else{stopSong(); return;} + } + if(m_codec == CODEC_MP3){ + int res = read_MP3_Header(InBuff.getReadPtr(), InBuff.bufferFilled()); + if(res >= 0) bytesDecoded = res; + else{m_controlCounter = 100;} // error, skip header + } + if(m_codec == CODEC_M4A){ + int res = read_M4A_Header(InBuff.getReadPtr(), InBuff.bufferFilled()); + if(res >= 0) bytesDecoded = res; + else{stopSong(); return;} + } + if(m_codec == CODEC_FLAC){ + int res = read_FLAC_Header(InBuff.getReadPtr(), InBuff.bufferFilled()); + if(res >= 0) bytesDecoded = res; + else{stopSong(); return;} // error, skip header + } + if(m_codec == CODEC_AAC){ // aac has no header + m_controlCounter = 100; + return; + } + InBuff.bytesWasRead(bytesDecoded); + return; + } + + // play audio data - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if((InBuff.bufferFilled() >= maxFrameSize) && (f_stream == true)) { // fill > framesize? + if(m_f_webfile){ + bytesDecoded = sendBytes(InBuff.getReadPtr(), maxFrameSize); + } + else { // not a webfile + if(m_controlCounter != 100 && (m_codec == CODEC_OGG || m_codec == CODEC_OGG_FLAC)) { //application/ogg + int res = read_OGG_Header(InBuff.getReadPtr(), InBuff.bufferFilled()); + if(res >= 0) bytesDecoded = res; + else { // error, skip header + stopSong(); + m_controlCounter = 100; + } + } + else{ + bytesDecoded = sendBytes(InBuff.getReadPtr(), maxFrameSize); + } + } + if(bytesDecoded < 0) { // no syncword found or decode error, try next chunk + InBuff.bytesWasRead(200); // try next chunk + m_bytesNotDecoded += 200; + return; + } + else { + InBuff.bytesWasRead(bytesDecoded); + } + } + return; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::processAudioHeaderData() { + + int av = 0; + if(!m_f_ssl) av=client.available(); + else av= clientsecure.available(); + if(av <= 0) return; + + char hl[512]; // headerline + uint8_t b = 0; + uint16_t pos = 0; + int16_t idx = 0; + static bool f_icyname = false; + static bool f_icydescription = false; + static bool f_icyurl = false; + + while(true){ + if(!m_f_ssl) b = client.read(); + else b = clientsecure.read(); + if(b == '\n') break; + if(b == '\r') hl[pos] = 0; + if(b < 0x20) continue; + hl[pos] = b; + pos++; + if(pos == 510){ + hl[pos] = '\0'; + log_e("headerline overflow"); + break; + } + } + + if(!pos && m_f_ctseen){ // audio header complete? + m_datamode = AUDIO_DATA; // Expecting data now + sprintf(chbuf, "Switch to DATA, metaint is %d", m_metaint); + if(m_f_Log) if(audio_info) audio_info(chbuf); + memcpy(chbuf, m_lastHost, strlen(m_lastHost)+1); +// uint idx = indexOf(chbuf, "?", 0); +// if(idx > 0) chbuf[idx] = 0; + if(audio_lasthost) audio_lasthost(chbuf); + if(!f_icyname){if(audio_showstation) audio_showstation("");} + if(!f_icydescription){if(audio_icydescription) audio_icydescription("");} + if(!f_icyurl){if(audio_icyurl) audio_icyurl("");} + f_icyname = false; + f_icydescription = false; + f_icyurl = false; + delay(50); // #77 + return; + } + if(!pos){ + stopSong(); + if(audio_showstation) audio_showstation(""); + if(audio_icydescription) audio_icydescription(""); + if(audio_icyurl) audio_icyurl(""); + f_icyname = false; + f_icydescription = false; + f_icyurl = false; + log_e("can't see content in audioHeaderData"); + return; + } + + idx = indexOf(hl, ":", 0); // lowercase all letters up to the colon + if(idx >= 0) { + for(int i=0; i< idx; i++) { + hl[i] = toLowerCase(hl[i]); + } + } + + if(indexOf(hl, "HTTP/1.0 404", 0) >= 0) { + m_f_running = false; + stopSong(); + if(audio_info) audio_info("404 Not Found"); + return; + } + + if(indexOf(hl, "HTTP/1.1 404", 0) >= 0) { + m_f_running = false; + stopSong(); + if(audio_info) audio_info("404 Not Found"); + return; + } + + if(indexOf(hl, "ICY 401", 0) >= 0) { + m_f_running = false; + stopSong(); + if(audio_info) audio_info("ICY 401 Service Unavailable"); + return; + } + + if(indexOf(hl, "content-type:", 0) >= 0) { + if(parseContentType(hl)) m_f_ctseen = true; + } + else if(startsWith(hl, "location:")) { + int pos = indexOf(hl, "http", 0); + const char* c_host = (hl + pos); + sprintf(chbuf, "redirect to new host \"%s\"", c_host); + if(audio_info) audio_info(chbuf); + connecttohost(c_host); + } + else if(startsWith(hl, "content-disposition:")) { + int pos1, pos2; // pos3; + // e.g we have this headerline: content-disposition: attachment; filename=stream.asx + // filename is: "stream.asx" + pos1 = indexOf(hl, "filename=", 0); + if(pos1 > 0){ + pos1 += 9; + if(hl[pos1] == '\"') pos1++; // remove '\"' around filename if present + pos2 = strlen(hl); + if(hl[pos2 - 1] == '\"') hl[pos2 - 1] = '\0'; + } + log_d("Filename is %s", hl + pos1); + } + else if(startsWith(hl, "set-cookie:") || + startsWith(hl, "pragma:") || + startsWith(hl, "expires:") || + startsWith(hl, "cache-control:") || + startsWith(hl, "icy-pub:") || + startsWith(hl, "p3p:") || + startsWith(hl, "accept-ranges:") ){ + ; // do nothing + } + else if(startsWith(hl, "connection:")) { + if(indexOf(hl, "close", 0) >= 0) {; /* do nothing */} + } + else if(startsWith(hl, "icy-genre:")) { + ; // do nothing Ambient, Rock, etc + } + else if(startsWith(hl, "icy-br:")) { + const char* c_bitRate = (hl + 7); + int32_t br = atoi(c_bitRate); // Found bitrate tag, read the bitrate in Kbit + br = br * 1000; + setBitrate(br); + sprintf(chbuf, "%d", getBitRate()); + if(audio_bitrate) audio_bitrate(chbuf); + } + else if(startsWith(hl, "icy-metaint:")) { + const char* c_metaint = (hl + 12); + int32_t i_metaint = atoi(c_metaint); + m_metaint = i_metaint; + if(m_metaint) m_f_swm = false ; // Multimediastream + } + else if(startsWith(hl, "icy-name:")) { + char* c_icyname = (hl + 9); // Get station name + idx = 0; + while(c_icyname[idx] == ' '){idx++;} c_icyname += idx; // Remove leading spaces + idx = strlen(c_icyname); + while(c_icyname[idx] == ' '){idx--;} c_icyname[idx + 1] = 0; // Remove trailing spaces + + if(strlen(c_icyname) > 0) { + sprintf(chbuf, "icy-name: %s", c_icyname); + if(audio_info) audio_info(chbuf); + if(audio_showstation) audio_showstation(c_icyname); + f_icyname = true; + } + } + else if(startsWith(hl, "content-length:")) { + const char* c_cl = (hl + 15); + int32_t i_cl = atoi(c_cl); + m_contentlength = i_cl; + m_f_webfile = true; // Stream comes from a fileserver + sprintf(chbuf, "content-length: %i", m_contentlength); + if(m_f_Log) if(audio_info) audio_info(chbuf); + } + else if(startsWith(hl, "icy-description:")) { + const char* c_idesc = (hl + 16); + while(c_idesc[0] == ' ') c_idesc++; + latinToUTF8(hl, sizeof(hl)); // if already UTF-0 do nothing, otherwise convert to UTF-8 + if(audio_icydescription) audio_icydescription(c_idesc); + f_icydescription = true; + } + else if((startsWith(hl, "transfer-encoding:"))){ + if(endsWith(hl, "chunked") || endsWith(hl, "Chunked") ) { // Station provides chunked transfer + m_f_chunked = true; + if(audio_info) audio_info("chunked data transfer"); + m_chunkcount = 0; // Expect chunkcount in DATA + } + } + else if(startsWith(hl, "icy-url:")) { + const char* icyurl = (hl + 8); + idx = 0; + while(icyurl[idx] == ' ') {idx ++;} icyurl += idx; // remove leading blanks + sprintf(chbuf, "icy-url: %s", icyurl); + // if(audio_info) audio_info(chbuf); + if(audio_icyurl) audio_icyurl(icyurl); + f_icyurl = true; + } + else if(startsWith(hl, "www-authenticate:")) { + if(audio_info) audio_info("authentification failed, wrong credentials?"); + m_f_running = false; + stopSong(); + } + else { + if(isascii(hl[0]) && hl[0] >= 0x20) { // all other + sprintf(chbuf, "%s", hl); + if(m_f_Log) if(audio_info) audio_info(chbuf); + } + } + return; +} + +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::readMetadata(uint8_t b, bool first) { + + static uint16_t pos_ml = 0; // determines the current position in metaline + static uint16_t metalen = 0; + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(first){ + pos_ml = 0; + metalen = 0; + return true; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(!metalen) { // First byte of metadata? + metalen = b * 16 + 1; // New count for metadata including length byte + if(metalen >512){ + if(audio_info) audio_info("Metadata block to long! Skipping all Metadata from now on."); + m_f_swm = true; // expect stream without metadata + } + pos_ml = 0; chbuf[pos_ml] = 0; // Prepare for new line + } + else { + chbuf[pos_ml] = (char) b; // Put new char in +++++ + if(pos_ml < 510) pos_ml ++; + chbuf[pos_ml] = 0; + if(pos_ml == 509) { log_e("metaline overflow in AUDIO_METADATA! metaline=%s", chbuf); } + if(pos_ml == 510) { ; /* last current char in b */} + + } + if(--metalen == 0) { + if(strlen(chbuf)) { // Any info present? + // metaline contains artist and song name. For example: + // "StreamTitle='Don McLean - American Pie';StreamUrl='';" + // Sometimes it is just other info like: + // "StreamTitle='60s 03 05 Magic60s';StreamUrl='';" + // Isolate the StreamTitle, remove leading and trailing quotes if present. + // log_i("ST %s", metaline); + + latinToUTF8(chbuf, sizeof(chbuf)); // convert to UTF-8 if necessary + + int pos = indexOf(chbuf, "song_spot", 0); // remove some irrelevant infos + if(pos > 3) { // e.g. song_spot="T" MediaBaseId="0" itunesTrackId="0" + chbuf[pos] = 0; + } + if(!m_f_localfile) showstreamtitle(chbuf); // Show artist and title if present in metadata + } + return true ; + } + return false;// end_METADATA +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::parseContentType(const char* ct) { + bool ct_seen = false; + if(indexOf(ct, "audio", 0) >= 0) { // Is ct audio? + ct_seen = true; // Yes, remember seeing this + if(indexOf(ct, "mpeg", 13) >= 0) { + m_codec = CODEC_MP3; + sprintf(chbuf, "%s, format is mp3", ct); + if(audio_info) audio_info(chbuf); //ok is likely mp3 + if(!MP3Decoder_AllocateBuffers()) {m_f_running = false; stopSong(); return false;} + sprintf(chbuf, "MP3Decoder has been initialized, free Heap: %u bytes", ESP.getFreeHeap()); + InBuff.changeMaxBlockSize(m_frameSizeMP3); + if(audio_info) audio_info(chbuf); + } + else if(indexOf(ct, "mp3", 13) >= 0) { + m_codec = CODEC_MP3; + sprintf(chbuf, "%s, format is mp3", ct); + if(audio_info) audio_info(chbuf); + if(!MP3Decoder_AllocateBuffers()) {m_f_running = false; stopSong(); return false;} + sprintf(chbuf, "MP3Decoder has been initialized, free Heap: %u bytes", ESP.getFreeHeap()); + InBuff.changeMaxBlockSize(m_frameSizeMP3); + if(audio_info) audio_info(chbuf); + } + else if(indexOf(ct, "aac", 13) >= 0) { // audio/x-aac + m_codec = CODEC_AAC; + sprintf(chbuf, "%s, format is aac", ct); + if(m_f_Log) if(audio_info) audio_info(chbuf); + if(!AACDecoder_IsInit()){ + if(!AACDecoder_AllocateBuffers()) {m_f_running = false; stopSong(); return false;} + sprintf(chbuf, "AACDecoder has been initialized, free Heap: %u bytes", ESP.getFreeHeap()); + InBuff.changeMaxBlockSize(m_frameSizeAAC); + if(audio_info) audio_info(chbuf); + } + } + else if(indexOf(ct, "mp4", 13) >= 0) { // audio/mp4a, audio/mp4a-latm + m_codec = CODEC_M4A; + sprintf(chbuf, "%s, format is aac", ct); + if(m_f_Log) if(audio_info) audio_info(chbuf); + if(!AACDecoder_IsInit()){ + if(!AACDecoder_AllocateBuffers()) {m_f_running = false; stopSong(); return false;} + sprintf(chbuf, "AACDecoder has been initialized, free Heap: %u bytes", ESP.getFreeHeap()); + InBuff.changeMaxBlockSize(m_frameSizeAAC); + if(audio_info) audio_info(chbuf); + } + } + else if(indexOf(ct, "m4a", 13) >= 0) { // audio/x-m4a + m_codec = CODEC_M4A; + sprintf(chbuf, "%s, format is aac", ct); + if(audio_info) audio_info(chbuf); + if(!AACDecoder_IsInit()){ + if(!AACDecoder_AllocateBuffers()) {m_f_running = false; stopSong(); return false;} + sprintf(chbuf, "AACDecoder has been initialized, free Heap: %u bytes", ESP.getFreeHeap()); + InBuff.changeMaxBlockSize(m_frameSizeAAC); + if(audio_info) audio_info(chbuf); + } + } + else if(indexOf(ct, "wav", 13) >= 0) { // audio/x-wav + m_codec = CODEC_WAV; + sprintf(chbuf, "%s, format is wav", ct); + if(audio_info) audio_info(chbuf); + InBuff.changeMaxBlockSize(m_frameSizeWav); + } + else if(indexOf(ct, "ogg", 13) >= 0) { + m_codec = CODEC_OGG; + sprintf(chbuf, "ContentType %s found", ct); + if(audio_info) audio_info(chbuf); + } + else if(indexOf(ct, "flac", 13) >= 0) { // audio/flac, audio/x-flac + m_codec = CODEC_FLAC; + sprintf(chbuf, "%s, format is flac", ct); + if(audio_info) audio_info(chbuf); + if(!psramFound()){ + if(audio_info) audio_info("FLAC works only with PSRAM!"); + m_f_running = false; + return false; + } + if(!FLACDecoder_AllocateBuffers()) {m_f_running = false; stopSong(); return false;} + InBuff.changeMaxBlockSize(m_frameSizeFLAC); + sprintf(chbuf, "FLACDecoder has been initialized, free Heap: %u bytes", ESP.getFreeHeap()); + if(audio_info) audio_info(chbuf); + } + else { + m_f_running = false; + sprintf(chbuf, "%s, unsupported audio format", ct); + if(audio_info) audio_info(chbuf); + } + } + if(indexOf(ct, "application", 0) >= 0) { // Is ct application? + ct_seen = true; // Yes, remember seeing this + uint8_t pos = indexOf(ct, "application", 0); + if(indexOf(ct, "ogg", 13) >= 0) { + m_codec = CODEC_OGG; + sprintf(chbuf, "ContentType %s found", ct + pos); + if(audio_info) audio_info(chbuf); + } + } + return ct_seen; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::showstreamtitle(const char* ml) { + // example for ml: + // StreamTitle='Oliver Frank - Mega Hitmix';StreamUrl='www.radio-welle-woerthersee.at'; + // or adw_ad='true';durationMilliseconds='10135';adId='34254';insertionType='preroll'; + + int16_t idx1, idx2; + uint16_t i = 0, hash = 0; + static uint16_t sTit_remember = 0, sUrl_renember = 0; + + idx1 = indexOf(ml, "StreamTitle=", 0); + if(idx1 >= 0){ // Streamtitle found + idx2 = indexOf(ml, ";", idx1); + char *sTit; + if(idx2 >= 0){sTit = strndup(ml + idx1, idx2 + 1); sTit[idx2] = '\0';} + else sTit = strdup(ml); + + while(i < strlen(sTit)){hash += sTit[i] * i+1; i++;} + + if(sTit_remember != hash){ + sTit_remember = hash; + if(audio_info) audio_info(sTit); + uint8_t pos = 12; // remove "StreamTitle=" + if(sTit[pos] == '\'') pos++; // remove leading \' + if(sTit[strlen(sTit) - 1] == '\'') sTit[strlen(sTit) -1] = '\0'; // remove trailing \' + if(audio_showstreamtitle) audio_showstreamtitle(sTit + pos); + } + free(sTit); + } + + idx1 = indexOf(ml, "StreamUrl=", 0); + idx2 = indexOf(ml, ";", idx1); + if(idx1 >= 0 && idx2 > idx1){ // StreamURL found + uint16_t len = idx2 - idx1; + char *sUrl; + sUrl = strndup(ml + idx1, len + 1); sUrl[len] = '\0'; + + while(i < strlen(sUrl)){hash += sUrl[i] * i+1; i++;} + if(sUrl_renember != hash){ + sUrl_renember = hash; + if(audio_info) audio_info(sUrl); + } + free(sUrl); + } + + idx1 = indexOf(ml, "adw_ad=", 0); + if(idx1 >= 0){ // Advertisement found + idx1 = indexOf(ml, "durationMilliseconds=", 0); + idx2 = indexOf(ml, ";", idx1); + if(idx1 >= 0 && idx2 > idx1){ + uint16_t len = idx2 - idx1; + char *sAdv; + sAdv = strndup(ml + idx1, len + 1); sAdv[len] = '\0'; + if(audio_info) audio_info(sAdv); + uint8_t pos = 21; // remove "StreamTitle=" + if(sAdv[pos] == '\'') pos++; // remove leading \' + if(sAdv[strlen(sAdv) - 1] == '\'') sAdv[strlen(sAdv) -1] = '\0'; // remove trailing \' + if(audio_commercial) audio_commercial(sAdv + pos); + free(sAdv); + } + } +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::showCodecParams(){ + // print Codec Parameter (mp3, aac) in audio_info() + sprintf(chbuf,"Channels: %i", getChannels()); + if(audio_info) audio_info(chbuf); + sprintf(chbuf,"SampleRate: %i", getSampleRate()); + if(audio_info) audio_info(chbuf); + sprintf(chbuf,"BitsPerSample: %i", getBitsPerSample()); + if(audio_info) audio_info(chbuf); + if(getBitRate()){ + sprintf(chbuf,"BitRate: %i", getBitRate()); + if(audio_info) audio_info(chbuf); + } + else { + if(audio_info) audio_info("BitRate: N/A"); + } + + if(m_codec == CODEC_AAC || m_codec == CODEC_M4A){ + uint8_t answ; + if((answ = AACGetFormat()) < 4){ + const char hf[4][8] = {"unknown", "ADTS", "ADIF", "RAW"}; + sprintf(chbuf, "AAC HeaderFormat: %s", hf[answ]); + if(audio_info) audio_info(chbuf); + } + if(answ == 1){ // ADTS Header + const char co[2][23] = {"MPEG-4", "MPEG-2"}; + sprintf(chbuf, "AAC Codec: %s", co[AACGetID()]); + if(audio_info) audio_info(chbuf); + if(AACGetProfile() <5){ + const char pr[4][23] = {"Main", "LowComplexity", "Scalable Sampling Rate", "reserved"}; + sprintf(chbuf, "AAC Profile: %s", pr[answ]); + if(audio_info) audio_info(chbuf); + } + } + } +} +//--------------------------------------------------------------------------------------------------------------------- +int Audio::findNextSync(uint8_t* data, size_t len){ + // Mp3 and aac audio data are divided into frames. At the beginning of each frame there is a sync word. + // The sync word is 0xFFF. This is followed by information about the structure of the frame. + // Wav files have no frames + // Return: 0 the synchronous word was found at position 0 + // > 0 is the offset to the next sync word + // -1 the sync word was not found within the block with the length len + + int nextSync; + static uint32_t swnf = 0; + if(m_codec == CODEC_WAV) { + m_f_playing = true; nextSync = 0; + } + if(m_codec == CODEC_MP3) { + nextSync = MP3FindSyncWord(data, len); + } + if(m_codec == CODEC_AAC) { + nextSync = AACFindSyncWord(data, len); + } + if(m_codec == CODEC_M4A) { + AACSetRawBlockParams(0, 2,44100, 1); m_f_playing = true; nextSync = 0; + } + if(m_codec == CODEC_FLAC) { + FLACSetRawBlockParams(m_flacNumChannels, m_flacSampleRate, + m_flacBitsPerSample, m_flacTotalSamplesInStream, m_audioDataSize); + nextSync = FLACFindSyncWord(data, len); + } + if(m_codec == CODEC_OGG_FLAC) { + FLACSetRawBlockParams(m_flacNumChannels, m_flacSampleRate, + m_flacBitsPerSample, m_flacTotalSamplesInStream, m_audioDataSize); + nextSync = FLACFindSyncWord(data, len); + } + if(nextSync == -1) { + if(audio_info && swnf == 0) audio_info("syncword not found"); + if(m_codec == CODEC_OGG_FLAC){ + nextSync = len; + } + else { + swnf++; // syncword not found counter, can be multimediadata + } + } + if (nextSync == 0){ + if(audio_info && swnf>0){ + sprintf(chbuf, "syncword not found %i times", swnf); + audio_info(chbuf); + swnf = 0; + } + else { + if(audio_info) audio_info("syncword found at pos 0"); + } + } + if(nextSync > 0){ + sprintf(chbuf, "syncword found at pos %i", nextSync); + if(audio_info) audio_info(chbuf); + } + return nextSync; +} +//--------------------------------------------------------------------------------------------------------------------- +int Audio::sendBytes(uint8_t* data, size_t len) { + int bytesLeft; + static bool f_setDecodeParamsOnce = true; + int nextSync = 0; + if(!m_f_playing) { + f_setDecodeParamsOnce = true; + nextSync = findNextSync(data, len); + if(nextSync == 0) { m_f_playing = true;} + return nextSync; + } + // m_f_playing is true at this pos + bytesLeft = len; + int ret = 0; + int bytesDecoded = 0; + if(m_codec == CODEC_WAV){ //copy len data in outbuff and set validsamples and bytesdecoded=len + memmove(m_outBuff, data , len); + if(getBitsPerSample() == 16) m_validSamples = len / (2 * getChannels()); + if(getBitsPerSample() == 8 ) m_validSamples = len / 2; + bytesLeft = 0; + } + if(m_codec == CODEC_MP3) ret = MP3Decode(data, &bytesLeft, m_outBuff, 0); + if(m_codec == CODEC_AAC) ret = AACDecode(data, &bytesLeft, m_outBuff); + if(m_codec == CODEC_M4A) ret = AACDecode(data, &bytesLeft, m_outBuff); + if(m_codec == CODEC_FLAC) ret = FLACDecode(data, &bytesLeft, m_outBuff); + if(m_codec == CODEC_OGG_FLAC) ret = FLACDecode(data, &bytesLeft, m_outBuff); // FLAC webstream wrapped in OGG + + bytesDecoded = len - bytesLeft; + if(bytesDecoded == 0 && ret == 0){ // unlikely framesize + if(audio_info) audio_info("framesize is 0, start decoding again"); + m_f_playing = false; // seek for new syncword + // we're here because there was a wrong sync word + // so skip two sync bytes and seek for next + return 1; + } + if(ret < 0) { // Error, skip the frame... + //if(m_codec == CODEC_M4A){log_i("begin not found"); return 1;} + i2s_zero_dma_buffer((i2s_port_t)m_i2s_num); + if(!getChannels() && (ret == -2)) { + ; // suppress errorcode MAINDATA_UNDERFLOW + } + else { + printDecodeError(ret); + m_f_playing = false; // seek for new syncword + } + if(!bytesDecoded) bytesDecoded = 2; + return bytesDecoded; + } + else{ // ret>=0 + if(f_setDecodeParamsOnce){ + f_setDecodeParamsOnce = false; + m_PlayingStartTime = millis(); + + if(m_codec == CODEC_MP3){ + setChannels(MP3GetChannels()); + setSampleRate(MP3GetSampRate()); + setBitsPerSample(MP3GetBitsPerSample()); + setBitrate(MP3GetBitrate()); + } + if(m_codec == CODEC_AAC || m_codec == CODEC_M4A){ + setChannels(AACGetChannels()); + setSampleRate(AACGetSampRate()); + setBitsPerSample(AACGetBitsPerSample()); + setBitrate(AACGetBitrate()); + } + if(m_codec == CODEC_FLAC || m_codec == CODEC_OGG_FLAC){ + setChannels(FLACGetChannels()); + setSampleRate(FLACGetSampRate()); + setBitsPerSample(FLACGetBitsPerSample()); + setBitrate(FLACGetBitRate()); + } + showCodecParams(); + if(m_f_tts) while(!playI2Sremains()){;} // short silence + } + if(m_codec == CODEC_MP3){ + m_validSamples = MP3GetOutputSamps() / getChannels(); + } + if((m_codec == CODEC_AAC) || (m_codec == CODEC_M4A)){ + m_validSamples = AACGetOutputSamps() / getChannels(); + } + if((m_codec == CODEC_FLAC) || (m_codec == CODEC_OGG_FLAC)){ + m_validSamples = FLACGetOutputSamps() / getChannels(); + } + } + compute_audioCurrentTime(bytesDecoded); + + if(audio_process_extern){ + bool continueI2S = false; + audio_process_extern(m_outBuff, m_validSamples, &continueI2S); + if(!continueI2S){ + return bytesDecoded; + } + } + + while(m_validSamples) { + playChunk(); + } + return bytesDecoded; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::compute_audioCurrentTime(int bd) { + static uint16_t loop_counter = 0; + static int old_bitrate = 0; + static uint64_t sum_bitrate = 0; + static boolean f_CBR = true; // constant bitrate + + if(m_codec == CODEC_MP3) {setBitrate(MP3GetBitrate()) ;} // if not CBR, bitrate can be changed + if(m_codec == CODEC_M4A) {setBitrate(AACGetBitrate()) ;} // if not CBR, bitrate can be changed + if(m_codec == CODEC_AAC) {setBitrate(AACGetBitrate()) ;} // if not CBR, bitrate can be changed + if(m_codec == CODEC_FLAC){setBitrate(FLACGetBitRate());} // if not CBR, bitrate can be changed + if(!getBitRate()) return; + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_avr_bitrate == 0) { // first time + loop_counter = 0; + old_bitrate = 0; + sum_bitrate = 0; + f_CBR = true; + m_avr_bitrate = getBitRate(); + old_bitrate = getBitRate(); + } + if(!m_avr_bitrate) return; + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + if(loop_counter < 1000) loop_counter ++; + + if((old_bitrate != getBitRate()) && f_CBR) { + if(audio_info) audio_info("VBR recognized, audioFileDuration is estimated"); + f_CBR = false; // variable bitrate + } + old_bitrate = getBitRate(); + + if(!f_CBR) { + if(loop_counter > 20 && loop_counter < 200) { + // if VBR: m_avr_bitrate is average of the first values of m_bitrate + sum_bitrate += getBitRate(); + m_avr_bitrate = sum_bitrate / (loop_counter - 20); + } + } + else { + if(loop_counter == 2) m_avr_bitrate = getBitRate(); + } + m_audioCurrentTime += (float) bd * 8 / m_avr_bitrate; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::printDecodeError(int r) { + char* e = nullptr; + + if(m_codec == CODEC_MP3){ + switch(r){ + case ERR_MP3_NONE: e = strdup("NONE"); break; + case ERR_MP3_INDATA_UNDERFLOW: e = strdup("INDATA_UNDERFLOW"); break; + case ERR_MP3_MAINDATA_UNDERFLOW: e = strdup("MAINDATA_UNDERFLOW"); break; + case ERR_MP3_FREE_BITRATE_SYNC: e = strdup("FREE_BITRATE_SYNC"); break; + case ERR_MP3_OUT_OF_MEMORY: e = strdup("OUT_OF_MEMORY"); break; + case ERR_MP3_NULL_POINTER: e = strdup("NULL_POINTER"); break; + case ERR_MP3_INVALID_FRAMEHEADER: e = strdup("INVALID_FRAMEHEADER"); break; + case ERR_MP3_INVALID_SIDEINFO: e = strdup("INVALID_SIDEINFO"); break; + case ERR_MP3_INVALID_SCALEFACT: e = strdup("INVALID_SCALEFACT"); break; + case ERR_MP3_INVALID_HUFFCODES: e = strdup("INVALID_HUFFCODES"); break; + case ERR_MP3_INVALID_DEQUANTIZE: e = strdup("INVALID_DEQUANTIZE"); break; + case ERR_MP3_INVALID_IMDCT: e = strdup("INVALID_IMDCT"); break; + case ERR_MP3_INVALID_SUBBAND: e = strdup("INVALID_SUBBAND"); break; + default: e = strdup("ERR_UNKNOWN"); + } + sprintf(chbuf, "MP3 decode error %d : %s", r, e); + if(audio_info) audio_info(chbuf); + } + if(m_codec == CODEC_AAC){ + switch(r){ + case ERR_AAC_NONE: e = strdup("NONE"); break; + case ERR_AAC_INDATA_UNDERFLOW: e = strdup("INDATA_UNDERFLOW"); break; + case ERR_AAC_NULL_POINTER: e = strdup("NULL_POINTER"); break; + case ERR_AAC_INVALID_ADTS_HEADER: e = strdup("INVALID_ADTS_HEADER"); break; + case ERR_AAC_INVALID_ADIF_HEADER: e = strdup("INVALID_ADIF_HEADER"); break; + case ERR_AAC_INVALID_FRAME: e = strdup("INVALID_FRAME"); break; + case ERR_AAC_MPEG4_UNSUPPORTED: e = strdup("MPEG4_UNSUPPORTED"); break; + case ERR_AAC_CHANNEL_MAP: e = strdup("CHANNEL_MAP"); break; + case ERR_AAC_SYNTAX_ELEMENT: e = strdup("SYNTAX_ELEMENT"); break; + case ERR_AAC_DEQUANT: e = strdup("DEQUANT"); break; + case ERR_AAC_STEREO_PROCESS: e = strdup("STEREO_PROCESS"); break; + case ERR_AAC_PNS: e = strdup("PNS"); break; + case ERR_AAC_SHORT_BLOCK_DEINT: e = strdup("SHORT_BLOCK_DEINT"); break; + case ERR_AAC_TNS: e = strdup("TNS"); break; + case ERR_AAC_IMDCT: e = strdup("IMDCT"); break; + case ERR_AAC_SBR_INIT: e = strdup("SBR_INIT"); break; + case ERR_AAC_SBR_BITSTREAM: e = strdup("SBR_BITSTREAM"); break; + case ERR_AAC_SBR_DATA: e = strdup("SBR_DATA"); break; + case ERR_AAC_SBR_PCM_FORMAT: e = strdup("SBR_PCM_FORMAT"); break; + case ERR_AAC_SBR_NCHANS_TOO_HIGH: e = strdup("SBR_NCHANS_TOO_HIGH"); break; + case ERR_AAC_SBR_SINGLERATE_UNSUPPORTED: e = strdup("BR_SINGLERATE_UNSUPPORTED"); break; + case ERR_AAC_NCHANS_TOO_HIGH: e = strdup("NCHANS_TOO_HIGH"); break; + case ERR_AAC_RAWBLOCK_PARAMS: e = strdup("RAWBLOCK_PARAMS"); break; + default: e = strdup("ERR_UNKNOWN"); + } + sprintf(chbuf, "AAC decode error %d : %s", r, e); + if(audio_info) audio_info(chbuf); + } + if(m_codec == CODEC_FLAC){ + switch(r){ + case ERR_FLAC_NONE: e = strdup("NONE"); break; + case ERR_FLAC_BLOCKSIZE_TOO_BIG: e = strdup("BLOCKSIZE TOO BIG"); break; + case ERR_FLAC_RESERVED_BLOCKSIZE_UNSUPPORTED: e = strdup("Reserved Blocksize unsupported"); break; + case ERR_FLAC_SYNC_CODE_NOT_FOUND: e = strdup("SYNC CODE NOT FOUND"); break; + case ERR_FLAC_UNKNOWN_CHANNEL_ASSIGNMENT: e = strdup("UNKNOWN CHANNEL ASSIGNMENT"); break; + case ERR_FLAC_RESERVED_CHANNEL_ASSIGNMENT: e = strdup("RESERVED CHANNEL ASSIGNMENT"); break; + case ERR_FLAC_RESERVED_SUB_TYPE: e = strdup("RESERVED SUB TYPE"); break; + case ERR_FLAC_PREORDER_TOO_BIG: e = strdup("PREORDER TOO BIG"); break; + case ERR_FLAC_RESERVED_RESIDUAL_CODING: e = strdup("RESERVED RESIDUAL CODING"); break; + case ERR_FLAC_WRONG_RICE_PARTITION_NR: e = strdup("WRONG RICE PARTITION NR"); break; + case ERR_FLAC_BITS_PER_SAMPLE_TOO_BIG: e = strdup("BITS PER SAMPLE > 16"); break; + case ERR_FLAG_BITS_PER_SAMPLE_UNKNOWN: e = strdup("BITS PER SAMPLE UNKNOWN"); break; + default: e = strdup("ERR_UNKNOWN"); + } + sprintf(chbuf, "FLAC decode error %d : %s", r, e); + if(audio_info) audio_info(chbuf); + } + if(e) free(e); +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::setPinout(uint8_t BCLK, uint8_t LRC, uint8_t DOUT, int8_t DIN) { + + m_pin_config.bck_io_num = BCLK; + m_pin_config.ws_io_num = LRC; // wclk + m_pin_config.data_out_num = DOUT; + m_pin_config.data_in_num = DIN; + + const esp_err_t result = i2s_set_pin((i2s_port_t) m_i2s_num, &m_pin_config); + return (result == ESP_OK); +} +//--------------------------------------------------------------------------------------------------------------------- +uint32_t Audio::getFileSize() { + if(!audiofile) return 0; + return audiofile.size(); +} +//--------------------------------------------------------------------------------------------------------------------- +uint32_t Audio::getFilePos() { + if(!audiofile) return 0; + return audiofile.position(); +} +//--------------------------------------------------------------------------------------------------------------------- +uint32_t Audio::getAudioDataStartPos() { + if(!audiofile) return 0; + return m_audioDataStart; +} +//--------------------------------------------------------------------------------------------------------------------- +uint32_t Audio::getAudioFileDuration() { + if(m_f_localfile) {if(!audiofile) return 0;} + if(m_f_webfile) {if(!m_contentlength) return 0;} + + if (m_avr_bitrate && m_codec == CODEC_MP3) m_audioFileDuration = 8 * m_audioDataSize / m_avr_bitrate; + else if(m_avr_bitrate && m_codec == CODEC_WAV) m_audioFileDuration = 8 * m_audioDataSize / m_avr_bitrate; + else if(m_avr_bitrate && m_codec == CODEC_M4A) m_audioFileDuration = 8 * m_audioDataSize / m_avr_bitrate; + else if(m_avr_bitrate && m_codec == CODEC_AAC) m_audioFileDuration = 8 * m_audioDataSize / m_avr_bitrate; + else if( m_codec == CODEC_FLAC) m_audioFileDuration = FLACGetAudioFileDuration(); + else return 0; + return m_audioFileDuration; +} +//--------------------------------------------------------------------------------------------------------------------- +uint32_t Audio::getAudioCurrentTime() { // return current time in seconds + return (uint32_t) m_audioCurrentTime; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::setAudioPlayPosition(uint16_t sec){ + // Jump to an absolute position in time within an audio file + // e.g. setAudioPlayPosition(300) sets the pointer at pos 5 min + // works only with format mp3 or wav + if(m_codec == CODEC_M4A) return false; + if(sec > getAudioFileDuration()) sec = getAudioFileDuration(); + uint32_t filepos = m_audioDataStart + (m_avr_bitrate * sec / 8); + + return setFilePos(filepos); +} +//--------------------------------------------------------------------------------------------------------------------- +uint32_t Audio::getTotalPlayingTime() { + // Is set to zero by a connectToXXX() and starts as soon as the first audio data is available, + // the time counting is not interrupted by a 'pause / resume' and is not reset by a fileloop + return millis() - m_PlayingStartTime; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::setTimeOffset(int sec){ + // fast forward or rewind the current position in seconds + // audiosource must be a mp3, aac or wav file + + if(!audiofile || !m_avr_bitrate) return false; + + uint32_t oneSec = m_avr_bitrate / 8; // bytes decoded in one sec + int32_t offset = oneSec * sec; // bytes to be wind/rewind + uint32_t startAB = m_audioDataStart; // audioblock begin + uint32_t endAB = m_audioDataStart + m_audioDataSize; // audioblock end + + if(m_codec == CODEC_MP3 || m_codec == CODEC_AAC || m_codec == CODEC_WAV || m_codec == CODEC_FLAC){ + int32_t pos = getFilePos(); + pos += offset; + if(pos < (int32_t)startAB) pos = startAB; + if(pos >= (int32_t)endAB) pos = endAB; + setFilePos(pos); + return true; + } + return false; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::setFilePos(uint32_t pos) { + if(!audiofile) return false; +// if(!m_avr_bitrate) return false; + if(m_codec == CODEC_M4A) return false; + m_f_playing = false; + if(m_codec == CODEC_MP3) MP3Decoder_ClearBuffer(); + if(m_codec == CODEC_WAV) {while((pos % 4) != 0) pos++;} // must be divisible by four + if(m_codec == CODEC_FLAC) FLACDecoderReset(); + InBuff.resetBuffer(); + if(pos < m_audioDataStart) pos = m_audioDataStart; // issue #96 + if(m_avr_bitrate) m_audioCurrentTime = (pos-m_audioDataStart) * 8 / m_avr_bitrate; // #96 + return audiofile.seek(pos); +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::audioFileSeek(const float speed) { + // 0.5 is half speed + // 1.0 is normal speed + // 1.5 is one and half speed + if((speed > 1.5f) || (speed < 0.25f)) return false; + + uint32_t srate = getSampleRate() * speed; + i2s_set_sample_rates((i2s_port_t)m_i2s_num, srate); + return true; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::setSampleRate(uint32_t sampRate) { + if(!sampRate) sampRate = 16000; // fuse, if there is no value -> set default #209 + i2s_set_sample_rates((i2s_port_t)m_i2s_num, sampRate); + m_sampleRate = sampRate; + IIR_calculateCoefficients(m_gain0, m_gain1, m_gain2); // must be recalculated after each samplerate change + return true; +} +uint32_t Audio::getSampleRate(){ + return m_sampleRate; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::setBitsPerSample(int bits) { + if((bits != 16) && (bits != 8)) return false; + m_bitsPerSample = bits; + return true; +} +uint8_t Audio::getBitsPerSample(){ + return m_bitsPerSample; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::setChannels(int ch) { + if((ch < 1) || (ch > 2)) return false; + m_channels = ch; + return true; +} +uint8_t Audio::getChannels(){ + if (m_channels == 0) { // this should not happen! #209 + m_channels = 2; + } + return m_channels; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::setBitrate(int br){ + m_bitRate = br; + if(br)return true; + return false; +} +uint32_t Audio::getBitRate(){ + return m_bitRate; +} +//--------------------------------------------------------------------------------------------------------------------- +[[deprecated]]void Audio::setInternalDAC(bool internalDAC /* = true */, i2s_dac_mode_t channelEnabled /* = I2S_DAC_CHANNEL_LEFT_EN */ ) { +// is deprecated, set internal DAC in constructor e.g. Audio audio(true, I2S_DAC_CHANNEL_BOTH_EN); + m_f_channelEnabled = channelEnabled; + m_f_internalDAC = internalDAC; + i2s_driver_uninstall((i2s_port_t)m_i2s_num); + if (internalDAC) { + log_i("internal DAC"); + m_i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN ); + + #if ESP_ARDUINO_VERSION_MAJOR >= 2 + m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S); // vers >= 2.0.0 + #else + m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S_MSB); + #endif + + i2s_driver_install((i2s_port_t) m_i2s_num, &m_i2s_config, 0, NULL); + // enable the DAC channels + i2s_set_dac_mode(m_f_channelEnabled); + if(m_f_channelEnabled != I2S_DAC_CHANNEL_BOTH_EN) { + m_f_forceMono = true; + } + } + else { // external DAC + m_i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX); + + #if ESP_ARDUINO_VERSION_MAJOR >= 2 + m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S); // vers >= 2.0.0 + #else + m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB); + #endif + + i2s_driver_install ((i2s_port_t)m_i2s_num, &m_i2s_config, 0, NULL); + i2s_set_pin((i2s_port_t) m_i2s_num, &m_pin_config); + } + // clear the DMA buffers + i2s_zero_dma_buffer((i2s_port_t) m_i2s_num); +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::setI2SCommFMT_LSB(bool commFMT) { + // false: I2S communication format is by default I2S_COMM_FORMAT_I2S_MSB, right->left (AC101, PCM5102A) + // true: changed to I2S_COMM_FORMAT_I2S_LSB for some DACs (PT8211) + // Japanese or called LSBJ (Least Significant Bit Justified) format + + if (commFMT) { + log_i("commFMT LSB"); + + #if ESP_ARDUINO_VERSION_MAJOR >= 2 + m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_MSB); // v >= 2.0.0 + #else + m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_LSB); + #endif + + } + else { + log_i("commFMT MSB"); + + #if ESP_ARDUINO_VERSION_MAJOR >= 2 + m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S); // vers >= 2.0.0 + #else + m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB); + #endif + + } + log_i("commFMT = %i", m_i2s_config.communication_format); + i2s_driver_uninstall((i2s_port_t)m_i2s_num); + i2s_driver_install ((i2s_port_t)m_i2s_num, &m_i2s_config, 0, NULL); +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::playSample(int16_t sample[2]) { + + if (getBitsPerSample() == 8) { // Upsample from unsigned 8 bits to signed 16 bits + sample[LEFTCHANNEL] = ((sample[LEFTCHANNEL] & 0xff) -128) << 8; + sample[RIGHTCHANNEL] = ((sample[RIGHTCHANNEL] & 0xff) -128) << 8; + } + + sample[LEFTCHANNEL] = sample[LEFTCHANNEL] >> 1; // half Vin so we can boost up to 6dB in filters + sample[RIGHTCHANNEL] = sample[RIGHTCHANNEL] >> 1; + + // Filterchain, can commented out if not used + sample = IIR_filterChain0(sample); + sample = IIR_filterChain1(sample); + sample = IIR_filterChain2(sample); + //------------------------------------------- + + uint32_t s32 = Gain(sample); // vosample2lume; + + if(m_f_internalDAC) { + s32 += 0x80008000; + } + + esp_err_t err = i2s_write((i2s_port_t) m_i2s_num, (const char*) &s32, sizeof(uint32_t), &m_i2s_bytesWritten, 1000); + if(err != ESP_OK) { + log_e("ESP32 Errorcode %i", err); + return false; + } + if(m_i2s_bytesWritten < 4) { + log_e("Can't stuff any more in I2S..."); // increase waitingtime or outputbuffer + return false; + } + return true; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::setTone(int8_t gainLowPass, int8_t gainBandPass, int8_t gainHighPass){ + // see https://www.earlevel.com/main/2013/10/13/biquad-calculator-v2/ + // values can be between -40 ... +6 (dB) + + m_gain0 = gainLowPass; + m_gain1 = gainBandPass; + m_gain2 = gainHighPass; + + IIR_calculateCoefficients(m_gain0, m_gain1, m_gain2); + + /* + This will cause a clicking sound when adjusting the EQ. + Because when the EQ is adjusted, the IIR filter will be cleared and played, + mixed in the audio data frame, and a click-like sound will be produced. + */ + /* + int16_t tmp[2]; tmp[0] = 0; tmp[1]= 0; + + IIR_filterChain0(tmp, true ); // flush the filter + IIR_filterChain1(tmp, true ); // flush the filter + IIR_filterChain2(tmp, true ); // flush the filter + */ +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::forceMono(bool m) { // #100 mono option + m_f_forceMono = m; // false stereo, true mono +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::setBalance(int8_t bal){ // bal -16...16 + if(bal < -16) bal = -16; + if(bal > 16) bal = 16; + m_balance = bal; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::setVolume(uint8_t vol) { // vol 22 steps, 0...21 + //if(vol > 21) vol = 21; + //volume = map(eeprom_config.volume, 0, 21, 0, 255); + m_vol = map(vol, 0, 254, 0, 64); + //m_vol = volumetable[vol]; +} +//--------------------------------------------------------------------------------------------------------------------- +uint8_t Audio::getVolume() { + return map(m_vol, 0, 64, 0, 254); + for(uint8_t i = 0; i < 22; i++) { + if(volumetable[i] == m_vol) return i; + } + m_vol = 12; // if m_vol not found in table + return m_vol; +} +//--------------------------------------------------------------------------------------------------------------------- +int32_t Audio::Gain(int16_t s[2]) { + int32_t v[2]; + float step = (float)m_vol /64; + uint8_t l = 0, r = 0; + + if(m_balance < 0){ + step = step * (float)(abs(m_balance) * 4); + l = (uint8_t)(step); + } + if(m_balance > 0){ + step = step * m_balance * 4; + r = (uint8_t)(step); + } + + v[LEFTCHANNEL] = (s[LEFTCHANNEL] * (m_vol - l)) >> 6; + v[RIGHTCHANNEL]= (s[RIGHTCHANNEL] * (m_vol - r)) >> 6; + + return (v[RIGHTCHANNEL] << 16) | (v[LEFTCHANNEL] & 0xffff); +} +//--------------------------------------------------------------------------------------------------------------------- +uint32_t Audio::inBufferFilled() { + // current audio input buffer fillsize in bytes + return InBuff.bufferFilled(); +} +//--------------------------------------------------------------------------------------------------------------------- +uint32_t Audio::inBufferFree() { + // current audio input buffer free space in bytes + return InBuff.freeSpace(); +} +//--------------------------------------------------------------------------------------------------------------------- +// *** D i g i t a l b i q u a d r a t i c f i l t e r *** +//--------------------------------------------------------------------------------------------------------------------- +void Audio::IIR_calculateCoefficients(int8_t G0, int8_t G1, int8_t G2){ // Infinite Impulse Response (IIR) filters + + // G1 - gain low shelf set between -40 ... +6 dB + // G2 - gain peakEQ set between -40 ... +6 dB + // G3 - gain high shelf set between -40 ... +6 dB + // https://www.earlevel.com/main/2012/11/26/biquad-c-source-code/ + + if(getSampleRate() < 1000) return; // fuse + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*if(G0 < -40) G0 = -40; // -40dB -> Vin*0.01 + if(G0 > 6) G0 = 6; // +6dB -> Vin*2 + if(G1 < -40) G1 = -40; + if(G1 > 6) G1 = 6; + if(G2 < -40) G2 = -40; + if(G2 > 6) G2 = 6;*/ + if(G0 < -40) G0 = -40; // -40dB -> Vin*0.01 + if(G0 > 16) G0 = 16; // +6dB -> Vin*2 + if(G1 < -40) G1 = -40; + if(G1 > 16) G1 = 16; + if(G2 < -40) G2 = -40; + if(G2 > 16) G2 = 16; + /*const float FcLS = 500; // Frequency LowShelf[Hz] + const float FcPKEQ = 3000; // Frequency PeakEQ[Hz] + const float FcHS = 6000; // Frequency HighShelf[Hz]*/ + const float FcLS = 500; // Frequency LowShelf[Hz] + const float FcPKEQ = 3000; // Frequency PeakEQ[Hz] + const float FcHS = 6000; // Frequency HighShelf[Hz] + + float K, norm, Q, Fc, V ; + + // LOWSHELF + Fc = (float)FcLS / (float)getSampleRate(); // Cutoff frequency + K = tanf((float)PI * Fc); + V = powf(10, fabs(G0) / 20.0); + + if (G0 >= 0) { // boost + norm = 1 / (1 + sqrtf(2) * K + K * K); + m_filter[LOWSHELF].a0 = (1 + sqrtf(2*V) * K + V * K * K) * norm; + m_filter[LOWSHELF].a1 = 2 * (V * K * K - 1) * norm; + m_filter[LOWSHELF].a2 = (1 - sqrtf(2*V) * K + V * K * K) * norm; + m_filter[LOWSHELF].b1 = 2 * (K * K - 1) * norm; + m_filter[LOWSHELF].b2 = (1 - sqrtf(2) * K + K * K) * norm; + } + else { // cut + norm = 1 / (1 + sqrtf(2*V) * K + V * K * K); + m_filter[LOWSHELF].a0 = (1 + sqrtf(2) * K + K * K) * norm; + m_filter[LOWSHELF].a1 = 2 * (K * K - 1) * norm; + m_filter[LOWSHELF].a2 = (1 - sqrtf(2) * K + K * K) * norm; + m_filter[LOWSHELF].b1 = 2 * (V * K * K - 1) * norm; + m_filter[LOWSHELF].b2 = (1 - sqrtf(2*V) * K + V * K * K) * norm; + } + + // PEAK EQ + Fc = (float)FcPKEQ / (float)getSampleRate(); // Cutoff frequency + K = tanf((float)PI * Fc); + V = powf(10, fabs(G1) / 20.0); + Q = 2.5; // Quality factor + if (G1 >= 0) { // boost + norm = 1 / (1 + 1/Q * K + K * K); + m_filter[PEAKEQ].a0 = (1 + V/Q * K + K * K) * norm; + m_filter[PEAKEQ].a1 = 2 * (K * K - 1) * norm; + m_filter[PEAKEQ].a2 = (1 - V/Q * K + K * K) * norm; + m_filter[PEAKEQ].b1 = m_filter[PEAKEQ].a1; + m_filter[PEAKEQ].b2 = (1 - 1/Q * K + K * K) * norm; + } + else { // cut + norm = 1 / (1 + V/Q * K + K * K); + m_filter[PEAKEQ].a0 = (1 + 1/Q * K + K * K) * norm; + m_filter[PEAKEQ].a1 = 2 * (K * K - 1) * norm; + m_filter[PEAKEQ].a2 = (1 - 1/Q * K + K * K) * norm; + m_filter[PEAKEQ].b1 = m_filter[PEAKEQ].a1; + m_filter[PEAKEQ].b2 = (1 - V/Q * K + K * K) * norm; + } + + // HIGHSHELF + Fc = (float)FcHS / (float)getSampleRate(); // Cutoff frequency + K = tanf((float)PI * Fc); + V = powf(10, fabs(G2) / 20.0); + if (G2 >= 0) { // boost + norm = 1 / (1 + sqrtf(2) * K + K * K); + m_filter[HIFGSHELF].a0 = (V + sqrtf(2*V) * K + K * K) * norm; + m_filter[HIFGSHELF].a1 = 2 * (K * K - V) * norm; + m_filter[HIFGSHELF].a2 = (V - sqrtf(2*V) * K + K * K) * norm; + m_filter[HIFGSHELF].b1 = 2 * (K * K - 1) * norm; + m_filter[HIFGSHELF].b2 = (1 - sqrtf(2) * K + K * K) * norm; + } + else { + norm = 1 / (V + sqrtf(2*V) * K + K * K); + m_filter[HIFGSHELF].a0 = (1 + sqrtf(2) * K + K * K) * norm; + m_filter[HIFGSHELF].a1 = 2 * (K * K - 1) * norm; + m_filter[HIFGSHELF].a2 = (1 - sqrtf(2) * K + K * K) * norm; + m_filter[HIFGSHELF].b1 = 2 * (K * K - V) * norm; + m_filter[HIFGSHELF].b2 = (V - sqrtf(2*V) * K + K * K) * norm; + } + +// log_i("LS a0=%f, a1=%f, a2=%f, b1=%f, b2=%f", m_filter[0].a0, m_filter[0].a1, m_filter[0].a2, +// m_filter[0].b1, m_filter[0].b2); +// log_i("EQ a0=%f, a1=%f, a2=%f, b1=%f, b2=%f", m_filter[1].a0, m_filter[1].a1, m_filter[1].a2, +// m_filter[1].b1, m_filter[1].b2); +// log_i("HS a0=%f, a1=%f, a2=%f, b1=%f, b2=%f", m_filter[2].a0, m_filter[2].a1, m_filter[2].a2, +// m_filter[2].b1, m_filter[2].b2); +} +//--------------------------------------------------------------------------------------------------------------------- +int16_t* Audio::IIR_filterChain0(int16_t iir_in[2], bool clear){ // Infinite Impulse Response (IIR) filters + + uint8_t z1 = 0, z2 = 1; + enum: uint8_t {in = 0, out = 1}; + float inSample[2]; + float outSample[2]; + static int16_t iir_out[2]; + + if(clear){ + memset(m_filterBuff, 0, sizeof(m_filterBuff)); // zero IIR filterbuffer + iir_out[0] = 0; + iir_out[1] = 0; + iir_in[0] = 0; + iir_in[1] = 0; + } + + inSample[LEFTCHANNEL] = (float)(iir_in[LEFTCHANNEL]); + inSample[RIGHTCHANNEL] = (float)(iir_in[RIGHTCHANNEL]); + + outSample[LEFTCHANNEL] = m_filter[0].a0 * inSample[LEFTCHANNEL] + + m_filter[0].a1 * m_filterBuff[0][z1][in] [LEFTCHANNEL] + + m_filter[0].a2 * m_filterBuff[0][z2][in] [LEFTCHANNEL] + - m_filter[0].b1 * m_filterBuff[0][z1][out][LEFTCHANNEL] + - m_filter[0].b2 * m_filterBuff[0][z2][out][LEFTCHANNEL]; + + m_filterBuff[0][z2][in] [LEFTCHANNEL] = m_filterBuff[0][z1][in][LEFTCHANNEL]; + m_filterBuff[0][z1][in] [LEFTCHANNEL] = inSample[LEFTCHANNEL]; + m_filterBuff[0][z2][out][LEFTCHANNEL] = m_filterBuff[0][z1][out][LEFTCHANNEL]; + m_filterBuff[0][z1][out][LEFTCHANNEL] = outSample[LEFTCHANNEL]; + iir_out[LEFTCHANNEL] = (int16_t)outSample[LEFTCHANNEL]; + + + outSample[RIGHTCHANNEL] = m_filter[0].a0 * inSample[RIGHTCHANNEL] + + m_filter[0].a1 * m_filterBuff[0][z1][in] [RIGHTCHANNEL] + + m_filter[0].a2 * m_filterBuff[0][z2][in] [RIGHTCHANNEL] + - m_filter[0].b1 * m_filterBuff[0][z1][out][RIGHTCHANNEL] + - m_filter[0].b2 * m_filterBuff[0][z2][out][RIGHTCHANNEL]; + + m_filterBuff[0][z2][in] [RIGHTCHANNEL] = m_filterBuff[0][z1][in][RIGHTCHANNEL]; + m_filterBuff[0][z1][in] [RIGHTCHANNEL] = inSample[RIGHTCHANNEL]; + m_filterBuff[0][z2][out][RIGHTCHANNEL] = m_filterBuff[0][z1][out][RIGHTCHANNEL]; + m_filterBuff[0][z1][out][RIGHTCHANNEL] = outSample[RIGHTCHANNEL]; + iir_out[RIGHTCHANNEL] = (int16_t) outSample[RIGHTCHANNEL]; + + return iir_out; +} +//--------------------------------------------------------------------------------------------------------------------- +int16_t* Audio::IIR_filterChain1(int16_t iir_in[2], bool clear){ // Infinite Impulse Response (IIR) filters + + uint8_t z1 = 0, z2 = 1; + enum: uint8_t {in = 0, out = 1}; + float inSample[2]; + float outSample[2]; + static int16_t iir_out[2]; + + if(clear){ + memset(m_filterBuff, 0, sizeof(m_filterBuff)); // zero IIR filterbuffer + iir_out[0] = 0; + iir_out[1] = 0; + iir_in[0] = 0; + iir_in[1] = 0; + } + + inSample[LEFTCHANNEL] = (float)(iir_in[LEFTCHANNEL]); + inSample[RIGHTCHANNEL] = (float)(iir_in[RIGHTCHANNEL]); + + outSample[LEFTCHANNEL] = m_filter[1].a0 * inSample[LEFTCHANNEL] + + m_filter[1].a1 * m_filterBuff[1][z1][in] [LEFTCHANNEL] + + m_filter[1].a2 * m_filterBuff[1][z2][in] [LEFTCHANNEL] + - m_filter[1].b1 * m_filterBuff[1][z1][out][LEFTCHANNEL] + - m_filter[1].b2 * m_filterBuff[1][z2][out][LEFTCHANNEL]; + + m_filterBuff[1][z2][in] [LEFTCHANNEL] = m_filterBuff[1][z1][in][LEFTCHANNEL]; + m_filterBuff[1][z1][in] [LEFTCHANNEL] = inSample[LEFTCHANNEL]; + m_filterBuff[1][z2][out][LEFTCHANNEL] = m_filterBuff[1][z1][out][LEFTCHANNEL]; + m_filterBuff[1][z1][out][LEFTCHANNEL] = outSample[LEFTCHANNEL]; + iir_out[LEFTCHANNEL] = (int16_t)outSample[LEFTCHANNEL]; + + + outSample[RIGHTCHANNEL] = m_filter[1].a0 * inSample[RIGHTCHANNEL] + + m_filter[1].a1 * m_filterBuff[1][z1][in] [RIGHTCHANNEL] + + m_filter[1].a2 * m_filterBuff[1][z2][in] [RIGHTCHANNEL] + - m_filter[1].b1 * m_filterBuff[1][z1][out][RIGHTCHANNEL] + - m_filter[1].b2 * m_filterBuff[1][z2][out][RIGHTCHANNEL]; + + m_filterBuff[1][z2][in] [RIGHTCHANNEL] = m_filterBuff[1][z1][in][RIGHTCHANNEL]; + m_filterBuff[1][z1][in] [RIGHTCHANNEL] = inSample[RIGHTCHANNEL]; + m_filterBuff[1][z2][out][RIGHTCHANNEL] = m_filterBuff[1][z1][out][RIGHTCHANNEL]; + m_filterBuff[1][z1][out][RIGHTCHANNEL] = outSample[RIGHTCHANNEL]; + iir_out[RIGHTCHANNEL] = (int16_t) outSample[RIGHTCHANNEL]; + + return iir_out; +} +//--------------------------------------------------------------------------------------------------------------------- +int16_t* Audio::IIR_filterChain2(int16_t iir_in[2], bool clear){ // Infinite Impulse Response (IIR) filters + + uint8_t z1 = 0, z2 = 1; + enum: uint8_t {in = 0, out = 1}; + float inSample[2]; + float outSample[2]; + static int16_t iir_out[2]; + + if(clear){ + memset(m_filterBuff, 0, sizeof(m_filterBuff)); // zero IIR filterbuffer + iir_out[0] = 0; + iir_out[1] = 0; + iir_in[0] = 0; + iir_in[1] = 0; + } + + inSample[LEFTCHANNEL] = (float)(iir_in[LEFTCHANNEL]); + inSample[RIGHTCHANNEL] = (float)(iir_in[RIGHTCHANNEL]); + + outSample[LEFTCHANNEL] = m_filter[2].a0 * inSample[LEFTCHANNEL] + + m_filter[2].a1 * m_filterBuff[2][z1][in] [LEFTCHANNEL] + + m_filter[2].a2 * m_filterBuff[2][z2][in] [LEFTCHANNEL] + - m_filter[2].b1 * m_filterBuff[2][z1][out][LEFTCHANNEL] + - m_filter[2].b2 * m_filterBuff[2][z2][out][LEFTCHANNEL]; + + m_filterBuff[2][z2][in] [LEFTCHANNEL] = m_filterBuff[2][z1][in][LEFTCHANNEL]; + m_filterBuff[2][z1][in] [LEFTCHANNEL] = inSample[LEFTCHANNEL]; + m_filterBuff[2][z2][out][LEFTCHANNEL] = m_filterBuff[2][z1][out][LEFTCHANNEL]; + m_filterBuff[2][z1][out][LEFTCHANNEL] = outSample[LEFTCHANNEL]; + iir_out[LEFTCHANNEL] = (int16_t)outSample[LEFTCHANNEL]; + + + outSample[RIGHTCHANNEL] = m_filter[2].a0 * inSample[RIGHTCHANNEL] + + m_filter[2].a1 * m_filterBuff[2][z1][in] [RIGHTCHANNEL] + + m_filter[2].a2 * m_filterBuff[2][z2][in] [RIGHTCHANNEL] + - m_filter[2].b1 * m_filterBuff[2][z1][out][RIGHTCHANNEL] + - m_filter[2].b2 * m_filterBuff[2][z2][out][RIGHTCHANNEL]; + + m_filterBuff[2][z2][in] [RIGHTCHANNEL] = m_filterBuff[2][z1][in][RIGHTCHANNEL]; + m_filterBuff[2][z1][in] [RIGHTCHANNEL] = inSample[RIGHTCHANNEL]; + m_filterBuff[2][z2][out][RIGHTCHANNEL] = m_filterBuff[2][z1][out][RIGHTCHANNEL]; + m_filterBuff[2][z1][out][RIGHTCHANNEL] = outSample[RIGHTCHANNEL]; + iir_out[RIGHTCHANNEL] = (int16_t) outSample[RIGHTCHANNEL]; + + return iir_out; +} diff --git a/yoRadio/src/audioI2S/AudioEx.h b/yoRadio/src/audioI2S/AudioEx.h new file mode 100644 index 0000000..4ebaa56 --- /dev/null +++ b/yoRadio/src/audioI2S/AudioEx.h @@ -0,0 +1,480 @@ +/* + * Audio.h + * + * Created on: Oct 26,2018 + * Updated on: Jan 05,2022 + * Author: Wolle (schreibfaul1) + */ + +//#define SDFATFS_USED // activate for SdFat + + +#pragma once +#pragma GCC optimize ("Ofast") + +#include +#include +#include +#include +#include + +#include + +#ifdef SDFATFS_USED +#include // https://github.com/greiman/SdFat +#else +#include +#include +#include +#include +#include +#endif // SDFATFS_USED + +#define AUDIOBUFFER_MULTIPLIER 13 + +#ifdef SDFATFS_USED +typedef File32 File; + +namespace fs { + class FS : public SdFat { + public: + bool begin(SdCsPin_t csPin = SS, uint32_t maxSck = SD_SCK_MHZ(25)) { return SdFat::begin(csPin, maxSck); } + }; + + class SDFATFS : public fs::FS { + public: + // sdcard_type_t cardType(); + uint64_t cardSize() { + return totalBytes(); + } + uint64_t usedBytes() { + // set SdFatConfig MAINTAIN_FREE_CLUSTER_COUNT non-zero. Then only the first call will take time. + return (uint64_t)(clusterCount() - freeClusterCount()) * (uint64_t)bytesPerCluster(); + } + uint64_t totalBytes() { + return (uint64_t)clusterCount() * (uint64_t)bytesPerCluster(); + } + }; +} + +extern fs::SDFATFS SD_SDFAT; + +using namespace fs; +#define SD SD_SDFAT +#endif //SDFATFS_USED + + + + +extern __attribute__((weak)) void audio_info(const char*); +extern __attribute__((weak)) void audio_id3data(const char*); //ID3 metadata +extern __attribute__((weak)) void audio_id3image(File& file, const size_t pos, const size_t size); //ID3 metadata image +extern __attribute__((weak)) void audio_eof_mp3(const char*); //end of mp3 file +extern __attribute__((weak)) void audio_showstreamtitle(const char*); +extern __attribute__((weak)) void audio_showstation(const char*); +extern __attribute__((weak)) void audio_bitrate(const char*); +extern __attribute__((weak)) void audio_commercial(const char*); +extern __attribute__((weak)) void audio_icyurl(const char*); +extern __attribute__((weak)) void audio_icydescription(const char*); +extern __attribute__((weak)) void audio_lasthost(const char*); +extern __attribute__((weak)) void audio_eof_speech(const char*); +extern __attribute__((weak)) void audio_eof_stream(const char*); // The webstream comes to an end +extern __attribute__((weak)) void audio_process_extern(int16_t* buff, uint16_t len, bool *continueI2S); // record audiodata or send via BT + +//---------------------------------------------------------------------------------------------------------------------- + +class AudioBuffer { +// AudioBuffer will be allocated in PSRAM, If PSRAM not available or has not enough space AudioBuffer will be +// allocated in FlashRAM with reduced size +// +// m_buffer m_readPtr m_writePtr m_endPtr +// | |<------dataLength------->|<------ writeSpace ----->| +// ▼ ▼ ▼ ▼ +// --------------------------------------------------------------------------------------------------------------- +// | <--m_buffSize--> | <--m_resBuffSize --> | +// --------------------------------------------------------------------------------------------------------------- +// |<-----freeSpace------->| |<------freeSpace-------->| +// +// +// +// if the space between m_readPtr and buffend < m_resBuffSize copy data from the beginning to resBuff +// so that the mp3/aac/flac frame is always completed +// +// m_buffer m_writePtr m_readPtr m_endPtr +// | |<-------writeSpace------>|<--dataLength-->| +// ▼ ▼ ▼ ▼ +// --------------------------------------------------------------------------------------------------------------- +// | <--m_buffSize--> | <--m_resBuffSize --> | +// --------------------------------------------------------------------------------------------------------------- +// |<--- ------dataLength-- ------>|<-------freeSpace------->| +// +// + +public: + AudioBuffer(size_t maxBlockSize = 0); // constructor + ~AudioBuffer(); // frees the buffer + size_t init(); // set default values + void changeMaxBlockSize(uint16_t mbs); // is default 1600 for mp3 and aac, set 16384 for FLAC + uint16_t getMaxBlockSize(); // returns maxBlockSize + size_t freeSpace(); // number of free bytes to overwrite + size_t writeSpace(); // space fom writepointer to bufferend + size_t bufferFilled(); // returns the number of filled bytes + void bytesWritten(size_t bw); // update writepointer + void bytesWasRead(size_t br); // update readpointer + uint8_t* getWritePtr(); // returns the current writepointer + uint8_t* getReadPtr(); // returns the current readpointer + uint32_t getWritePos(); // write position relative to the beginning + uint32_t getReadPos(); // read position relative to the beginning + void resetBuffer(); // restore defaults + +protected: + const size_t m_buffSizePSRAM = 300000; // most webstreams limit the advance to 100...300Kbytes + //const size_t m_buffSizeRAM = 1600 * 5 * AUDIOBUFFER_MULTIPLIER; + const size_t m_buffSizeRAM = 1600 * AUDIOBUFFER_MULTIPLIER; + size_t m_buffSize = 0; + size_t m_freeSpace = 0; + size_t m_writeSpace = 0; + size_t m_dataLength = 0; + //size_t m_resBuffSizeRAM = 1600 * AUDIOBUFFER_MULTIPLIER; // reserved buffspace, >= one mp3 frame + size_t m_resBuffSizeRAM = 1600; // reserved buffspace, >= one mp3 frame + size_t m_resBuffSizePSRAM = 4096 * 4; // reserved buffspace, >= one flac frame + size_t m_maxBlockSize = 1600; + uint8_t* m_buffer = NULL; + uint8_t* m_writePtr = NULL; + uint8_t* m_readPtr = NULL; + uint8_t* m_endPtr = NULL; + bool m_f_start = true; +}; +//---------------------------------------------------------------------------------------------------------------------- + +class Audio : private AudioBuffer{ + + AudioBuffer InBuff; // instance of input buffer + +public: + Audio(bool internalDAC = false, i2s_dac_mode_t channelEnabled = I2S_DAC_CHANNEL_LEFT_EN); // #99 + ~Audio(); + bool connecttohost(const char* host, const char* user = "", const char* pwd = ""); + bool connecttospeech(const char* speech, const char* lang); + bool connecttoFS(fs::FS &fs, const char* path); + bool connecttoSD(const char* path); + bool setFileLoop(bool input);//TEST loop + bool setAudioPlayPosition(uint16_t sec); + bool setFilePos(uint32_t pos); + bool audioFileSeek(const float speed); + bool setTimeOffset(int sec); + bool setPinout(uint8_t BCLK, uint8_t LRC, uint8_t DOUT, int8_t DIN=I2S_PIN_NO_CHANGE); + bool pauseResume(); + bool isRunning() {return m_f_running;} + void loop(); + void stopSong(); + void forceMono(bool m); + void setBalance(int8_t bal = 0); + void setVolume(uint8_t vol); + uint8_t getVolume(); + + uint32_t getAudioDataStartPos(); + uint32_t getFileSize(); + uint32_t getFilePos(); + uint32_t getSampleRate(); + uint8_t getBitsPerSample(); + uint8_t getChannels(); + uint32_t getBitRate(); + uint32_t getAudioFileDuration(); + uint32_t getAudioCurrentTime(); + uint32_t getTotalPlayingTime(); + + esp_err_t i2s_mclk_pin_select(const uint8_t pin); + uint32_t inBufferFilled(); // returns the number of stored bytes in the inputbuffer + uint32_t inBufferFree(); // returns the number of free bytes in the inputbuffer + void setTone(int8_t gainLowPass, int8_t gainBandPass, int8_t gainHighPass); + [[deprecated]]void setInternalDAC(bool internalDAC = true, i2s_dac_mode_t channelEnabled = I2S_DAC_CHANNEL_LEFT_EN); + void setI2SCommFMT_LSB(bool commFMT); + +private: + void UTF8toASCII(char* str); + bool latinToUTF8(char* buff, size_t bufflen); + void httpPrint(const char* url); + void setDefaults(); // free buffers and set defaults + void initInBuff(); + void processLocalFile(); + void processWebStream(); + void processPlayListData(); + void processM3U8entries(uint8_t nrOfEntries = 0, uint32_t seqNr = 0, uint8_t pos = 0, uint16_t targetDuration = 0); + bool STfromEXTINF(char* str); + void showCodecParams(); + int findNextSync(uint8_t* data, size_t len); + int sendBytes(uint8_t* data, size_t len); + void compute_audioCurrentTime(int bd); + void printDecodeError(int r); + void showID3Tag(const char* tag, const char* val); + void unicode2utf8(char* buff, uint32_t len); + int read_WAV_Header(uint8_t* data, size_t len); + int read_FLAC_Header(uint8_t *data, size_t len); + int read_MP3_Header(uint8_t* data, size_t len); + int read_M4A_Header(uint8_t* data, size_t len); + int read_OGG_Header(uint8_t *data, size_t len); + bool setSampleRate(uint32_t hz); + bool setBitsPerSample(int bits); + bool setChannels(int channels); + bool setBitrate(int br); + bool playChunk(); + bool playSample(int16_t sample[2]) ; + bool playI2Sremains(); + int32_t Gain(int16_t s[2]); + bool fill_InputBuf(); + void showstreamtitle(const char* ml); + bool parseContentType(const char* ct); + void processAudioHeaderData(); + bool readMetadata(uint8_t b, bool first = false); + esp_err_t I2Sstart(uint8_t i2s_num); + esp_err_t I2Sstop(uint8_t i2s_num); + void urlencode(char* buff, uint16_t buffLen, bool spacesOnly = false); + int16_t* IIR_filterChain0(int16_t iir_in[2], bool clear = false); + int16_t* IIR_filterChain1(int16_t* iir_in, bool clear = false); + int16_t* IIR_filterChain2(int16_t* iir_in, bool clear = false); + inline void setDatamode(uint8_t dm){m_datamode=dm;} + inline uint8_t getDatamode(){return m_datamode;} + inline uint32_t streamavail() {if(m_f_ssl==false) return client.available(); else return clientsecure.available();} + void IIR_calculateCoefficients(int8_t G1, int8_t G2, int8_t G3); + + // implement several function with respect to the index of string + void trim(char *s) { + //fb trim in place + char *pe; + char *p = s; + while ( isspace(*p) ) p++; //left + pe = p; //right + while ( *pe != '\0' ) pe++; + do { + pe--; + } while ( (pe > p) && isspace(*pe) ); + if (p == s) { + *++pe = '\0'; + } else { //move + while ( p <= pe ) *s++ = *p++; + *s = '\0'; + } + } + + bool startsWith (const char* base, const char* str) { + //fb + char c; + while ( (c = *str++) != '\0' ) + if (c != *base++) return false; + return true; + } + + bool endsWith (const char* base, const char* str) { + //fb + int slen = strlen(str) - 1; + const char *p = base + strlen(base) - 1; + while(p > base && isspace(*p)) p--; // rtrim + p -= slen; + if (p < base) return false; + return (strncmp(p, str, slen) == 0); + } + + int indexOf (const char* base, const char* str, int startIndex) { + //fb + const char *p = base; + for (; startIndex > 0; startIndex--) + if (*p++ == '\0') return -1; + char* pos = strstr(p, str); + if (pos == nullptr) return -1; + return pos - base; + } + + int indexOf (const char* base, char ch, int startIndex) { + //fb + const char *p = base; + for (; startIndex > 0; startIndex--) + if (*p++ == '\0') return -1; + char *pos = strchr(p, ch); + if (pos == nullptr) return -1; + return pos - base; + } + + int lastIndexOf(const char* haystack, const char* needle) { + //fb + int nlen = strlen(needle); + if (nlen == 0) return -1; + const char *p = haystack - nlen + strlen(haystack); + while (p >= haystack) { + int i = 0; + while (needle[i] == p[i]) + if (++i == nlen) return p - haystack; + p--; + } + return -1; + } + + int lastIndexOf(const char* haystack, const char needle) { + //fb + const char *p = strrchr(haystack, needle); + return (p ? p - haystack : -1); + } + + int specialIndexOf (uint8_t* base, const char* str, int baselen, bool exact = false){ + int result; // seek for str in buffer or in header up to baselen, not nullterninated + if (strlen(str) > baselen) return -1; // if exact == true seekstr in buffer must have "\0" at the end + for (int i = 0; i < baselen - strlen(str); i++){ + result = i; + for (int j = 0; j < strlen(str) + exact; j++){ + if (*(base + i + j) != *(str + j)){ + result = -1; + break; + } + } + if (result >= 0) break; + } + return result; + } + size_t bigEndian(uint8_t* base, uint8_t numBytes, uint8_t shiftLeft = 8){ + size_t result = 0; + if(numBytes < 1 or numBytes > 4) return 0; + for (int i = 0; i < numBytes; i++) { + result += *(base + i) << (numBytes -i - 1) * shiftLeft; + } + return result; + } + bool b64encode(const char* source, uint16_t sourceLength, char* dest){ + size_t size = base64_encode_expected_len(sourceLength) + 1; + char * buffer = (char *) malloc(size); + if(buffer) { + base64_encodestate _state; + base64_init_encodestate(&_state); + int len = base64_encode_block(&source[0], sourceLength, &buffer[0], &_state); + len = base64_encode_blockend((buffer + len), &_state); + memcpy(dest, buffer, strlen(buffer)); + dest[strlen(buffer)] = '\0'; + free(buffer); + return true; + } + return false; + } + size_t urlencode_expected_len(const char* source){ + size_t expectedLen = strlen(source); + for(int i = 0; i < strlen(source); i++) { + if(isalnum(source[i])){;} + else expectedLen += 2; + } + return expectedLen; + } + + +private: + enum : int { APLL_AUTO = -1, APLL_ENABLE = 1, APLL_DISABLE = 0 }; + enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 }; + enum : int { CODEC_NONE, CODEC_WAV, CODEC_MP3, CODEC_AAC, CODEC_M4A, CODEC_FLAC, CODEC_OGG, + CODEC_OGG_FLAC, CODEC_OGG_OPUS}; + enum : int { FORMAT_NONE = 0, FORMAT_M3U = 1, FORMAT_PLS = 2, FORMAT_ASX = 3, FORMAT_M3U8 = 4}; + enum : int { AUDIO_NONE, AUDIO_HEADER, AUDIO_DATA, + AUDIO_PLAYLISTINIT, AUDIO_PLAYLISTHEADER, AUDIO_PLAYLISTDATA}; + enum : int { FLAC_BEGIN = 0, FLAC_MAGIC = 1, FLAC_MBH =2, FLAC_SINFO = 3, FLAC_PADDING = 4, FLAC_APP = 5, + FLAC_SEEK = 6, FLAC_VORBIS = 7, FLAC_CUESHEET = 8, FLAC_PICTURE = 9, FLAC_OKAY = 100}; + enum : int { M4A_BEGIN = 0, M4A_FTYP = 1, M4A_CHK = 2, M4A_MOOV = 3, M4A_FREE = 4, M4A_TRAK = 5, M4A_MDAT = 6, + M4A_ILST = 7, M4A_MP4A = 8, M4A_AMRDY = 99, M4A_OKAY = 100}; + enum : int { OGG_BEGIN = 0, OGG_MAGIC = 1, OGG_HEADER = 2, OGG_FIRST = 3, OGG_AMRDY = 99, OGG_OKAY = 100}; + typedef enum { LEFTCHANNEL=0, RIGHTCHANNEL=1 } SampleIndex; + typedef enum { LOWSHELF = 0, PEAKEQ = 1, HIFGSHELF =2 } FilterType; + + const uint8_t volumetable[22]={ 0, 1, 2, 3, 4 , 6 , 8, 10, 12, 14, 17, + 20, 23, 27, 30 ,34, 38, 43 ,48, 52, 58, 64}; //22 elements + + typedef struct _filter{ + float a0; + float a1; + float a2; + float b1; + float b2; + } filter_t; + + File audiofile; // @suppress("Abstract class cannot be instantiated") + WiFiClient client; // @suppress("Abstract class cannot be instantiated") + WiFiClientSecure clientsecure; // @suppress("Abstract class cannot be instantiated") + WiFiUDP udpclient; // @suppress("Abstract class cannot be instantiated") + i2s_config_t m_i2s_config; // stores values for I2S driver + i2s_pin_config_t m_pin_config; + + const size_t m_frameSizeWav = 1600; + const size_t m_frameSizeMP3 = 1600; + const size_t m_frameSizeAAC = 1600; + const size_t m_frameSizeFLAC = 4096 * 4; + + char chbuf[512 + 128]; // must be greater than m_lastHost #254 + char m_lastHost[512]; // Store the last URL to a webstream + char* m_playlistBuff = NULL; // stores playlistdata + const uint16_t m_plsBuffEntryLen = 256; // length of each entry in playlistBuff + filter_t m_filter[3]; // digital filters + int m_LFcount = 0; // Detection of end of header + uint32_t m_sampleRate=16000; + uint32_t m_bitRate=0; // current bitrate given fom decoder + uint32_t m_avr_bitrate = 0; // average bitrate, median computed by VBR + int m_readbytes=0; // bytes read + int m_metalen=0; // Number of bytes in metadata + int m_controlCounter = 0; // Status within readID3data() and readWaveHeader() + int8_t m_balance = 0; // -16 (mute left) ... +16 (mute right) + uint8_t m_vol=64; // volume + uint8_t m_bitsPerSample = 16; // bitsPerSample + uint8_t m_channels=2; + uint8_t m_i2s_num = I2S_NUM_0; // I2S_NUM_0 or I2S_NUM_1 + uint8_t m_playlistFormat = 0; // M3U, PLS, ASX + uint8_t m_m3u8codec = CODEC_NONE; // M4A + uint8_t m_codec = CODEC_NONE; // + uint8_t m_filterType[2]; // lowpass, highpass + int16_t m_outBuff[2048*2]; // Interleaved L/R + int16_t m_validSamples = 0; + int16_t m_curSample = 0; + uint16_t m_st_remember = 0; // Save hash from the last streamtitle + uint16_t m_datamode = 0; // Statemaschine + uint8_t m_flacBitsPerSample = 0; // bps should be 16 + uint8_t m_flacNumChannels = 0; // can be read out in the FLAC file header + uint32_t m_flacSampleRate = 0; // can be read out in the FLAC file header + uint16_t m_flacMaxFrameSize = 0; // can be read out in the FLAC file header + uint16_t m_flacMaxBlockSize = 0; // can be read out in the FLAC file header + uint32_t m_flacTotalSamplesInStream = 0; // can be read out in the FLAC file header + uint32_t m_metaint = 0; // Number of databytes between metadata + uint32_t m_chunkcount = 0 ; // Counter for chunked transfer + uint32_t m_t0 = 0; // store millis(), is needed for a small delay + uint32_t m_contentlength = 0; // Stores the length if the stream comes from fileserver + uint32_t m_bytesNotDecoded = 0; // pictures or something else that comes with the stream + uint32_t m_PlayingStartTime = 0; // Stores the milliseconds after the start of the audio + bool m_f_swm = true; // Stream without metadata + bool m_f_unsync = false; // set within ID3 tag but not used + bool m_f_exthdr = false; // ID3 extended header + bool m_f_localfile = false ; // Play from local mp3-file + bool m_f_webstream = false ; // Play from URL + bool m_f_ssl = false; + bool m_f_running = false; + bool m_f_firstCall = false; // InitSequence for processWebstream and processLokalFile + bool m_f_ctseen = false; // First line of header seen or not + bool m_f_chunked = false ; // Station provides chunked transfer + bool m_f_firstmetabyte = false; // True if first metabyte (counter) + bool m_f_playing = false; // valid mp3 stream recognized + bool m_f_webfile = false; // assume it's a radiostream, not a podcast + bool m_f_tts = false; // text to speech + bool m_f_psram = false; // set if PSRAM is availabe + bool m_f_loop = false; // Set if audio file should loop + bool m_f_forceMono = false; // if true stereo -> mono + bool m_f_internalDAC = false; // false: output vis I2S, true output via internal DAC + bool m_f_rtsp = false; // set if RTSP is used (m3u8 stream) + bool m_f_m3u8data = false; // used in processM3U8entries + bool m_f_Log = true; // if m3u8: log is cancelled + bool m_f_continue = false; // next m3u8 chunk is available + bool m_f_initInbuffOnce = false; // init InBuff only once + i2s_dac_mode_t m_f_channelEnabled = I2S_DAC_CHANNEL_LEFT_EN; // internal DAC on GPIO26 for M5StickC/Plus + uint32_t m_audioFileDuration = 0; + float m_audioCurrentTime = 0; + uint32_t m_audioDataStart = 0; // in bytes + size_t m_audioDataSize = 0; // + float m_filterBuff[3][2][2][2]; // IIR filters memory for Audio DSP + size_t m_i2s_bytesWritten = 0; // set in i2s_write() but not used + size_t m_file_size = 0; // size of the file + uint16_t m_filterFrequency[2]; + int8_t m_gain0 = 0; // cut or boost filters (EQ) + int8_t m_gain1 = 0; + int8_t m_gain2 = 0; +}; + +//---------------------------------------------------------------------------------------------------------------------- diff --git a/yoRadio/src/audioI2S/aac_decoder/aac_decoder.cpp b/yoRadio/src/audioI2S/aac_decoder/aac_decoder.cpp new file mode 100644 index 0000000..55fddd6 --- /dev/null +++ b/yoRadio/src/audioI2S/aac_decoder/aac_decoder.cpp @@ -0,0 +1,10224 @@ +/* + * aac_decoder.cpp + * libhelix_HAACDECODER + * + * Created on: 26.10.2018 + * Updated on: 10.09.2021 + ************************************************************************************/ + +#include "aac_decoder.h" + +const uint32_t SQRTHALF = 0x5a82799a; /* sqrt(0.5), format = Q31 */ +const uint32_t Q28_2 = 0x20000000; /* Q28: 2.0 */ +const uint32_t Q28_15 = 0x30000000; /* Q28: 1.5 */ +const uint8_t NUM_ITER_IRN = 5; +const uint8_t NUM_TERMS_RPI = 5; +const uint32_t LOG2_EXP_INV = 0x58b90bfc; /* 1/log2(e), Q31 */ +const uint8_t SF_OFFSET = 100; +const uint8_t AAC_PROFILE_LC = 1; +const uint8_t NUM_TIME_SLOTS = 16; +const uint8_t SAMPLES_PER_SLOT = 2; /* RATE in spec */ +const uint8_t SYNCWORDH = 0xff; /* 12-bit syncword */ +const uint8_t SYNCWORDL = 0xf0; +const uint8_t NUM_SAMPLE_RATES = 12; +const uint8_t NUM_DEF_CHAN_MAPS = 8; +const uint32_t NSAMPS_LONG = 1024; +const uint8_t NSAMPS_SHORT = 128; +const uint8_t NUM_SYN_ID_BITS = 3; +const uint8_t NUM_INST_TAG_BITS = 4; +const uint8_t NWINDOWS_LONG = 1; +const uint8_t NWINDOWS_SHORT = 8; +const uint8_t AAC_MAX_NCHANS = 2; /* set to default max number of channels */ +const uint16_t AAC_MAX_NSAMPS = 1024; +const uint8_t MAX_NCHANS_ELEM = 2; /* max number of channels in any single bitstream element */ +const uint8_t MAX_NUM_PCE_ADIF = 16; +const uint8_t ADIF_COPYID_SIZE = 9; +const uint8_t HUFFTAB_SPEC_OFFSET = 1; +const uint8_t FBITS_OUT_DQ_OFF = 20 - 15; /* (FBITS_OUT_DQ - SF_DQ_OFFSET) */ +const uint8_t GBITS_IN_DCT4 = 4; /* min guard bits in for DCT4 */ +const uint8_t FBITS_LOST_DCT4 = 1; /* number of fraction bits lost (>> out) in DCT-IV */ +const uint8_t FBITS_OUT_IMDCT = 3; +const uint8_t NUM_IMDCT_SIZES = 2; +const uint8_t FBITS_LPC_COEFS = 20; +const uint8_t NUM_ITER_INVSQRT = 4; +const uint32_t X0_COEF_2 = 0xc0000000; /* Q29: -2.0 */ +const uint32_t X0_OFF_2 = 0x60000000; /* Q29: 3.0 */ +const uint32_t Q26_3 = 0x0c000000; /* Q26: 3.0 */ +const uint8_t EXT_SBR_DATA = 0x0d; +const uint8_t EXT_SBR_DATA_CRC = 0x0e; +const uint8_t NUM_SAMPLE_RATES_SBR = 9; /* downsampled (single-rate) mode unsupported */ +const uint8_t MAX_NUM_PATCHES = 5; +const uint8_t MAX_QMF_BANDS = 48; /* max QMF subbands covered by SBR (4.6.18.3.6) */ +const uint8_t MAX_NUM_ENV = 5; +const uint8_t NUM_QMF_DELAY_BUFS = 10; +const uint8_t FBITS_IN_QMFA = 14; +const uint8_t NOISE_FLOOR_OFFSET = 6; +const uint8_t FBITS_OUT_DQ_NOISE = 24; /* range of Q_orig = [2^-24, 2^6] */ +const uint8_t FBITS_LOST_QMFA = (1 + 2 + 3 + 2 + 1); +const uint8_t FBITS_OUT_QMFA = (FBITS_IN_QMFA - FBITS_LOST_QMFA); +const uint8_t FBITS_IN_QMFS = FBITS_OUT_QMFA; +const uint8_t FBITS_LOST_DCT4_64 = (2 + 3 + 2); /* 2 in premul, 3 in FFT, 2 in postmul */ +const uint8_t FBITS_OUT_QMFS = (FBITS_IN_QMFS - FBITS_LOST_DCT4_64 + 6 - 1); +const uint8_t RND_VAL = (1 << (FBITS_OUT_QMFS-1)); +const uint8_t HF_ADJ = 2; +const uint8_t HF_GEN = 8; +const uint8_t FBITS_LPCOEFS = 29; /* Q29 for range of (-4, 4) */ +const uint32_t MAG_16 = (16 * (1 << (32 - (2*(32-FBITS_LPCOEFS))))); /* i.e. 16 in Q26 format */ +const uint32_t RELAX_COEF = 0x7ffff79c; /* 1.0 / (1.0 + 1e-6), Q31 */ +const uint8_t MAX_NUM_SMOOTH_COEFS = 5; +const uint8_t FBITS_OUT_DQ_ENV = 29; /* dequantized env scalefactors are Q(29 - envDataDequantScale) */ +const uint8_t FBITS_GLIM_BOOST = 24; +const uint8_t FBITS_QLIM_BOOST = 14; +const uint8_t MIN_GBITS_IN_QMFS = 2; +const uint16_t nmdctTab[2] = {128, 1024}; +const uint8_t postSkip[2] = {15, 1}; +const uint16_t nfftTab[2] = {64, 512}; +const uint8_t nfftlog2Tab[2] = {6, 9}; +const uint8_t cos4sin4tabOffset[2] = {0, 128}; + +PSInfoBase_t *m_PSInfoBase; +AACDecInfo_t *m_AACDecInfo; +AACFrameInfo_t m_AACFrameInfo; +ADTSHeader_t m_fhADTS; +ADIFHeader_t m_fhADIF; +ProgConfigElement_t *m_pce[16]; +PulseInfo_t m_pulseInfo[2]; // [MAX_NCHANS_ELEM] +aac_BitStreamInfo_t m_aac_BitStreamInfo; +PSInfoSBR_t *m_PSInfoSBR; + +//---------------------------------------------------------------------------------------------------------------------- +inline int MULSHIFT32(int x, int y){ + int z; z = (int64_t)x * (int64_t)y >> 32; + return z; +} +inline int CLZ(int x){ +#ifdef __XTENSA__ + return __builtin_clz(x); +#else + int numZeros; + if(!x) return 32; /* count leading zeros with binary search (function should be 17 ARM instructions total) */ + numZeros = 1; + if (!((unsigned int)x >> 16)) { numZeros += 16; x <<= 16; } + if (!((unsigned int)x >> 24)) { numZeros += 8; x <<= 8; } + if (!((unsigned int)x >> 28)) { numZeros += 4; x <<= 4; } + if (!((unsigned int)x >> 30)) { numZeros += 2; x <<= 2; } + numZeros -= ((unsigned int)x >> 31); + return numZeros; +#endif +} +inline int FASTABS(int x){ +#ifdef __XTENSA__ //fb + return __builtin_abs(x); +#else + int sign; + sign = x >> (sizeof(int) * 8 - 1); + x ^= sign; x -= sign; return x; +#endif +} +inline int64_t MADD64(int64_t sum64, int x, int y){ + sum64 += (int64_t)x * (int64_t)y; + return sum64; +} +inline short CLIPTOSHORT(int x){ +#ifdef __XTENSA__ //fb + asm ("clamps %0, %1, 15" : "=a" (x) : "a" (x) : ); + return x; +#else + int sign; /* clip to [-32768, 32767] */ + sign = x >> 31; + if (sign != (x >> 15)) x = sign ^ ((1 << 15) - 1); + return (short)x; +#endif +} +inline int CLIP_2N(int y, int n){ +#ifdef __XTENSA__ //fb + int x = 1 << n; \ + if (y < -x) y = -x; \ + x--; \ + if (y > x) y = x; \ + return y; +#else + int sign = y >> 31; + if(sign != (y >> n)) + y = sign ^ ((1 << n) - 1); + return y; +#endif +} +inline int CLIP_2N_SHIFT30(int y, int n){ + int sign = y >> 31; + if(sign != (y >> (30 - n))) + y = sign ^ (0x3fffffff); + else + y = (y << n); + return y; +} +//---------------------------------------------------------------------------------------------------------------------- + +const uint32_t cos4sin4tab[128 + 1024] PROGMEM = { +/* 128 - format = Q30 * 2^-7 */ +0xbf9bc731, 0xff9b783c, 0xbed5332c, 0xc002c697, 0xbe112251, 0xfe096c8d, 0xbd4f9c30, 0xc00f1c4a, +0xbc90a83f, 0xfc77ae5e, 0xbbd44dd9, 0xc0254e27, 0xbb1a9443, 0xfae67ba2, 0xba6382a6, 0xc04558c0, +0xb9af200f, 0xf9561237, 0xb8fd7373, 0xc06f3726, 0xb84e83ac, 0xf7c6afdc, 0xb7a25779, 0xc0a2e2e3, +0xb6f8f57c, 0xf6389228, 0xb652643e, 0xc0e05401, 0xb5aeaa2a, 0xf4abf67e, 0xb50dcd90, 0xc1278104, +0xb46fd4a4, 0xf3211a07, 0xb3d4c57c, 0xc1785ef4, 0xb33ca614, 0xf19839a6, 0xb2a77c49, 0xc1d2e158, +0xb2154dda, 0xf01191f3, 0xb186206b, 0xc236fa3b, 0xb0f9f981, 0xee8d5f29, 0xb070de82, 0xc2a49a2e, +0xafead4b9, 0xed0bdd25, 0xaf67e14f, 0xc31bb049, 0xaee80952, 0xeb8d475b, 0xae6b51ae, 0xc39c2a2f, +0xadf1bf34, 0xea11d8c8, 0xad7b5692, 0xc425f410, 0xad081c5a, 0xe899cbf1, 0xac9814fd, 0xc4b8f8ad, +0xac2b44cc, 0xe7255ad1, 0xabc1aff9, 0xc555215a, 0xab5b5a96, 0xe5b4bed8, 0xaaf84896, 0xc5fa5603, +0xaa987dca, 0xe44830dd, 0xaa3bfde3, 0xc6a87d2d, 0xa9e2cc73, 0xe2dfe917, 0xa98cece9, 0xc75f7bfe, +0xa93a6296, 0xe17c1f15, 0xa8eb30a7, 0xc81f363d, 0xa89f5a2b, 0xe01d09b4, 0xa856e20e, 0xc8e78e5b, +0xa811cb1b, 0xdec2df18, 0xa7d017fc, 0xc9b86572, 0xa791cb39, 0xdd6dd4a2, 0xa756e73a, 0xca919b4e, +0xa71f6e43, 0xdc1e1ee9, 0xa6eb6279, 0xcb730e70, 0xa6bac5dc, 0xdad3f1b1, 0xa68d9a4c, 0xcc5c9c14, +0xa663e188, 0xd98f7fe6, 0xa63d9d2b, 0xcd4e2037, 0xa61aceaf, 0xd850fb8e, 0xa5fb776b, 0xce47759a, +0xa5df9894, 0xd71895c9, 0xa5c7333e, 0xcf4875ca, 0xa5b2485a, 0xd5e67ec1, 0xa5a0d8b5, 0xd050f926, +0xa592e4fd, 0xd4bae5ab, 0xa5886dba, 0xd160d6e5, 0xa5817354, 0xd395f8ba, 0xa57df60f, 0xd277e518, +/* 1024 - format = Q30 * 2^-10 */ +0xbff3703e, 0xfff36f02, 0xbfda5824, 0xc0000b1a, 0xbfc149ed, 0xffc12b16, 0xbfa845a0, 0xc0003c74, +0xbf8f4b3e, 0xff8ee750, 0xbf765acc, 0xc0009547, 0xbf5d744e, 0xff5ca3d0, 0xbf4497c8, 0xc0011594, +0xbf2bc53d, 0xff2a60b4, 0xbf12fcb2, 0xc001bd5c, 0xbefa3e2a, 0xfef81e1d, 0xbee189a8, 0xc0028c9c, +0xbec8df32, 0xfec5dc28, 0xbeb03eca, 0xc0038356, 0xbe97a875, 0xfe939af5, 0xbe7f1c36, 0xc004a188, +0xbe669a10, 0xfe615aa3, 0xbe4e2209, 0xc005e731, 0xbe35b423, 0xfe2f1b50, 0xbe1d5062, 0xc0075452, +0xbe04f6cb, 0xfdfcdd1d, 0xbdeca760, 0xc008e8e8, 0xbdd46225, 0xfdcaa027, 0xbdbc2720, 0xc00aa4f3, +0xbda3f652, 0xfd98648d, 0xbd8bcfbf, 0xc00c8872, 0xbd73b36d, 0xfd662a70, 0xbd5ba15d, 0xc00e9364, +0xbd439995, 0xfd33f1ed, 0xbd2b9c17, 0xc010c5c7, 0xbd13a8e7, 0xfd01bb24, 0xbcfbc00a, 0xc0131f9b, +0xbce3e182, 0xfccf8634, 0xbccc0d53, 0xc015a0dd, 0xbcb44382, 0xfc9d533b, 0xbc9c8411, 0xc018498c, +0xbc84cf05, 0xfc6b2259, 0xbc6d2461, 0xc01b19a7, 0xbc558428, 0xfc38f3ac, 0xbc3dee5f, 0xc01e112b, +0xbc266309, 0xfc06c754, 0xbc0ee22a, 0xc0213018, 0xbbf76bc4, 0xfbd49d70, 0xbbdfffdd, 0xc024766a, +0xbbc89e77, 0xfba2761e, 0xbbb14796, 0xc027e421, 0xbb99fb3e, 0xfb70517d, 0xbb82b972, 0xc02b7939, +0xbb6b8235, 0xfb3e2fac, 0xbb54558d, 0xc02f35b1, 0xbb3d337b, 0xfb0c10cb, 0xbb261c04, 0xc0331986, +0xbb0f0f2b, 0xfad9f4f8, 0xbaf80cf4, 0xc03724b6, 0xbae11561, 0xfaa7dc52, 0xbaca2878, 0xc03b573f, +0xbab3463b, 0xfa75c6f8, 0xba9c6eae, 0xc03fb11d, 0xba85a1d4, 0xfa43b508, 0xba6edfb1, 0xc044324f, +0xba582849, 0xfa11a6a3, 0xba417b9e, 0xc048dad1, 0xba2ad9b5, 0xf9df9be6, 0xba144291, 0xc04daaa1, +0xb9fdb635, 0xf9ad94f0, 0xb9e734a4, 0xc052a1bb, 0xb9d0bde4, 0xf97b91e1, 0xb9ba51f6, 0xc057c01d, +0xb9a3f0de, 0xf94992d7, 0xb98d9aa0, 0xc05d05c3, 0xb9774f3f, 0xf91797f0, 0xb9610ebe, 0xc06272aa, +0xb94ad922, 0xf8e5a14d, 0xb934ae6d, 0xc06806ce, 0xb91e8ea3, 0xf8b3af0c, 0xb90879c7, 0xc06dc22e, +0xb8f26fdc, 0xf881c14b, 0xb8dc70e7, 0xc073a4c3, 0xb8c67cea, 0xf84fd829, 0xb8b093ea, 0xc079ae8c, +0xb89ab5e8, 0xf81df3c5, 0xb884e2e9, 0xc07fdf85, 0xb86f1af0, 0xf7ec143e, 0xb8595e00, 0xc08637a9, +0xb843ac1d, 0xf7ba39b3, 0xb82e0549, 0xc08cb6f5, 0xb818698a, 0xf7886442, 0xb802d8e0, 0xc0935d64, +0xb7ed5351, 0xf756940a, 0xb7d7d8df, 0xc09a2af3, 0xb7c2698e, 0xf724c92a, 0xb7ad0561, 0xc0a11f9d, +0xb797ac5b, 0xf6f303c0, 0xb7825e80, 0xc0a83b5e, 0xb76d1bd2, 0xf6c143ec, 0xb757e455, 0xc0af7e33, +0xb742b80d, 0xf68f89cb, 0xb72d96fd, 0xc0b6e815, 0xb7188127, 0xf65dd57d, 0xb7037690, 0xc0be7901, +0xb6ee773a, 0xf62c2721, 0xb6d98328, 0xc0c630f2, 0xb6c49a5e, 0xf5fa7ed4, 0xb6afbce0, 0xc0ce0fe3, +0xb69aeab0, 0xf5c8dcb6, 0xb68623d1, 0xc0d615cf, 0xb6716847, 0xf59740e5, 0xb65cb815, 0xc0de42b2, +0xb648133e, 0xf565ab80, 0xb63379c5, 0xc0e69686, 0xb61eebae, 0xf5341ca5, 0xb60a68fb, 0xc0ef1147, +0xb5f5f1b1, 0xf5029473, 0xb5e185d1, 0xc0f7b2ee, 0xb5cd255f, 0xf4d11308, 0xb5b8d05f, 0xc1007b77, +0xb5a486d2, 0xf49f9884, 0xb59048be, 0xc1096add, 0xb57c1624, 0xf46e2504, 0xb567ef08, 0xc1128119, +0xb553d36c, 0xf43cb8a7, 0xb53fc355, 0xc11bbe26, 0xb52bbec4, 0xf40b538b, 0xb517c5be, 0xc12521ff, +0xb503d845, 0xf3d9f5cf, 0xb4eff65c, 0xc12eac9d, 0xb4dc2007, 0xf3a89f92, 0xb4c85548, 0xc1385dfb, +0xb4b49622, 0xf37750f2, 0xb4a0e299, 0xc1423613, 0xb48d3ab0, 0xf3460a0d, 0xb4799e69, 0xc14c34df, +0xb4660dc8, 0xf314cb02, 0xb45288cf, 0xc1565a58, 0xb43f0f82, 0xf2e393ef, 0xb42ba1e4, 0xc160a678, +0xb4183ff7, 0xf2b264f2, 0xb404e9bf, 0xc16b193a, 0xb3f19f3e, 0xf2813e2a, 0xb3de6078, 0xc175b296, +0xb3cb2d70, 0xf2501fb5, 0xb3b80628, 0xc1807285, 0xb3a4eaa4, 0xf21f09b1, 0xb391dae6, 0xc18b5903, +0xb37ed6f1, 0xf1edfc3d, 0xb36bdec9, 0xc1966606, 0xb358f26f, 0xf1bcf777, 0xb34611e8, 0xc1a1998a, +0xb3333d36, 0xf18bfb7d, 0xb320745c, 0xc1acf386, 0xb30db75d, 0xf15b086d, 0xb2fb063b, 0xc1b873f5, +0xb2e860fa, 0xf12a1e66, 0xb2d5c79d, 0xc1c41ace, 0xb2c33a26, 0xf0f93d86, 0xb2b0b898, 0xc1cfe80a, +0xb29e42f6, 0xf0c865ea, 0xb28bd943, 0xc1dbdba3, 0xb2797b82, 0xf09797b2, 0xb26729b5, 0xc1e7f591, +0xb254e3e0, 0xf066d2fa, 0xb242aa05, 0xc1f435cc, 0xb2307c27, 0xf03617e2, 0xb21e5a49, 0xc2009c4e, +0xb20c446d, 0xf0056687, 0xb1fa3a97, 0xc20d290d, 0xb1e83cc9, 0xefd4bf08, 0xb1d64b06, 0xc219dc03, +0xb1c46551, 0xefa42181, 0xb1b28bad, 0xc226b528, 0xb1a0be1b, 0xef738e12, 0xb18efca0, 0xc233b473, +0xb17d473d, 0xef4304d8, 0xb16b9df6, 0xc240d9de, 0xb15a00cd, 0xef1285f2, 0xb1486fc5, 0xc24e255e, +0xb136eae1, 0xeee2117c, 0xb1257223, 0xc25b96ee, 0xb114058e, 0xeeb1a796, 0xb102a524, 0xc2692e83, +0xb0f150e9, 0xee81485c, 0xb0e008e0, 0xc276ec16, 0xb0cecd09, 0xee50f3ed, 0xb0bd9d6a, 0xc284cf9f, +0xb0ac7a03, 0xee20aa67, 0xb09b62d8, 0xc292d914, 0xb08a57eb, 0xedf06be6, 0xb079593f, 0xc2a1086d, +0xb06866d7, 0xedc0388a, 0xb05780b5, 0xc2af5da2, 0xb046a6db, 0xed901070, 0xb035d94e, 0xc2bdd8a9, +0xb025180e, 0xed5ff3b5, 0xb014631e, 0xc2cc7979, 0xb003ba82, 0xed2fe277, 0xaff31e3b, 0xc2db400a, +0xafe28e4d, 0xecffdcd4, 0xafd20ab9, 0xc2ea2c53, 0xafc19383, 0xeccfe2ea, 0xafb128ad, 0xc2f93e4a, +0xafa0ca39, 0xec9ff4d6, 0xaf90782a, 0xc30875e5, 0xaf803283, 0xec7012b5, 0xaf6ff945, 0xc317d31c, +0xaf5fcc74, 0xec403ca5, 0xaf4fac12, 0xc32755e5, 0xaf3f9822, 0xec1072c4, 0xaf2f90a5, 0xc336fe37, +0xaf1f959f, 0xebe0b52f, 0xaf0fa712, 0xc346cc07, 0xaeffc500, 0xebb10404, 0xaeefef6c, 0xc356bf4d, +0xaee02658, 0xeb815f60, 0xaed069c7, 0xc366d7fd, 0xaec0b9bb, 0xeb51c760, 0xaeb11636, 0xc377160f, +0xaea17f3b, 0xeb223c22, 0xae91f4cd, 0xc3877978, 0xae8276ed, 0xeaf2bdc3, 0xae73059f, 0xc398022f, +0xae63a0e3, 0xeac34c60, 0xae5448be, 0xc3a8b028, 0xae44fd31, 0xea93e817, 0xae35be3f, 0xc3b9835a, +0xae268be9, 0xea649105, 0xae176633, 0xc3ca7bba, 0xae084d1f, 0xea354746, 0xadf940ae, 0xc3db993e, +0xadea40e4, 0xea060af9, 0xaddb4dc2, 0xc3ecdbdc, 0xadcc674b, 0xe9d6dc3b, 0xadbd8d82, 0xc3fe4388, +0xadaec067, 0xe9a7bb28, 0xad9fffff, 0xc40fd037, 0xad914c4b, 0xe978a7dd, 0xad82a54c, 0xc42181e0, +0xad740b07, 0xe949a278, 0xad657d7c, 0xc4335877, 0xad56fcaf, 0xe91aab16, 0xad4888a0, 0xc44553f2, +0xad3a2153, 0xe8ebc1d3, 0xad2bc6ca, 0xc4577444, 0xad1d7907, 0xe8bce6cd, 0xad0f380c, 0xc469b963, +0xad0103db, 0xe88e1a20, 0xacf2dc77, 0xc47c2344, 0xace4c1e2, 0xe85f5be9, 0xacd6b41e, 0xc48eb1db, +0xacc8b32c, 0xe830ac45, 0xacbabf10, 0xc4a1651c, 0xacacd7cb, 0xe8020b52, 0xac9efd60, 0xc4b43cfd, +0xac912fd1, 0xe7d3792b, 0xac836f1f, 0xc4c73972, 0xac75bb4d, 0xe7a4f5ed, 0xac68145d, 0xc4da5a6f, +0xac5a7a52, 0xe77681b6, 0xac4ced2c, 0xc4ed9fe7, 0xac3f6cef, 0xe7481ca1, 0xac31f99d, 0xc50109d0, +0xac249336, 0xe719c6cb, 0xac1739bf, 0xc514981d, 0xac09ed38, 0xe6eb8052, 0xabfcada3, 0xc5284ac3, +0xabef7b04, 0xe6bd4951, 0xabe2555b, 0xc53c21b4, 0xabd53caa, 0xe68f21e5, 0xabc830f5, 0xc5501ce5, +0xabbb323c, 0xe6610a2a, 0xabae4082, 0xc5643c4a, 0xaba15bc9, 0xe633023e, 0xab948413, 0xc5787fd6, +0xab87b962, 0xe6050a3b, 0xab7afbb7, 0xc58ce77c, 0xab6e4b15, 0xe5d72240, 0xab61a77d, 0xc5a17330, +0xab5510f3, 0xe5a94a67, 0xab488776, 0xc5b622e6, 0xab3c0b0b, 0xe57b82cd, 0xab2f9bb1, 0xc5caf690, +0xab23396c, 0xe54dcb8f, 0xab16e43d, 0xc5dfee22, 0xab0a9c27, 0xe52024c9, 0xaafe612a, 0xc5f5098f, +0xaaf23349, 0xe4f28e96, 0xaae61286, 0xc60a48c9, 0xaad9fee3, 0xe4c50914, 0xaacdf861, 0xc61fabc4, +0xaac1ff03, 0xe497945d, 0xaab612ca, 0xc6353273, 0xaaaa33b8, 0xe46a308f, 0xaa9e61cf, 0xc64adcc7, +0xaa929d10, 0xe43cddc4, 0xaa86e57e, 0xc660aab5, 0xaa7b3b1b, 0xe40f9c1a, 0xaa6f9de7, 0xc6769c2e, +0xaa640de6, 0xe3e26bac, 0xaa588b18, 0xc68cb124, 0xaa4d157f, 0xe3b54c95, 0xaa41ad1e, 0xc6a2e98b, +0xaa3651f6, 0xe3883ef2, 0xaa2b0409, 0xc6b94554, 0xaa1fc358, 0xe35b42df, 0xaa148fe6, 0xc6cfc472, +0xaa0969b3, 0xe32e5876, 0xa9fe50c2, 0xc6e666d7, 0xa9f34515, 0xe3017fd5, 0xa9e846ad, 0xc6fd2c75, +0xa9dd558b, 0xe2d4b916, 0xa9d271b2, 0xc714153e, 0xa9c79b23, 0xe2a80456, 0xa9bcd1e0, 0xc72b2123, +0xa9b215ea, 0xe27b61af, 0xa9a76744, 0xc7425016, 0xa99cc5ee, 0xe24ed13d, 0xa99231eb, 0xc759a20a, +0xa987ab3c, 0xe222531c, 0xa97d31e3, 0xc77116f0, 0xa972c5e1, 0xe1f5e768, 0xa9686738, 0xc788aeb9, +0xa95e15e9, 0xe1c98e3b, 0xa953d1f7, 0xc7a06957, 0xa9499b62, 0xe19d47b1, 0xa93f722c, 0xc7b846ba, +0xa9355658, 0xe17113e5, 0xa92b47e5, 0xc7d046d6, 0xa92146d7, 0xe144f2f3, 0xa917532e, 0xc7e8699a, +0xa90d6cec, 0xe118e4f6, 0xa9039413, 0xc800aef7, 0xa8f9c8a4, 0xe0ecea09, 0xa8f00aa0, 0xc81916df, +0xa8e65a0a, 0xe0c10247, 0xa8dcb6e2, 0xc831a143, 0xa8d3212a, 0xe0952dcb, 0xa8c998e3, 0xc84a4e14, +0xa8c01e10, 0xe0696cb0, 0xa8b6b0b1, 0xc8631d42, 0xa8ad50c8, 0xe03dbf11, 0xa8a3fe57, 0xc87c0ebd, +0xa89ab95e, 0xe012250a, 0xa89181df, 0xc8952278, 0xa88857dc, 0xdfe69eb4, 0xa87f3b57, 0xc8ae5862, +0xa8762c4f, 0xdfbb2c2c, 0xa86d2ac8, 0xc8c7b06b, 0xa86436c2, 0xdf8fcd8b, 0xa85b503e, 0xc8e12a84, +0xa852773f, 0xdf6482ed, 0xa849abc4, 0xc8fac69e, 0xa840edd1, 0xdf394c6b, 0xa8383d66, 0xc91484a8, +0xa82f9a84, 0xdf0e2a22, 0xa827052d, 0xc92e6492, 0xa81e7d62, 0xdee31c2b, 0xa8160324, 0xc948664d, +0xa80d9675, 0xdeb822a1, 0xa8053756, 0xc96289c9, 0xa7fce5c9, 0xde8d3d9e, 0xa7f4a1ce, 0xc97ccef5, +0xa7ec6b66, 0xde626d3e, 0xa7e44294, 0xc99735c2, 0xa7dc2759, 0xde37b199, 0xa7d419b4, 0xc9b1be1e, +0xa7cc19a9, 0xde0d0acc, 0xa7c42738, 0xc9cc67fa, 0xa7bc4262, 0xdde278ef, 0xa7b46b29, 0xc9e73346, +0xa7aca18e, 0xddb7fc1e, 0xa7a4e591, 0xca021fef, 0xa79d3735, 0xdd8d9472, 0xa795967a, 0xca1d2de7, +0xa78e0361, 0xdd634206, 0xa7867dec, 0xca385d1d, 0xa77f061c, 0xdd3904f4, 0xa7779bf2, 0xca53ad7e, +0xa7703f70, 0xdd0edd55, 0xa768f095, 0xca6f1efc, 0xa761af64, 0xdce4cb44, 0xa75a7bdd, 0xca8ab184, +0xa7535602, 0xdcbacedb, 0xa74c3dd4, 0xcaa66506, 0xa7453353, 0xdc90e834, 0xa73e3681, 0xcac23971, +0xa7374760, 0xdc671768, 0xa73065ef, 0xcade2eb3, 0xa7299231, 0xdc3d5c91, 0xa722cc25, 0xcafa44bc, +0xa71c13ce, 0xdc13b7c9, 0xa715692c, 0xcb167b79, 0xa70ecc41, 0xdbea292b, 0xa7083d0d, 0xcb32d2da, +0xa701bb91, 0xdbc0b0ce, 0xa6fb47ce, 0xcb4f4acd, 0xa6f4e1c6, 0xdb974ece, 0xa6ee8979, 0xcb6be341, +0xa6e83ee8, 0xdb6e0342, 0xa6e20214, 0xcb889c23, 0xa6dbd2ff, 0xdb44ce46, 0xa6d5b1a9, 0xcba57563, +0xa6cf9e13, 0xdb1baff2, 0xa6c9983e, 0xcbc26eee, 0xa6c3a02b, 0xdaf2a860, 0xa6bdb5da, 0xcbdf88b3, +0xa6b7d94e, 0xdac9b7a9, 0xa6b20a86, 0xcbfcc29f, 0xa6ac4984, 0xdaa0dde7, 0xa6a69649, 0xcc1a1ca0, +0xa6a0f0d5, 0xda781b31, 0xa69b5929, 0xcc3796a5, 0xa695cf46, 0xda4f6fa3, 0xa690532d, 0xcc55309b, +0xa68ae4df, 0xda26db54, 0xa685845c, 0xcc72ea70, 0xa68031a6, 0xd9fe5e5e, 0xa67aecbd, 0xcc90c412, +0xa675b5a3, 0xd9d5f8d9, 0xa6708c57, 0xccaebd6e, 0xa66b70db, 0xd9adaadf, 0xa6666330, 0xccccd671, +0xa6616355, 0xd9857489, 0xa65c714d, 0xcceb0f0a, 0xa6578d18, 0xd95d55ef, 0xa652b6b6, 0xcd096725, +0xa64dee28, 0xd9354f2a, 0xa6493370, 0xcd27deb0, 0xa644868d, 0xd90d6053, 0xa63fe781, 0xcd467599, +0xa63b564c, 0xd8e58982, 0xa636d2ee, 0xcd652bcb, 0xa6325d6a, 0xd8bdcad0, 0xa62df5bf, 0xcd840134, +0xa6299bed, 0xd8962456, 0xa6254ff7, 0xcda2f5c2, 0xa62111db, 0xd86e962b, 0xa61ce19c, 0xcdc20960, +0xa618bf39, 0xd8472069, 0xa614aab3, 0xcde13bfd, 0xa610a40c, 0xd81fc328, 0xa60cab43, 0xce008d84, +0xa608c058, 0xd7f87e7f, 0xa604e34e, 0xce1ffde2, 0xa6011424, 0xd7d15288, 0xa5fd52db, 0xce3f8d05, +0xa5f99f73, 0xd7aa3f5a, 0xa5f5f9ed, 0xce5f3ad8, 0xa5f2624a, 0xd783450d, 0xa5eed88a, 0xce7f0748, +0xa5eb5cae, 0xd75c63ba, 0xa5e7eeb6, 0xce9ef241, 0xa5e48ea3, 0xd7359b78, 0xa5e13c75, 0xcebefbb0, +0xa5ddf82d, 0xd70eec60, 0xa5dac1cb, 0xcedf2380, 0xa5d79950, 0xd6e85689, 0xa5d47ebc, 0xceff699f, +0xa5d17210, 0xd6c1da0b, 0xa5ce734d, 0xcf1fcdf8, 0xa5cb8272, 0xd69b76fe, 0xa5c89f80, 0xcf405077, +0xa5c5ca77, 0xd6752d79, 0xa5c30359, 0xcf60f108, 0xa5c04a25, 0xd64efd94, 0xa5bd9edc, 0xcf81af97, +0xa5bb017f, 0xd628e767, 0xa5b8720d, 0xcfa28c10, 0xa5b5f087, 0xd602eb0a, 0xa5b37cee, 0xcfc3865e, +0xa5b11741, 0xd5dd0892, 0xa5aebf82, 0xcfe49e6d, 0xa5ac75b0, 0xd5b74019, 0xa5aa39cd, 0xd005d42a, +0xa5a80bd7, 0xd59191b5, 0xa5a5ebd0, 0xd027277e, 0xa5a3d9b8, 0xd56bfd7d, 0xa5a1d590, 0xd0489856, +0xa59fdf57, 0xd5468389, 0xa59df70e, 0xd06a269d, 0xa59c1cb5, 0xd52123f0, 0xa59a504c, 0xd08bd23f, +0xa59891d4, 0xd4fbdec9, 0xa596e14e, 0xd0ad9b26, 0xa5953eb8, 0xd4d6b42b, 0xa593aa14, 0xd0cf813e, +0xa5922362, 0xd4b1a42c, 0xa590aaa2, 0xd0f18472, 0xa58f3fd4, 0xd48caee4, 0xa58de2f8, 0xd113a4ad, +0xa58c940f, 0xd467d469, 0xa58b5319, 0xd135e1d9, 0xa58a2016, 0xd44314d3, 0xa588fb06, 0xd1583be2, +0xa587e3ea, 0xd41e7037, 0xa586dac1, 0xd17ab2b3, 0xa585df8c, 0xd3f9e6ad, 0xa584f24b, 0xd19d4636, +0xa58412fe, 0xd3d5784a, 0xa58341a5, 0xd1bff656, 0xa5827e40, 0xd3b12526, 0xa581c8d0, 0xd1e2c2fd, +0xa5812154, 0xd38ced57, 0xa58087cd, 0xd205ac17, 0xa57ffc3b, 0xd368d0f3, 0xa57f7e9d, 0xd228b18d, +0xa57f0ef5, 0xd344d011, 0xa57ead41, 0xd24bd34a, 0xa57e5982, 0xd320eac6, 0xa57e13b8, 0xd26f1138, +0xa57ddbe4, 0xd2fd2129, 0xa57db204, 0xd2926b41, 0xa57d961a, 0xd2d97350, 0xa57d8825, 0xd2b5e151, +}; + +const int cos1sin1tab[514] PROGMEM = { +/* format = Q30 */ +0x40000000, 0x00000000, 0x40323034, 0x003243f1, 0x406438cf, 0x006487c4, 0x409619b2, 0x0096cb58, +0x40c7d2bd, 0x00c90e90, 0x40f963d3, 0x00fb514b, 0x412accd4, 0x012d936c, 0x415c0da3, 0x015fd4d2, +0x418d2621, 0x0192155f, 0x41be162f, 0x01c454f5, 0x41eeddaf, 0x01f69373, 0x421f7c84, 0x0228d0bb, +0x424ff28f, 0x025b0caf, 0x42803fb2, 0x028d472e, 0x42b063d0, 0x02bf801a, 0x42e05ecb, 0x02f1b755, +0x43103085, 0x0323ecbe, 0x433fd8e1, 0x03562038, 0x436f57c1, 0x038851a2, 0x439ead09, 0x03ba80df, +0x43cdd89a, 0x03ecadcf, 0x43fcda59, 0x041ed854, 0x442bb227, 0x0451004d, 0x445a5fe8, 0x0483259d, +0x4488e37f, 0x04b54825, 0x44b73ccf, 0x04e767c5, 0x44e56bbd, 0x0519845e, 0x4513702a, 0x054b9dd3, +0x454149fc, 0x057db403, 0x456ef916, 0x05afc6d0, 0x459c7d5a, 0x05e1d61b, 0x45c9d6af, 0x0613e1c5, +0x45f704f7, 0x0645e9af, 0x46240816, 0x0677edbb, 0x4650dff1, 0x06a9edc9, 0x467d8c6d, 0x06dbe9bb, +0x46aa0d6d, 0x070de172, 0x46d662d6, 0x073fd4cf, 0x47028c8d, 0x0771c3b3, 0x472e8a76, 0x07a3adff, +0x475a5c77, 0x07d59396, 0x47860275, 0x08077457, 0x47b17c54, 0x08395024, 0x47dcc9f9, 0x086b26de, +0x4807eb4b, 0x089cf867, 0x4832e02d, 0x08cec4a0, 0x485da887, 0x09008b6a, 0x4888443d, 0x09324ca7, +0x48b2b335, 0x09640837, 0x48dcf556, 0x0995bdfd, 0x49070a84, 0x09c76dd8, 0x4930f2a6, 0x09f917ac, +0x495aada2, 0x0a2abb59, 0x49843b5f, 0x0a5c58c0, 0x49ad9bc2, 0x0a8defc3, 0x49d6ceb3, 0x0abf8043, +0x49ffd417, 0x0af10a22, 0x4a28abd6, 0x0b228d42, 0x4a5155d6, 0x0b540982, 0x4a79d1ff, 0x0b857ec7, +0x4aa22036, 0x0bb6ecef, 0x4aca4065, 0x0be853de, 0x4af23270, 0x0c19b374, 0x4b19f641, 0x0c4b0b94, +0x4b418bbe, 0x0c7c5c1e, 0x4b68f2cf, 0x0cada4f5, 0x4b902b5c, 0x0cdee5f9, 0x4bb7354d, 0x0d101f0e, +0x4bde1089, 0x0d415013, 0x4c04bcf8, 0x0d7278eb, 0x4c2b3a84, 0x0da39978, 0x4c518913, 0x0dd4b19a, +0x4c77a88e, 0x0e05c135, 0x4c9d98de, 0x0e36c82a, 0x4cc359ec, 0x0e67c65a, 0x4ce8eb9f, 0x0e98bba7, +0x4d0e4de2, 0x0ec9a7f3, 0x4d33809c, 0x0efa8b20, 0x4d5883b7, 0x0f2b650f, 0x4d7d571c, 0x0f5c35a3, +0x4da1fab5, 0x0f8cfcbe, 0x4dc66e6a, 0x0fbdba40, 0x4deab226, 0x0fee6e0d, 0x4e0ec5d1, 0x101f1807, +0x4e32a956, 0x104fb80e, 0x4e565c9f, 0x10804e06, 0x4e79df95, 0x10b0d9d0, 0x4e9d3222, 0x10e15b4e, +0x4ec05432, 0x1111d263, 0x4ee345ad, 0x11423ef0, 0x4f06067f, 0x1172a0d7, 0x4f289692, 0x11a2f7fc, +0x4f4af5d1, 0x11d3443f, 0x4f6d2427, 0x12038584, 0x4f8f217e, 0x1233bbac, 0x4fb0edc1, 0x1263e699, +0x4fd288dc, 0x1294062f, 0x4ff3f2bb, 0x12c41a4f, 0x50152b47, 0x12f422db, 0x5036326e, 0x13241fb6, +0x50570819, 0x135410c3, 0x5077ac37, 0x1383f5e3, 0x50981eb1, 0x13b3cefa, 0x50b85f74, 0x13e39be9, +0x50d86e6d, 0x14135c94, 0x50f84b87, 0x144310dd, 0x5117f6ae, 0x1472b8a5, 0x51376fd0, 0x14a253d1, +0x5156b6d9, 0x14d1e242, 0x5175cbb5, 0x150163dc, 0x5194ae52, 0x1530d881, 0x51b35e9b, 0x15604013, +0x51d1dc80, 0x158f9a76, 0x51f027eb, 0x15bee78c, 0x520e40cc, 0x15ee2738, 0x522c270f, 0x161d595d, +0x5249daa2, 0x164c7ddd, 0x52675b72, 0x167b949d, 0x5284a96e, 0x16aa9d7e, 0x52a1c482, 0x16d99864, +0x52beac9f, 0x17088531, 0x52db61b0, 0x173763c9, 0x52f7e3a6, 0x1766340f, 0x5314326d, 0x1794f5e6, +0x53304df6, 0x17c3a931, 0x534c362d, 0x17f24dd3, 0x5367eb03, 0x1820e3b0, 0x53836c66, 0x184f6aab, +0x539eba45, 0x187de2a7, 0x53b9d48f, 0x18ac4b87, 0x53d4bb34, 0x18daa52f, 0x53ef6e23, 0x1908ef82, +0x5409ed4b, 0x19372a64, 0x5424389d, 0x196555b8, 0x543e5007, 0x19937161, 0x5458337a, 0x19c17d44, +0x5471e2e6, 0x19ef7944, 0x548b5e3b, 0x1a1d6544, 0x54a4a56a, 0x1a4b4128, 0x54bdb862, 0x1a790cd4, +0x54d69714, 0x1aa6c82b, 0x54ef4171, 0x1ad47312, 0x5507b76a, 0x1b020d6c, 0x551ff8ef, 0x1b2f971e, +0x553805f2, 0x1b5d100a, 0x554fde64, 0x1b8a7815, 0x55678236, 0x1bb7cf23, 0x557ef15a, 0x1be51518, +0x55962bc0, 0x1c1249d8, 0x55ad315b, 0x1c3f6d47, 0x55c4021d, 0x1c6c7f4a, 0x55da9df7, 0x1c997fc4, +0x55f104dc, 0x1cc66e99, 0x560736bd, 0x1cf34baf, 0x561d338d, 0x1d2016e9, 0x5632fb3f, 0x1d4cd02c, +0x56488dc5, 0x1d79775c, 0x565deb11, 0x1da60c5d, 0x56731317, 0x1dd28f15, 0x568805c9, 0x1dfeff67, +0x569cc31b, 0x1e2b5d38, 0x56b14b00, 0x1e57a86d, 0x56c59d6a, 0x1e83e0eb, 0x56d9ba4e, 0x1eb00696, +0x56eda1a0, 0x1edc1953, 0x57015352, 0x1f081907, 0x5714cf59, 0x1f340596, 0x572815a8, 0x1f5fdee6, +0x573b2635, 0x1f8ba4dc, 0x574e00f2, 0x1fb7575c, 0x5760a5d5, 0x1fe2f64c, 0x577314d2, 0x200e8190, +0x57854ddd, 0x2039f90f, 0x579750ec, 0x20655cac, 0x57a91df2, 0x2090ac4d, 0x57bab4e6, 0x20bbe7d8, +0x57cc15bc, 0x20e70f32, 0x57dd406a, 0x21122240, 0x57ee34e5, 0x213d20e8, 0x57fef323, 0x21680b0f, +0x580f7b19, 0x2192e09b, 0x581fccbc, 0x21bda171, 0x582fe804, 0x21e84d76, 0x583fcce6, 0x2212e492, +0x584f7b58, 0x223d66a8, 0x585ef351, 0x2267d3a0, 0x586e34c7, 0x22922b5e, 0x587d3fb0, 0x22bc6dca, +0x588c1404, 0x22e69ac8, 0x589ab1b9, 0x2310b23e, 0x58a918c6, 0x233ab414, 0x58b74923, 0x2364a02e, +0x58c542c5, 0x238e7673, 0x58d305a6, 0x23b836ca, 0x58e091bd, 0x23e1e117, 0x58ede700, 0x240b7543, +0x58fb0568, 0x2434f332, 0x5907eced, 0x245e5acc, 0x59149d87, 0x2487abf7, 0x5921172e, 0x24b0e699, +0x592d59da, 0x24da0a9a, 0x59396584, 0x250317df, 0x59453a24, 0x252c0e4f, 0x5950d7b3, 0x2554edd1, +0x595c3e2a, 0x257db64c, 0x59676d82, 0x25a667a7, 0x597265b4, 0x25cf01c8, 0x597d26b8, 0x25f78497, +0x5987b08a, 0x261feffa, 0x59920321, 0x264843d9, 0x599c1e78, 0x2670801a, 0x59a60288, 0x2698a4a6, +0x59afaf4c, 0x26c0b162, 0x59b924bc, 0x26e8a637, 0x59c262d5, 0x2710830c, 0x59cb698f, 0x273847c8, +0x59d438e5, 0x275ff452, 0x59dcd0d3, 0x27878893, 0x59e53151, 0x27af0472, 0x59ed5a5c, 0x27d667d5, +0x59f54bee, 0x27fdb2a7, 0x59fd0603, 0x2824e4cc, 0x5a048895, 0x284bfe2f, 0x5a0bd3a1, 0x2872feb6, +0x5a12e720, 0x2899e64a, 0x5a19c310, 0x28c0b4d2, 0x5a20676c, 0x28e76a37, 0x5a26d42f, 0x290e0661, +0x5a2d0957, 0x29348937, 0x5a3306de, 0x295af2a3, 0x5a38ccc2, 0x2981428c, 0x5a3e5afe, 0x29a778db, +0x5a43b190, 0x29cd9578, 0x5a48d074, 0x29f3984c, 0x5a4db7a6, 0x2a19813f, 0x5a526725, 0x2a3f503a, +0x5a56deec, 0x2a650525, 0x5a5b1efa, 0x2a8a9fea, 0x5a5f274b, 0x2ab02071, 0x5a62f7dd, 0x2ad586a3, +0x5a6690ae, 0x2afad269, 0x5a69f1bb, 0x2b2003ac, 0x5a6d1b03, 0x2b451a55, 0x5a700c84, 0x2b6a164d, +0x5a72c63b, 0x2b8ef77d, 0x5a754827, 0x2bb3bdce, 0x5a779246, 0x2bd8692b, 0x5a79a498, 0x2bfcf97c, +0x5a7b7f1a, 0x2c216eaa, 0x5a7d21cc, 0x2c45c8a0, 0x5a7e8cac, 0x2c6a0746, 0x5a7fbfbb, 0x2c8e2a87, +0x5a80baf6, 0x2cb2324c, 0x5a817e5d, 0x2cd61e7f, 0x5a8209f1, 0x2cf9ef09, 0x5a825db0, 0x2d1da3d5, +0x5a82799a, 0x2d413ccd, +}; + +const uint8_t sinWindowOffset[NUM_IMDCT_SIZES] PROGMEM = {0, 128}; + +const int sinWindow[128 + 1024] PROGMEM = { +/* 128 - format = Q31 * 2^0 */ +0x00c90f88, 0x7fff6216, 0x025b26d7, 0x7ffa72d1, 0x03ed26e6, 0x7ff09478, 0x057f0035, 0x7fe1c76b, +0x0710a345, 0x7fce0c3e, 0x08a2009a, 0x7fb563b3, 0x0a3308bd, 0x7f97cebd, 0x0bc3ac35, 0x7f754e80, +0x0d53db92, 0x7f4de451, 0x0ee38766, 0x7f2191b4, 0x1072a048, 0x7ef05860, 0x120116d5, 0x7eba3a39, +0x138edbb1, 0x7e7f3957, 0x151bdf86, 0x7e3f57ff, 0x16a81305, 0x7dfa98a8, 0x183366e9, 0x7db0fdf8, +0x19bdcbf3, 0x7d628ac6, 0x1b4732ef, 0x7d0f4218, 0x1ccf8cb3, 0x7cb72724, 0x1e56ca1e, 0x7c5a3d50, +0x1fdcdc1b, 0x7bf88830, 0x2161b3a0, 0x7b920b89, 0x22e541af, 0x7b26cb4f, 0x24677758, 0x7ab6cba4, +0x25e845b6, 0x7a4210d8, 0x27679df4, 0x79c89f6e, 0x28e5714b, 0x794a7c12, 0x2a61b101, 0x78c7aba2, +0x2bdc4e6f, 0x78403329, 0x2d553afc, 0x77b417df, 0x2ecc681e, 0x77235f2d, 0x3041c761, 0x768e0ea6, +0x31b54a5e, 0x75f42c0b, 0x3326e2c3, 0x7555bd4c, 0x34968250, 0x74b2c884, 0x36041ad9, 0x740b53fb, +0x376f9e46, 0x735f6626, 0x38d8fe93, 0x72af05a7, 0x3a402dd2, 0x71fa3949, 0x3ba51e29, 0x71410805, +0x3d07c1d6, 0x708378ff, 0x3e680b2c, 0x6fc19385, 0x3fc5ec98, 0x6efb5f12, 0x4121589b, 0x6e30e34a, +0x427a41d0, 0x6d6227fa, 0x43d09aed, 0x6c8f351c, 0x452456bd, 0x6bb812d1, 0x46756828, 0x6adcc964, +0x47c3c22f, 0x69fd614a, 0x490f57ee, 0x6919e320, 0x4a581c9e, 0x683257ab, 0x4b9e0390, 0x6746c7d8, +0x4ce10034, 0x66573cbb, 0x4e210617, 0x6563bf92, 0x4f5e08e3, 0x646c59bf, 0x5097fc5e, 0x637114cc, +0x51ced46e, 0x6271fa69, 0x53028518, 0x616f146c, 0x5433027d, 0x60686ccf, 0x556040e2, 0x5f5e0db3, +0x568a34a9, 0x5e50015d, 0x57b0d256, 0x5d3e5237, 0x58d40e8c, 0x5c290acc, 0x59f3de12, 0x5b1035cf, +/* 1024 - format = Q31 * 2^0 */ +0x001921fb, 0x7ffffd88, 0x004b65ee, 0x7fffe9cb, 0x007da9d4, 0x7fffc251, 0x00afeda8, 0x7fff8719, +0x00e23160, 0x7fff3824, 0x011474f6, 0x7ffed572, 0x0146b860, 0x7ffe5f03, 0x0178fb99, 0x7ffdd4d7, +0x01ab3e97, 0x7ffd36ee, 0x01dd8154, 0x7ffc8549, 0x020fc3c6, 0x7ffbbfe6, 0x024205e8, 0x7ffae6c7, +0x027447b0, 0x7ff9f9ec, 0x02a68917, 0x7ff8f954, 0x02d8ca16, 0x7ff7e500, 0x030b0aa4, 0x7ff6bcf0, +0x033d4abb, 0x7ff58125, 0x036f8a51, 0x7ff4319d, 0x03a1c960, 0x7ff2ce5b, 0x03d407df, 0x7ff1575d, +0x040645c7, 0x7fefcca4, 0x04388310, 0x7fee2e30, 0x046abfb3, 0x7fec7c02, 0x049cfba7, 0x7feab61a, +0x04cf36e5, 0x7fe8dc78, 0x05017165, 0x7fe6ef1c, 0x0533ab20, 0x7fe4ee06, 0x0565e40d, 0x7fe2d938, +0x05981c26, 0x7fe0b0b1, 0x05ca5361, 0x7fde7471, 0x05fc89b8, 0x7fdc247a, 0x062ebf22, 0x7fd9c0ca, +0x0660f398, 0x7fd74964, 0x06932713, 0x7fd4be46, 0x06c5598a, 0x7fd21f72, 0x06f78af6, 0x7fcf6ce8, +0x0729bb4e, 0x7fcca6a7, 0x075bea8c, 0x7fc9ccb2, 0x078e18a7, 0x7fc6df08, 0x07c04598, 0x7fc3dda9, +0x07f27157, 0x7fc0c896, 0x08249bdd, 0x7fbd9fd0, 0x0856c520, 0x7fba6357, 0x0888ed1b, 0x7fb7132b, +0x08bb13c5, 0x7fb3af4e, 0x08ed3916, 0x7fb037bf, 0x091f5d06, 0x7facac7f, 0x09517f8f, 0x7fa90d8e, +0x0983a0a7, 0x7fa55aee, 0x09b5c048, 0x7fa1949e, 0x09e7de6a, 0x7f9dbaa0, 0x0a19fb04, 0x7f99ccf4, +0x0a4c1610, 0x7f95cb9a, 0x0a7e2f85, 0x7f91b694, 0x0ab0475c, 0x7f8d8de1, 0x0ae25d8d, 0x7f895182, +0x0b147211, 0x7f850179, 0x0b4684df, 0x7f809dc5, 0x0b7895f0, 0x7f7c2668, 0x0baaa53b, 0x7f779b62, +0x0bdcb2bb, 0x7f72fcb4, 0x0c0ebe66, 0x7f6e4a5e, 0x0c40c835, 0x7f698461, 0x0c72d020, 0x7f64aabf, +0x0ca4d620, 0x7f5fbd77, 0x0cd6da2d, 0x7f5abc8a, 0x0d08dc3f, 0x7f55a7fa, 0x0d3adc4e, 0x7f507fc7, +0x0d6cda53, 0x7f4b43f2, 0x0d9ed646, 0x7f45f47b, 0x0dd0d01f, 0x7f409164, 0x0e02c7d7, 0x7f3b1aad, +0x0e34bd66, 0x7f359057, 0x0e66b0c3, 0x7f2ff263, 0x0e98a1e9, 0x7f2a40d2, 0x0eca90ce, 0x7f247ba5, +0x0efc7d6b, 0x7f1ea2dc, 0x0f2e67b8, 0x7f18b679, 0x0f604faf, 0x7f12b67c, 0x0f923546, 0x7f0ca2e7, +0x0fc41876, 0x7f067bba, 0x0ff5f938, 0x7f0040f6, 0x1027d784, 0x7ef9f29d, 0x1059b352, 0x7ef390ae, +0x108b8c9b, 0x7eed1b2c, 0x10bd6356, 0x7ee69217, 0x10ef377d, 0x7edff570, 0x11210907, 0x7ed94538, +0x1152d7ed, 0x7ed28171, 0x1184a427, 0x7ecbaa1a, 0x11b66dad, 0x7ec4bf36, 0x11e83478, 0x7ebdc0c6, +0x1219f880, 0x7eb6aeca, 0x124bb9be, 0x7eaf8943, 0x127d7829, 0x7ea85033, 0x12af33ba, 0x7ea1039b, +0x12e0ec6a, 0x7e99a37c, 0x1312a230, 0x7e922fd6, 0x13445505, 0x7e8aa8ac, 0x137604e2, 0x7e830dff, +0x13a7b1bf, 0x7e7b5fce, 0x13d95b93, 0x7e739e1d, 0x140b0258, 0x7e6bc8eb, 0x143ca605, 0x7e63e03b, +0x146e4694, 0x7e5be40c, 0x149fe3fc, 0x7e53d462, 0x14d17e36, 0x7e4bb13c, 0x1503153a, 0x7e437a9c, +0x1534a901, 0x7e3b3083, 0x15663982, 0x7e32d2f4, 0x1597c6b7, 0x7e2a61ed, 0x15c95097, 0x7e21dd73, +0x15fad71b, 0x7e194584, 0x162c5a3b, 0x7e109a24, 0x165dd9f0, 0x7e07db52, 0x168f5632, 0x7dff0911, +0x16c0cef9, 0x7df62362, 0x16f2443e, 0x7ded2a47, 0x1723b5f9, 0x7de41dc0, 0x17552422, 0x7ddafdce, +0x17868eb3, 0x7dd1ca75, 0x17b7f5a3, 0x7dc883b4, 0x17e958ea, 0x7dbf298d, 0x181ab881, 0x7db5bc02, +0x184c1461, 0x7dac3b15, 0x187d6c82, 0x7da2a6c6, 0x18aec0db, 0x7d98ff17, 0x18e01167, 0x7d8f4409, +0x19115e1c, 0x7d85759f, 0x1942a6f3, 0x7d7b93da, 0x1973ebe6, 0x7d719eba, 0x19a52ceb, 0x7d679642, +0x19d669fc, 0x7d5d7a74, 0x1a07a311, 0x7d534b50, 0x1a38d823, 0x7d4908d9, 0x1a6a0929, 0x7d3eb30f, +0x1a9b361d, 0x7d3449f5, 0x1acc5ef6, 0x7d29cd8c, 0x1afd83ad, 0x7d1f3dd6, 0x1b2ea43a, 0x7d149ad5, +0x1b5fc097, 0x7d09e489, 0x1b90d8bb, 0x7cff1af5, 0x1bc1ec9e, 0x7cf43e1a, 0x1bf2fc3a, 0x7ce94dfb, +0x1c240786, 0x7cde4a98, 0x1c550e7c, 0x7cd333f3, 0x1c861113, 0x7cc80a0f, 0x1cb70f43, 0x7cbcccec, +0x1ce80906, 0x7cb17c8d, 0x1d18fe54, 0x7ca618f3, 0x1d49ef26, 0x7c9aa221, 0x1d7adb73, 0x7c8f1817, +0x1dabc334, 0x7c837ad8, 0x1ddca662, 0x7c77ca65, 0x1e0d84f5, 0x7c6c06c0, 0x1e3e5ee5, 0x7c602fec, +0x1e6f342c, 0x7c5445e9, 0x1ea004c1, 0x7c4848ba, 0x1ed0d09d, 0x7c3c3860, 0x1f0197b8, 0x7c3014de, +0x1f325a0b, 0x7c23de35, 0x1f63178f, 0x7c179467, 0x1f93d03c, 0x7c0b3777, 0x1fc4840a, 0x7bfec765, +0x1ff532f2, 0x7bf24434, 0x2025dcec, 0x7be5ade6, 0x205681f1, 0x7bd9047c, 0x208721f9, 0x7bcc47fa, +0x20b7bcfe, 0x7bbf7860, 0x20e852f6, 0x7bb295b0, 0x2118e3dc, 0x7ba59fee, 0x21496fa7, 0x7b989719, +0x2179f64f, 0x7b8b7b36, 0x21aa77cf, 0x7b7e4c45, 0x21daf41d, 0x7b710a49, 0x220b6b32, 0x7b63b543, +0x223bdd08, 0x7b564d36, 0x226c4996, 0x7b48d225, 0x229cb0d5, 0x7b3b4410, 0x22cd12bd, 0x7b2da2fa, +0x22fd6f48, 0x7b1feee5, 0x232dc66d, 0x7b1227d3, 0x235e1826, 0x7b044dc7, 0x238e646a, 0x7af660c2, +0x23beab33, 0x7ae860c7, 0x23eeec78, 0x7ada4dd8, 0x241f2833, 0x7acc27f7, 0x244f5e5c, 0x7abdef25, +0x247f8eec, 0x7aafa367, 0x24afb9da, 0x7aa144bc, 0x24dfdf20, 0x7a92d329, 0x250ffeb7, 0x7a844eae, +0x25401896, 0x7a75b74f, 0x25702cb7, 0x7a670d0d, 0x25a03b11, 0x7a584feb, 0x25d0439f, 0x7a497feb, +0x26004657, 0x7a3a9d0f, 0x26304333, 0x7a2ba75a, 0x26603a2c, 0x7a1c9ece, 0x26902b39, 0x7a0d836d, +0x26c01655, 0x79fe5539, 0x26effb76, 0x79ef1436, 0x271fda96, 0x79dfc064, 0x274fb3ae, 0x79d059c8, +0x277f86b5, 0x79c0e062, 0x27af53a6, 0x79b15435, 0x27df1a77, 0x79a1b545, 0x280edb23, 0x79920392, +0x283e95a1, 0x79823f20, 0x286e49ea, 0x797267f2, 0x289df7f8, 0x79627e08, 0x28cd9fc1, 0x79528167, +0x28fd4140, 0x79427210, 0x292cdc6d, 0x79325006, 0x295c7140, 0x79221b4b, 0x298bffb2, 0x7911d3e2, +0x29bb87bc, 0x790179cd, 0x29eb0957, 0x78f10d0f, 0x2a1a847b, 0x78e08dab, 0x2a49f920, 0x78cffba3, +0x2a796740, 0x78bf56f9, 0x2aa8ced3, 0x78ae9fb0, 0x2ad82fd2, 0x789dd5cb, 0x2b078a36, 0x788cf94c, +0x2b36ddf7, 0x787c0a36, 0x2b662b0e, 0x786b088c, 0x2b957173, 0x7859f44f, 0x2bc4b120, 0x7848cd83, +0x2bf3ea0d, 0x7837942b, 0x2c231c33, 0x78264849, 0x2c52478a, 0x7814e9df, 0x2c816c0c, 0x780378f1, +0x2cb089b1, 0x77f1f581, 0x2cdfa071, 0x77e05f91, 0x2d0eb046, 0x77ceb725, 0x2d3db928, 0x77bcfc3f, +0x2d6cbb10, 0x77ab2ee2, 0x2d9bb5f6, 0x77994f11, 0x2dcaa9d5, 0x77875cce, 0x2df996a3, 0x7775581d, +0x2e287c5a, 0x776340ff, 0x2e575af3, 0x77511778, 0x2e863267, 0x773edb8b, 0x2eb502ae, 0x772c8d3a, +0x2ee3cbc1, 0x771a2c88, 0x2f128d99, 0x7707b979, 0x2f41482e, 0x76f5340e, 0x2f6ffb7a, 0x76e29c4b, +0x2f9ea775, 0x76cff232, 0x2fcd4c19, 0x76bd35c7, 0x2ffbe95d, 0x76aa670d, 0x302a7f3a, 0x76978605, +0x30590dab, 0x768492b4, 0x308794a6, 0x76718d1c, 0x30b61426, 0x765e7540, 0x30e48c22, 0x764b4b23, +0x3112fc95, 0x76380ec8, 0x31416576, 0x7624c031, 0x316fc6be, 0x76115f63, 0x319e2067, 0x75fdec60, +0x31cc7269, 0x75ea672a, 0x31fabcbd, 0x75d6cfc5, 0x3228ff5c, 0x75c32634, 0x32573a3f, 0x75af6a7b, +0x32856d5e, 0x759b9c9b, 0x32b398b3, 0x7587bc98, 0x32e1bc36, 0x7573ca75, 0x330fd7e1, 0x755fc635, +0x333debab, 0x754bafdc, 0x336bf78f, 0x7537876c, 0x3399fb85, 0x75234ce8, 0x33c7f785, 0x750f0054, +0x33f5eb89, 0x74faa1b3, 0x3423d78a, 0x74e63108, 0x3451bb81, 0x74d1ae55, 0x347f9766, 0x74bd199f, +0x34ad6b32, 0x74a872e8, 0x34db36df, 0x7493ba34, 0x3508fa66, 0x747eef85, 0x3536b5be, 0x746a12df, +0x356468e2, 0x74552446, 0x359213c9, 0x744023bc, 0x35bfb66e, 0x742b1144, 0x35ed50c9, 0x7415ece2, +0x361ae2d3, 0x7400b69a, 0x36486c86, 0x73eb6e6e, 0x3675edd9, 0x73d61461, 0x36a366c6, 0x73c0a878, +0x36d0d746, 0x73ab2ab4, 0x36fe3f52, 0x73959b1b, 0x372b9ee3, 0x737ff9ae, 0x3758f5f2, 0x736a4671, +0x37864477, 0x73548168, 0x37b38a6d, 0x733eaa96, 0x37e0c7cc, 0x7328c1ff, 0x380dfc8d, 0x7312c7a5, +0x383b28a9, 0x72fcbb8c, 0x38684c19, 0x72e69db7, 0x389566d6, 0x72d06e2b, 0x38c278d9, 0x72ba2cea, +0x38ef821c, 0x72a3d9f7, 0x391c8297, 0x728d7557, 0x39497a43, 0x7276ff0d, 0x39766919, 0x7260771b, +0x39a34f13, 0x7249dd86, 0x39d02c2a, 0x72333251, 0x39fd0056, 0x721c7580, 0x3a29cb91, 0x7205a716, +0x3a568dd4, 0x71eec716, 0x3a834717, 0x71d7d585, 0x3aaff755, 0x71c0d265, 0x3adc9e86, 0x71a9bdba, +0x3b093ca3, 0x71929789, 0x3b35d1a5, 0x717b5fd3, 0x3b625d86, 0x7164169d, 0x3b8ee03e, 0x714cbbeb, +0x3bbb59c7, 0x71354fc0, 0x3be7ca1a, 0x711dd220, 0x3c143130, 0x7106430e, 0x3c408f03, 0x70eea28e, +0x3c6ce38a, 0x70d6f0a4, 0x3c992ec0, 0x70bf2d53, 0x3cc5709e, 0x70a7589f, 0x3cf1a91c, 0x708f728b, +0x3d1dd835, 0x70777b1c, 0x3d49fde1, 0x705f7255, 0x3d761a19, 0x70475839, 0x3da22cd7, 0x702f2ccd, +0x3dce3614, 0x7016f014, 0x3dfa35c8, 0x6ffea212, 0x3e262bee, 0x6fe642ca, 0x3e52187f, 0x6fcdd241, +0x3e7dfb73, 0x6fb5507a, 0x3ea9d4c3, 0x6f9cbd79, 0x3ed5a46b, 0x6f841942, 0x3f016a61, 0x6f6b63d8, +0x3f2d26a0, 0x6f529d40, 0x3f58d921, 0x6f39c57d, 0x3f8481dd, 0x6f20dc92, 0x3fb020ce, 0x6f07e285, +0x3fdbb5ec, 0x6eeed758, 0x40074132, 0x6ed5bb10, 0x4032c297, 0x6ebc8db0, 0x405e3a16, 0x6ea34f3d, +0x4089a7a8, 0x6e89ffb9, 0x40b50b46, 0x6e709f2a, 0x40e064ea, 0x6e572d93, 0x410bb48c, 0x6e3daaf8, +0x4136fa27, 0x6e24175c, 0x416235b2, 0x6e0a72c5, 0x418d6729, 0x6df0bd35, 0x41b88e84, 0x6dd6f6b1, +0x41e3abbc, 0x6dbd1f3c, 0x420ebecb, 0x6da336dc, 0x4239c7aa, 0x6d893d93, 0x4264c653, 0x6d6f3365, +0x428fbabe, 0x6d551858, 0x42baa4e6, 0x6d3aec6e, 0x42e584c3, 0x6d20afac, 0x43105a50, 0x6d066215, +0x433b2585, 0x6cec03af, 0x4365e65b, 0x6cd1947c, 0x43909ccd, 0x6cb71482, 0x43bb48d4, 0x6c9c83c3, +0x43e5ea68, 0x6c81e245, 0x44108184, 0x6c67300b, 0x443b0e21, 0x6c4c6d1a, 0x44659039, 0x6c319975, +0x449007c4, 0x6c16b521, 0x44ba74bd, 0x6bfbc021, 0x44e4d71c, 0x6be0ba7b, 0x450f2edb, 0x6bc5a431, +0x45397bf4, 0x6baa7d49, 0x4563be60, 0x6b8f45c7, 0x458df619, 0x6b73fdae, 0x45b82318, 0x6b58a503, +0x45e24556, 0x6b3d3bcb, 0x460c5cce, 0x6b21c208, 0x46366978, 0x6b0637c1, 0x46606b4e, 0x6aea9cf8, +0x468a624a, 0x6acef1b2, 0x46b44e65, 0x6ab335f4, 0x46de2f99, 0x6a9769c1, 0x470805df, 0x6a7b8d1e, +0x4731d131, 0x6a5fa010, 0x475b9188, 0x6a43a29a, 0x478546de, 0x6a2794c1, 0x47aef12c, 0x6a0b7689, +0x47d8906d, 0x69ef47f6, 0x48022499, 0x69d3090e, 0x482badab, 0x69b6b9d3, 0x48552b9b, 0x699a5a4c, +0x487e9e64, 0x697dea7b, 0x48a805ff, 0x69616a65, 0x48d16265, 0x6944da10, 0x48fab391, 0x6928397e, +0x4923f97b, 0x690b88b5, 0x494d341e, 0x68eec7b9, 0x49766373, 0x68d1f68f, 0x499f8774, 0x68b5153a, +0x49c8a01b, 0x689823bf, 0x49f1ad61, 0x687b2224, 0x4a1aaf3f, 0x685e106c, 0x4a43a5b0, 0x6840ee9b, +0x4a6c90ad, 0x6823bcb7, 0x4a957030, 0x68067ac3, 0x4abe4433, 0x67e928c5, 0x4ae70caf, 0x67cbc6c0, +0x4b0fc99d, 0x67ae54ba, 0x4b387af9, 0x6790d2b6, 0x4b6120bb, 0x677340ba, 0x4b89badd, 0x67559eca, +0x4bb24958, 0x6737ecea, 0x4bdacc28, 0x671a2b20, 0x4c034345, 0x66fc596f, 0x4c2baea9, 0x66de77dc, +0x4c540e4e, 0x66c0866d, 0x4c7c622d, 0x66a28524, 0x4ca4aa41, 0x66847408, 0x4ccce684, 0x6666531d, +0x4cf516ee, 0x66482267, 0x4d1d3b7a, 0x6629e1ec, 0x4d455422, 0x660b91af, 0x4d6d60df, 0x65ed31b5, +0x4d9561ac, 0x65cec204, 0x4dbd5682, 0x65b0429f, 0x4de53f5a, 0x6591b38c, 0x4e0d1c30, 0x657314cf, +0x4e34ecfc, 0x6554666d, 0x4e5cb1b9, 0x6535a86b, 0x4e846a60, 0x6516dacd, 0x4eac16eb, 0x64f7fd98, +0x4ed3b755, 0x64d910d1, 0x4efb4b96, 0x64ba147d, 0x4f22d3aa, 0x649b08a0, 0x4f4a4f89, 0x647bed3f, +0x4f71bf2e, 0x645cc260, 0x4f992293, 0x643d8806, 0x4fc079b1, 0x641e3e38, 0x4fe7c483, 0x63fee4f8, +0x500f0302, 0x63df7c4d, 0x50363529, 0x63c0043b, 0x505d5af1, 0x63a07cc7, 0x50847454, 0x6380e5f6, +0x50ab814d, 0x63613fcd, 0x50d281d5, 0x63418a50, 0x50f975e6, 0x6321c585, 0x51205d7b, 0x6301f171, +0x5147388c, 0x62e20e17, 0x516e0715, 0x62c21b7e, 0x5194c910, 0x62a219aa, 0x51bb7e75, 0x628208a1, +0x51e22740, 0x6261e866, 0x5208c36a, 0x6241b8ff, 0x522f52ee, 0x62217a72, 0x5255d5c5, 0x62012cc2, +0x527c4bea, 0x61e0cff5, 0x52a2b556, 0x61c06410, 0x52c91204, 0x619fe918, 0x52ef61ee, 0x617f5f12, +0x5315a50e, 0x615ec603, 0x533bdb5d, 0x613e1df0, 0x536204d7, 0x611d66de, 0x53882175, 0x60fca0d2, +0x53ae3131, 0x60dbcbd1, 0x53d43406, 0x60bae7e1, 0x53fa29ed, 0x6099f505, 0x542012e1, 0x6078f344, +0x5445eedb, 0x6057e2a2, 0x546bbdd7, 0x6036c325, 0x54917fce, 0x601594d1, 0x54b734ba, 0x5ff457ad, +0x54dcdc96, 0x5fd30bbc, 0x5502775c, 0x5fb1b104, 0x55280505, 0x5f90478a, 0x554d858d, 0x5f6ecf53, +0x5572f8ed, 0x5f4d4865, 0x55985f20, 0x5f2bb2c5, 0x55bdb81f, 0x5f0a0e77, 0x55e303e6, 0x5ee85b82, +0x5608426e, 0x5ec699e9, 0x562d73b2, 0x5ea4c9b3, 0x565297ab, 0x5e82eae5, 0x5677ae54, 0x5e60fd84, +0x569cb7a8, 0x5e3f0194, 0x56c1b3a1, 0x5e1cf71c, 0x56e6a239, 0x5dfade20, 0x570b8369, 0x5dd8b6a7, +0x5730572e, 0x5db680b4, 0x57551d80, 0x5d943c4e, 0x5779d65b, 0x5d71e979, 0x579e81b8, 0x5d4f883b, +0x57c31f92, 0x5d2d189a, 0x57e7afe4, 0x5d0a9a9a, 0x580c32a7, 0x5ce80e41, 0x5830a7d6, 0x5cc57394, +0x58550f6c, 0x5ca2ca99, 0x58796962, 0x5c801354, 0x589db5b3, 0x5c5d4dcc, 0x58c1f45b, 0x5c3a7a05, +0x58e62552, 0x5c179806, 0x590a4893, 0x5bf4a7d2, 0x592e5e19, 0x5bd1a971, 0x595265df, 0x5bae9ce7, +0x59765fde, 0x5b8b8239, 0x599a4c12, 0x5b68596d, 0x59be2a74, 0x5b452288, 0x59e1faff, 0x5b21dd90, +0x5a05bdae, 0x5afe8a8b, 0x5a29727b, 0x5adb297d, 0x5a4d1960, 0x5ab7ba6c, 0x5a70b258, 0x5a943d5e, +}; + +const int kbdWindowOffset[NUM_IMDCT_SIZES] PROGMEM = {0, 128}; + +const int kbdWindow[128 + 1024] PROGMEM = { +/* 128 - format = Q31 * 2^0 */ +0x00016f63, 0x7ffffffe, 0x0003e382, 0x7ffffff1, 0x00078f64, 0x7fffffc7, 0x000cc323, 0x7fffff5d, +0x0013d9ed, 0x7ffffe76, 0x001d3a9d, 0x7ffffcaa, 0x0029581f, 0x7ffff953, 0x0038b1bd, 0x7ffff372, +0x004bd34d, 0x7fffe98b, 0x00635538, 0x7fffd975, 0x007fdc64, 0x7fffc024, 0x00a219f1, 0x7fff995b, +0x00cacad0, 0x7fff5f5b, 0x00fab72d, 0x7fff0a75, 0x0132b1af, 0x7ffe9091, 0x01739689, 0x7ffde49e, +0x01be4a63, 0x7ffcf5ef, 0x0213b910, 0x7ffbaf84, 0x0274d41e, 0x7ff9f73a, 0x02e2913a, 0x7ff7acf1, +0x035de86c, 0x7ff4a99a, 0x03e7d233, 0x7ff0be3d, 0x0481457c, 0x7febb2f1, 0x052b357c, 0x7fe545d4, +0x05e68f77, 0x7fdd2a02, 0x06b4386f, 0x7fd30695, 0x07950acb, 0x7fc675b4, 0x0889d3ef, 0x7fb703be, +0x099351e0, 0x7fa42e89, 0x0ab230e0, 0x7f8d64d8, 0x0be70923, 0x7f7205f8, 0x0d325c93, 0x7f516195, +0x0e9494ae, 0x7f2ab7d0, 0x100e0085, 0x7efd3997, 0x119ed2ef, 0x7ec8094a, 0x134720d8, 0x7e8a3ba7, +0x1506dfdc, 0x7e42d906, 0x16dde50b, 0x7df0dee4, 0x18cbe3f7, 0x7d9341b4, 0x1ad06e07, 0x7d28ef02, +0x1ceaf215, 0x7cb0cfcc, 0x1f1abc4f, 0x7c29cb20, 0x215ef677, 0x7b92c8eb, 0x23b6a867, 0x7aeab4ec, +0x2620b8ec, 0x7a3081d0, 0x289beef5, 0x79632c5a, 0x2b26f30b, 0x7881be95, 0x2dc0511f, 0x778b5304, +0x30667aa2, 0x767f17c0, 0x3317c8dd, 0x755c5178, 0x35d27f98, 0x74225e50, 0x3894cff3, 0x72d0b887, +0x3b5cdb7b, 0x7166f8e7, 0x3e28b770, 0x6fe4d8e8, 0x40f6702a, 0x6e4a3491, 0x43c40caa, 0x6c970bfc, +0x468f9231, 0x6acb8483, 0x495707f5, 0x68e7e994, 0x4c187ac7, 0x66ecad1c, 0x4ed200c5, 0x64da6797, +0x5181bcea, 0x62b1d7b7, 0x5425e28e, 0x6073e1ae, 0x56bcb8c2, 0x5e218e16, 0x59449d76, 0x5bbc0875, +/* 1024 - format = Q31 * 2^0 */ +0x0009962f, 0x7fffffa4, 0x000e16fb, 0x7fffff39, 0x0011ea65, 0x7ffffebf, 0x0015750e, 0x7ffffe34, +0x0018dc74, 0x7ffffd96, 0x001c332e, 0x7ffffce5, 0x001f83f5, 0x7ffffc1f, 0x0022d59a, 0x7ffffb43, +0x00262cc2, 0x7ffffa4f, 0x00298cc4, 0x7ffff942, 0x002cf81f, 0x7ffff81a, 0x003070c4, 0x7ffff6d6, +0x0033f840, 0x7ffff573, 0x00378fd9, 0x7ffff3f1, 0x003b38a1, 0x7ffff24d, 0x003ef381, 0x7ffff085, +0x0042c147, 0x7fffee98, 0x0046a2a8, 0x7fffec83, 0x004a9847, 0x7fffea44, 0x004ea2b7, 0x7fffe7d8, +0x0052c283, 0x7fffe53f, 0x0056f829, 0x7fffe274, 0x005b4422, 0x7fffdf76, 0x005fa6dd, 0x7fffdc43, +0x006420c8, 0x7fffd8d6, 0x0068b249, 0x7fffd52f, 0x006d5bc4, 0x7fffd149, 0x00721d9a, 0x7fffcd22, +0x0076f828, 0x7fffc8b6, 0x007bebca, 0x7fffc404, 0x0080f8d9, 0x7fffbf06, 0x00861fae, 0x7fffb9bb, +0x008b609e, 0x7fffb41e, 0x0090bbff, 0x7fffae2c, 0x00963224, 0x7fffa7e1, 0x009bc362, 0x7fffa13a, +0x00a17009, 0x7fff9a32, 0x00a7386c, 0x7fff92c5, 0x00ad1cdc, 0x7fff8af0, 0x00b31da8, 0x7fff82ad, +0x00b93b21, 0x7fff79f9, 0x00bf7596, 0x7fff70cf, 0x00c5cd57, 0x7fff672a, 0x00cc42b1, 0x7fff5d05, +0x00d2d5f3, 0x7fff525c, 0x00d9876c, 0x7fff4729, 0x00e05769, 0x7fff3b66, 0x00e74638, 0x7fff2f10, +0x00ee5426, 0x7fff221f, 0x00f58182, 0x7fff148e, 0x00fcce97, 0x7fff0658, 0x01043bb3, 0x7ffef776, +0x010bc923, 0x7ffee7e2, 0x01137733, 0x7ffed795, 0x011b4631, 0x7ffec68a, 0x01233669, 0x7ffeb4ba, +0x012b4827, 0x7ffea21d, 0x01337bb8, 0x7ffe8eac, 0x013bd167, 0x7ffe7a61, 0x01444982, 0x7ffe6533, +0x014ce454, 0x7ffe4f1c, 0x0155a229, 0x7ffe3813, 0x015e834d, 0x7ffe2011, 0x0167880c, 0x7ffe070d, +0x0170b0b2, 0x7ffdecff, 0x0179fd8b, 0x7ffdd1df, 0x01836ee1, 0x7ffdb5a2, 0x018d0500, 0x7ffd9842, +0x0196c035, 0x7ffd79b3, 0x01a0a0ca, 0x7ffd59ee, 0x01aaa70a, 0x7ffd38e8, 0x01b4d341, 0x7ffd1697, +0x01bf25b9, 0x7ffcf2f2, 0x01c99ebd, 0x7ffccdee, 0x01d43e99, 0x7ffca780, 0x01df0597, 0x7ffc7f9e, +0x01e9f401, 0x7ffc563d, 0x01f50a22, 0x7ffc2b51, 0x02004844, 0x7ffbfecf, 0x020baeb1, 0x7ffbd0ab, +0x02173db4, 0x7ffba0da, 0x0222f596, 0x7ffb6f4f, 0x022ed6a1, 0x7ffb3bfd, 0x023ae11f, 0x7ffb06d8, +0x02471558, 0x7ffacfd3, 0x02537397, 0x7ffa96e0, 0x025ffc25, 0x7ffa5bf2, 0x026caf4a, 0x7ffa1efc, +0x02798d4f, 0x7ff9dfee, 0x0286967c, 0x7ff99ebb, 0x0293cb1b, 0x7ff95b55, 0x02a12b72, 0x7ff915ab, +0x02aeb7cb, 0x7ff8cdaf, 0x02bc706d, 0x7ff88351, 0x02ca559f, 0x7ff83682, 0x02d867a9, 0x7ff7e731, +0x02e6a6d2, 0x7ff7954e, 0x02f51361, 0x7ff740c8, 0x0303ad9c, 0x7ff6e98e, 0x031275ca, 0x7ff68f8f, +0x03216c30, 0x7ff632ba, 0x03309116, 0x7ff5d2fb, 0x033fe4bf, 0x7ff57042, 0x034f6773, 0x7ff50a7a, +0x035f1975, 0x7ff4a192, 0x036efb0a, 0x7ff43576, 0x037f0c78, 0x7ff3c612, 0x038f4e02, 0x7ff35353, +0x039fbfeb, 0x7ff2dd24, 0x03b06279, 0x7ff26370, 0x03c135ed, 0x7ff1e623, 0x03d23a8b, 0x7ff16527, +0x03e37095, 0x7ff0e067, 0x03f4d84e, 0x7ff057cc, 0x040671f7, 0x7fefcb40, 0x04183dd3, 0x7fef3aad, +0x042a3c22, 0x7feea5fa, 0x043c6d25, 0x7fee0d11, 0x044ed11d, 0x7fed6fda, 0x04616849, 0x7fecce3d, +0x047432eb, 0x7fec2821, 0x04873140, 0x7feb7d6c, 0x049a6388, 0x7feace07, 0x04adca01, 0x7fea19d6, +0x04c164ea, 0x7fe960c0, 0x04d53481, 0x7fe8a2aa, 0x04e93902, 0x7fe7df79, 0x04fd72aa, 0x7fe71712, +0x0511e1b6, 0x7fe6495a, 0x05268663, 0x7fe57634, 0x053b60eb, 0x7fe49d83, 0x05507189, 0x7fe3bf2b, +0x0565b879, 0x7fe2db0f, 0x057b35f4, 0x7fe1f110, 0x0590ea35, 0x7fe10111, 0x05a6d574, 0x7fe00af3, +0x05bcf7ea, 0x7fdf0e97, 0x05d351cf, 0x7fde0bdd, 0x05e9e35c, 0x7fdd02a6, 0x0600acc8, 0x7fdbf2d2, +0x0617ae48, 0x7fdadc40, 0x062ee814, 0x7fd9becf, 0x06465a62, 0x7fd89a5e, 0x065e0565, 0x7fd76eca, +0x0675e954, 0x7fd63bf1, 0x068e0662, 0x7fd501b0, 0x06a65cc3, 0x7fd3bfe4, 0x06beecaa, 0x7fd2766a, +0x06d7b648, 0x7fd1251e, 0x06f0b9d1, 0x7fcfcbda, 0x0709f775, 0x7fce6a7a, 0x07236f65, 0x7fcd00d8, +0x073d21d2, 0x7fcb8ecf, 0x07570eea, 0x7fca1439, 0x077136dd, 0x7fc890ed, 0x078b99da, 0x7fc704c7, +0x07a6380d, 0x7fc56f9d, 0x07c111a4, 0x7fc3d147, 0x07dc26cc, 0x7fc2299e, 0x07f777b1, 0x7fc07878, +0x0813047d, 0x7fbebdac, 0x082ecd5b, 0x7fbcf90f, 0x084ad276, 0x7fbb2a78, 0x086713f7, 0x7fb951bc, +0x08839206, 0x7fb76eaf, 0x08a04ccb, 0x7fb58126, 0x08bd446e, 0x7fb388f4, 0x08da7915, 0x7fb185ee, +0x08f7eae7, 0x7faf77e5, 0x09159a09, 0x7fad5ead, 0x0933869f, 0x7fab3a17, 0x0951b0cd, 0x7fa909f6, +0x097018b7, 0x7fa6ce1a, 0x098ebe7f, 0x7fa48653, 0x09ada248, 0x7fa23273, 0x09ccc431, 0x7f9fd249, +0x09ec245b, 0x7f9d65a4, 0x0a0bc2e7, 0x7f9aec53, 0x0a2b9ff3, 0x7f986625, 0x0a4bbb9e, 0x7f95d2e7, +0x0a6c1604, 0x7f933267, 0x0a8caf43, 0x7f908472, 0x0aad8776, 0x7f8dc8d5, 0x0ace9eb9, 0x7f8aff5c, +0x0aeff526, 0x7f8827d3, 0x0b118ad8, 0x7f854204, 0x0b335fe6, 0x7f824dbb, 0x0b557469, 0x7f7f4ac3, +0x0b77c879, 0x7f7c38e4, 0x0b9a5c2b, 0x7f7917e9, 0x0bbd2f97, 0x7f75e79b, 0x0be042d0, 0x7f72a7c3, +0x0c0395ec, 0x7f6f5828, 0x0c2728fd, 0x7f6bf892, 0x0c4afc16, 0x7f6888c9, 0x0c6f0f4a, 0x7f650894, +0x0c9362a8, 0x7f6177b9, 0x0cb7f642, 0x7f5dd5ff, 0x0cdcca26, 0x7f5a232a, 0x0d01de63, 0x7f565f00, +0x0d273307, 0x7f528947, 0x0d4cc81f, 0x7f4ea1c2, 0x0d729db7, 0x7f4aa835, 0x0d98b3da, 0x7f469c65, +0x0dbf0a92, 0x7f427e13, 0x0de5a1e9, 0x7f3e4d04, 0x0e0c79e7, 0x7f3a08f9, 0x0e339295, 0x7f35b1b4, +0x0e5aebfa, 0x7f3146f8, 0x0e82861a, 0x7f2cc884, 0x0eaa60fd, 0x7f28361b, 0x0ed27ca5, 0x7f238f7c, +0x0efad917, 0x7f1ed467, 0x0f237656, 0x7f1a049d, 0x0f4c5462, 0x7f151fdc, 0x0f75733d, 0x7f1025e3, +0x0f9ed2e6, 0x7f0b1672, 0x0fc8735e, 0x7f05f146, 0x0ff254a1, 0x7f00b61d, 0x101c76ae, 0x7efb64b4, +0x1046d981, 0x7ef5fcca, 0x10717d15, 0x7ef07e19, 0x109c6165, 0x7eeae860, 0x10c7866a, 0x7ee53b5b, +0x10f2ec1e, 0x7edf76c4, 0x111e9279, 0x7ed99a58, 0x114a7971, 0x7ed3a5d1, 0x1176a0fc, 0x7ecd98eb, +0x11a30910, 0x7ec77360, 0x11cfb1a1, 0x7ec134eb, 0x11fc9aa2, 0x7ebadd44, 0x1229c406, 0x7eb46c27, +0x12572dbf, 0x7eade14c, 0x1284d7bc, 0x7ea73c6c, 0x12b2c1ed, 0x7ea07d41, 0x12e0ec42, 0x7e99a382, +0x130f56a8, 0x7e92aee7, 0x133e010b, 0x7e8b9f2a, 0x136ceb59, 0x7e847402, 0x139c157b, 0x7e7d2d25, +0x13cb7f5d, 0x7e75ca4c, 0x13fb28e6, 0x7e6e4b2d, 0x142b1200, 0x7e66af7f, 0x145b3a92, 0x7e5ef6f8, +0x148ba281, 0x7e572150, 0x14bc49b4, 0x7e4f2e3b, 0x14ed300f, 0x7e471d70, 0x151e5575, 0x7e3eeea5, +0x154fb9c9, 0x7e36a18e, 0x15815ced, 0x7e2e35e2, 0x15b33ec1, 0x7e25ab56, 0x15e55f25, 0x7e1d019e, +0x1617bdf9, 0x7e14386e, 0x164a5b19, 0x7e0b4f7d, 0x167d3662, 0x7e02467e, 0x16b04fb2, 0x7df91d25, +0x16e3a6e2, 0x7defd327, 0x17173bce, 0x7de66837, 0x174b0e4d, 0x7ddcdc0a, 0x177f1e39, 0x7dd32e53, +0x17b36b69, 0x7dc95ec6, 0x17e7f5b3, 0x7dbf6d17, 0x181cbcec, 0x7db558f9, 0x1851c0e9, 0x7dab221f, +0x1887017d, 0x7da0c83c, 0x18bc7e7c, 0x7d964b05, 0x18f237b6, 0x7d8baa2b, 0x19282cfd, 0x7d80e563, +0x195e5e20, 0x7d75fc5e, 0x1994caee, 0x7d6aeed0, 0x19cb7335, 0x7d5fbc6d, 0x1a0256c2, 0x7d5464e6, +0x1a397561, 0x7d48e7ef, 0x1a70cede, 0x7d3d453b, 0x1aa86301, 0x7d317c7c, 0x1ae03195, 0x7d258d65, +0x1b183a63, 0x7d1977aa, 0x1b507d30, 0x7d0d3afc, 0x1b88f9c5, 0x7d00d710, 0x1bc1afe6, 0x7cf44b97, +0x1bfa9f58, 0x7ce79846, 0x1c33c7e0, 0x7cdabcce, 0x1c6d293f, 0x7ccdb8e4, 0x1ca6c337, 0x7cc08c39, +0x1ce0958a, 0x7cb33682, 0x1d1a9ff8, 0x7ca5b772, 0x1d54e240, 0x7c980ebd, 0x1d8f5c21, 0x7c8a3c14, +0x1dca0d56, 0x7c7c3f2e, 0x1e04f59f, 0x7c6e17bc, 0x1e4014b4, 0x7c5fc573, 0x1e7b6a53, 0x7c514807, +0x1eb6f633, 0x7c429f2c, 0x1ef2b80f, 0x7c33ca96, 0x1f2eaf9e, 0x7c24c9fa, 0x1f6adc98, 0x7c159d0d, +0x1fa73eb2, 0x7c064383, 0x1fe3d5a3, 0x7bf6bd11, 0x2020a11e, 0x7be7096c, 0x205da0d8, 0x7bd7284a, +0x209ad483, 0x7bc71960, 0x20d83bd1, 0x7bb6dc65, 0x2115d674, 0x7ba6710d, 0x2153a41b, 0x7b95d710, +0x2191a476, 0x7b850e24, 0x21cfd734, 0x7b7415ff, 0x220e3c02, 0x7b62ee59, 0x224cd28d, 0x7b5196e9, +0x228b9a82, 0x7b400f67, 0x22ca938a, 0x7b2e578a, 0x2309bd52, 0x7b1c6f0b, 0x23491783, 0x7b0a55a1, +0x2388a1c4, 0x7af80b07, 0x23c85bbf, 0x7ae58ef5, 0x2408451a, 0x7ad2e124, 0x24485d7c, 0x7ac0014e, +0x2488a48a, 0x7aacef2e, 0x24c919e9, 0x7a99aa7e, 0x2509bd3d, 0x7a8632f8, 0x254a8e29, 0x7a728858, +0x258b8c50, 0x7a5eaa5a, 0x25ccb753, 0x7a4a98b9, 0x260e0ed3, 0x7a365333, 0x264f9271, 0x7a21d983, +0x269141cb, 0x7a0d2b68, 0x26d31c80, 0x79f8489e, 0x2715222f, 0x79e330e4, 0x27575273, 0x79cde3f8, +0x2799acea, 0x79b8619a, 0x27dc3130, 0x79a2a989, 0x281ededf, 0x798cbb85, 0x2861b591, 0x7976974e, +0x28a4b4e0, 0x79603ca5, 0x28e7dc65, 0x7949ab4c, 0x292b2bb8, 0x7932e304, 0x296ea270, 0x791be390, +0x29b24024, 0x7904acb3, 0x29f6046b, 0x78ed3e30, 0x2a39eed8, 0x78d597cc, 0x2a7dff02, 0x78bdb94a, +0x2ac2347c, 0x78a5a270, 0x2b068eda, 0x788d5304, 0x2b4b0dae, 0x7874cacb, 0x2b8fb08a, 0x785c098d, +0x2bd47700, 0x78430f11, 0x2c1960a1, 0x7829db1f, 0x2c5e6cfd, 0x78106d7f, 0x2ca39ba3, 0x77f6c5fb, +0x2ce8ec23, 0x77dce45c, 0x2d2e5e0b, 0x77c2c86e, 0x2d73f0e8, 0x77a871fa, 0x2db9a449, 0x778de0cd, +0x2dff77b8, 0x777314b2, 0x2e456ac4, 0x77580d78, 0x2e8b7cf6, 0x773ccaeb, 0x2ed1addb, 0x77214cdb, +0x2f17fcfb, 0x77059315, 0x2f5e69e2, 0x76e99d69, 0x2fa4f419, 0x76cd6ba9, 0x2feb9b27, 0x76b0fda4, +0x30325e96, 0x7694532e, 0x30793dee, 0x76776c17, 0x30c038b5, 0x765a4834, 0x31074e72, 0x763ce759, +0x314e7eab, 0x761f4959, 0x3195c8e6, 0x76016e0b, 0x31dd2ca9, 0x75e35545, 0x3224a979, 0x75c4fedc, +0x326c3ed8, 0x75a66aab, 0x32b3ec4d, 0x75879887, 0x32fbb159, 0x7568884b, 0x33438d81, 0x754939d1, +0x338b8045, 0x7529acf4, 0x33d3892a, 0x7509e18e, 0x341ba7b1, 0x74e9d77d, 0x3463db5a, 0x74c98e9e, +0x34ac23a7, 0x74a906cd, 0x34f48019, 0x74883fec, 0x353cf02f, 0x746739d8, 0x3585736a, 0x7445f472, +0x35ce0949, 0x74246f9c, 0x3616b14c, 0x7402ab37, 0x365f6af0, 0x73e0a727, 0x36a835b5, 0x73be6350, +0x36f11118, 0x739bdf95, 0x3739fc98, 0x73791bdd, 0x3782f7b2, 0x7356180e, 0x37cc01e3, 0x7332d410, +0x38151aa8, 0x730f4fc9, 0x385e417e, 0x72eb8b24, 0x38a775e1, 0x72c7860a, 0x38f0b74d, 0x72a34066, +0x393a053e, 0x727eba24, 0x39835f30, 0x7259f331, 0x39ccc49e, 0x7234eb79, 0x3a163503, 0x720fa2eb, +0x3a5fafda, 0x71ea1977, 0x3aa9349e, 0x71c44f0c, 0x3af2c2ca, 0x719e439d, 0x3b3c59d7, 0x7177f71a, +0x3b85f940, 0x71516978, 0x3bcfa07e, 0x712a9aaa, 0x3c194f0d, 0x71038aa4, 0x3c630464, 0x70dc395e, +0x3cacbfff, 0x70b4a6cd, 0x3cf68155, 0x708cd2e9, 0x3d4047e1, 0x7064bdab, 0x3d8a131c, 0x703c670d, +0x3dd3e27e, 0x7013cf0a, 0x3e1db580, 0x6feaf59c, 0x3e678b9b, 0x6fc1dac1, 0x3eb16449, 0x6f987e76, +0x3efb3f01, 0x6f6ee0b9, 0x3f451b3d, 0x6f45018b, 0x3f8ef874, 0x6f1ae0eb, 0x3fd8d620, 0x6ef07edb, +0x4022b3b9, 0x6ec5db5d, 0x406c90b7, 0x6e9af675, 0x40b66c93, 0x6e6fd027, 0x410046c5, 0x6e446879, +0x414a1ec6, 0x6e18bf71, 0x4193f40d, 0x6decd517, 0x41ddc615, 0x6dc0a972, 0x42279455, 0x6d943c8d, +0x42715e45, 0x6d678e71, 0x42bb235f, 0x6d3a9f2a, 0x4304e31a, 0x6d0d6ec5, 0x434e9cf1, 0x6cdffd4f, +0x4398505b, 0x6cb24ad6, 0x43e1fcd1, 0x6c84576b, 0x442ba1cd, 0x6c56231c, 0x44753ec7, 0x6c27adfd, +0x44bed33a, 0x6bf8f81e, 0x45085e9d, 0x6bca0195, 0x4551e06b, 0x6b9aca75, 0x459b581e, 0x6b6b52d5, +0x45e4c52f, 0x6b3b9ac9, 0x462e2717, 0x6b0ba26b, 0x46777d52, 0x6adb69d3, 0x46c0c75a, 0x6aaaf11b, +0x470a04a9, 0x6a7a385c, 0x475334b9, 0x6a493fb3, 0x479c5707, 0x6a18073d, 0x47e56b0c, 0x69e68f17, +0x482e7045, 0x69b4d761, 0x4877662c, 0x6982e039, 0x48c04c3f, 0x6950a9c0, 0x490921f8, 0x691e341a, +0x4951e6d5, 0x68eb7f67, 0x499a9a51, 0x68b88bcd, 0x49e33beb, 0x68855970, 0x4a2bcb1f, 0x6851e875, +0x4a74476b, 0x681e3905, 0x4abcb04c, 0x67ea4b47, 0x4b050541, 0x67b61f63, 0x4b4d45c9, 0x6781b585, +0x4b957162, 0x674d0dd6, 0x4bdd878c, 0x67182883, 0x4c2587c6, 0x66e305b8, 0x4c6d7190, 0x66ada5a5, +0x4cb5446a, 0x66780878, 0x4cfcffd5, 0x66422e60, 0x4d44a353, 0x660c1790, 0x4d8c2e64, 0x65d5c439, +0x4dd3a08c, 0x659f348e, 0x4e1af94b, 0x656868c3, 0x4e623825, 0x6531610d, 0x4ea95c9d, 0x64fa1da3, +0x4ef06637, 0x64c29ebb, 0x4f375477, 0x648ae48d, 0x4f7e26e1, 0x6452ef53, 0x4fc4dcfb, 0x641abf46, +0x500b7649, 0x63e254a2, 0x5051f253, 0x63a9afa2, 0x5098509f, 0x6370d083, 0x50de90b3, 0x6337b784, +0x5124b218, 0x62fe64e3, 0x516ab455, 0x62c4d8e0, 0x51b096f3, 0x628b13bc, 0x51f6597b, 0x625115b8, +0x523bfb78, 0x6216df18, 0x52817c72, 0x61dc701f, 0x52c6dbf5, 0x61a1c912, 0x530c198d, 0x6166ea36, +0x535134c5, 0x612bd3d2, 0x53962d2a, 0x60f0862d, 0x53db024a, 0x60b50190, 0x541fb3b1, 0x60794644, +0x546440ef, 0x603d5494, 0x54a8a992, 0x60012cca, 0x54eced2b, 0x5fc4cf33, 0x55310b48, 0x5f883c1c, +0x5575037c, 0x5f4b73d2, 0x55b8d558, 0x5f0e76a5, 0x55fc806f, 0x5ed144e5, 0x56400452, 0x5e93dee1, +0x56836096, 0x5e5644ec, 0x56c694cf, 0x5e187757, 0x5709a092, 0x5dda7677, 0x574c8374, 0x5d9c429f, +0x578f3d0d, 0x5d5ddc24, 0x57d1ccf2, 0x5d1f435d, 0x581432bd, 0x5ce078a0, 0x58566e04, 0x5ca17c45, +0x58987e63, 0x5c624ea4, 0x58da6372, 0x5c22f016, 0x591c1ccc, 0x5be360f6, 0x595daa0d, 0x5ba3a19f, +0x599f0ad1, 0x5b63b26c, 0x59e03eb6, 0x5b2393ba, 0x5a214558, 0x5ae345e7, 0x5a621e56, 0x5aa2c951, +}; +/* bit reverse tables for FFT */ +const uint8_t bitrevtabOffset[NUM_IMDCT_SIZES] PROGMEM = {0, 17}; +const uint8_t bitrevtab[17 + 129] PROGMEM = { +/* nfft = 64 */ +0x01, 0x08, 0x02, 0x04, 0x03, 0x0c, 0x05, 0x0a, 0x07, 0x0e, 0x0b, 0x0d, 0x00, 0x06, 0x09, 0x0f, +0x00, +/* nfft = 512 */ +0x01, 0x40, 0x02, 0x20, 0x03, 0x60, 0x04, 0x10, 0x05, 0x50, 0x06, 0x30, 0x07, 0x70, 0x09, 0x48, +0x0a, 0x28, 0x0b, 0x68, 0x0c, 0x18, 0x0d, 0x58, 0x0e, 0x38, 0x0f, 0x78, 0x11, 0x44, 0x12, 0x24, +0x13, 0x64, 0x15, 0x54, 0x16, 0x34, 0x17, 0x74, 0x19, 0x4c, 0x1a, 0x2c, 0x1b, 0x6c, 0x1d, 0x5c, +0x1e, 0x3c, 0x1f, 0x7c, 0x21, 0x42, 0x23, 0x62, 0x25, 0x52, 0x26, 0x32, 0x27, 0x72, 0x29, 0x4a, +0x2b, 0x6a, 0x2d, 0x5a, 0x2e, 0x3a, 0x2f, 0x7a, 0x31, 0x46, 0x33, 0x66, 0x35, 0x56, 0x37, 0x76, +0x39, 0x4e, 0x3b, 0x6e, 0x3d, 0x5e, 0x3f, 0x7e, 0x43, 0x61, 0x45, 0x51, 0x47, 0x71, 0x4b, 0x69, +0x4d, 0x59, 0x4f, 0x79, 0x53, 0x65, 0x57, 0x75, 0x5b, 0x6d, 0x5f, 0x7d, 0x67, 0x73, 0x6f, 0x7b, +0x00, 0x08, 0x14, 0x1c, 0x22, 0x2a, 0x36, 0x3e, 0x41, 0x49, 0x55, 0x5d, 0x63, 0x6b, 0x77, 0x7f, +0x00, +}; + +const uint8_t uniqueIDTab[8] = {0x5f, 0x4b, 0x43, 0x5f, 0x5f, 0x4a, 0x52, 0x5f}; + +const uint32_t twidTabOdd[8*6 + 32*6 + 128*6] PROGMEM = { + 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x539eba45, 0xe7821d59, + 0x4b418bbe, 0xf383a3e2, 0x58c542c5, 0xdc71898d, 0x5a82799a, 0xd2bec333, 0x539eba45, 0xe7821d59, + 0x539eba45, 0xc4df2862, 0x539eba45, 0xc4df2862, 0x58c542c5, 0xdc71898d, 0x3248d382, 0xc13ad060, + 0x40000000, 0xc0000000, 0x5a82799a, 0xd2bec333, 0x00000000, 0xd2bec333, 0x22a2f4f8, 0xc4df2862, + 0x58c542c5, 0xcac933ae, 0xcdb72c7e, 0xf383a3e2, 0x00000000, 0xd2bec333, 0x539eba45, 0xc4df2862, + 0xac6145bb, 0x187de2a7, 0xdd5d0b08, 0xe7821d59, 0x4b418bbe, 0xc13ad060, 0xa73abd3b, 0x3536cc52, + 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x45f704f7, 0xf9ba1651, + 0x43103085, 0xfcdc1342, 0x48b2b335, 0xf69bf7c9, 0x4b418bbe, 0xf383a3e2, 0x45f704f7, 0xf9ba1651, + 0x4fd288dc, 0xed6bf9d1, 0x4fd288dc, 0xed6bf9d1, 0x48b2b335, 0xf69bf7c9, 0x553805f2, 0xe4a2eff6, + 0x539eba45, 0xe7821d59, 0x4b418bbe, 0xf383a3e2, 0x58c542c5, 0xdc71898d, 0x569cc31b, 0xe1d4a2c8, + 0x4da1fab5, 0xf0730342, 0x5a6690ae, 0xd5052d97, 0x58c542c5, 0xdc71898d, 0x4fd288dc, 0xed6bf9d1, + 0x5a12e720, 0xce86ff2a, 0x5a12e720, 0xd76619b6, 0x51d1dc80, 0xea70658a, 0x57cc15bc, 0xc91af976, + 0x5a82799a, 0xd2bec333, 0x539eba45, 0xe7821d59, 0x539eba45, 0xc4df2862, 0x5a12e720, 0xce86ff2a, + 0x553805f2, 0xe4a2eff6, 0x4da1fab5, 0xc1eb0209, 0x58c542c5, 0xcac933ae, 0x569cc31b, 0xe1d4a2c8, + 0x45f704f7, 0xc04ee4b8, 0x569cc31b, 0xc78e9a1d, 0x57cc15bc, 0xdf18f0ce, 0x3cc85709, 0xc013bc39, + 0x539eba45, 0xc4df2862, 0x58c542c5, 0xdc71898d, 0x3248d382, 0xc13ad060, 0x4fd288dc, 0xc2c17d52, + 0x5987b08a, 0xd9e01006, 0x26b2a794, 0xc3bdbdf6, 0x4b418bbe, 0xc13ad060, 0x5a12e720, 0xd76619b6, + 0x1a4608ab, 0xc78e9a1d, 0x45f704f7, 0xc04ee4b8, 0x5a6690ae, 0xd5052d97, 0x0d47d096, 0xcc983f70, + 0x40000000, 0xc0000000, 0x5a82799a, 0xd2bec333, 0x00000000, 0xd2bec333, 0x396b3199, 0xc04ee4b8, + 0x5a6690ae, 0xd09441bb, 0xf2b82f6a, 0xd9e01006, 0x3248d382, 0xc13ad060, 0x5a12e720, 0xce86ff2a, + 0xe5b9f755, 0xe1d4a2c8, 0x2aaa7c7f, 0xc2c17d52, 0x5987b08a, 0xcc983f70, 0xd94d586c, 0xea70658a, + 0x22a2f4f8, 0xc4df2862, 0x58c542c5, 0xcac933ae, 0xcdb72c7e, 0xf383a3e2, 0x1a4608ab, 0xc78e9a1d, + 0x57cc15bc, 0xc91af976, 0xc337a8f7, 0xfcdc1342, 0x11a855df, 0xcac933ae, 0x569cc31b, 0xc78e9a1d, + 0xba08fb09, 0x0645e9af, 0x08df1a8c, 0xce86ff2a, 0x553805f2, 0xc6250a18, 0xb25e054b, 0x0f8cfcbe, + 0x00000000, 0xd2bec333, 0x539eba45, 0xc4df2862, 0xac6145bb, 0x187de2a7, 0xf720e574, 0xd76619b6, + 0x51d1dc80, 0xc3bdbdf6, 0xa833ea44, 0x20e70f32, 0xee57aa21, 0xdc71898d, 0x4fd288dc, 0xc2c17d52, + 0xa5ed18e0, 0x2899e64a, 0xe5b9f755, 0xe1d4a2c8, 0x4da1fab5, 0xc1eb0209, 0xa5996f52, 0x2f6bbe45, + 0xdd5d0b08, 0xe7821d59, 0x4b418bbe, 0xc13ad060, 0xa73abd3b, 0x3536cc52, 0xd5558381, 0xed6bf9d1, + 0x48b2b335, 0xc0b15502, 0xaac7fa0e, 0x39daf5e8, 0xcdb72c7e, 0xf383a3e2, 0x45f704f7, 0xc04ee4b8, + 0xb02d7724, 0x3d3e82ae, 0xc694ce67, 0xf9ba1651, 0x43103085, 0xc013bc39, 0xb74d4ccb, 0x3f4eaafe, + 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x418d2621, 0xfe6deaa1, + 0x40c7d2bd, 0xff36f170, 0x424ff28f, 0xfda4f351, 0x43103085, 0xfcdc1342, 0x418d2621, 0xfe6deaa1, + 0x4488e37f, 0xfb4ab7db, 0x4488e37f, 0xfb4ab7db, 0x424ff28f, 0xfda4f351, 0x46aa0d6d, 0xf8f21e8e, + 0x45f704f7, 0xf9ba1651, 0x43103085, 0xfcdc1342, 0x48b2b335, 0xf69bf7c9, 0x475a5c77, 0xf82a6c6a, + 0x43cdd89a, 0xfc135231, 0x4aa22036, 0xf4491311, 0x48b2b335, 0xf69bf7c9, 0x4488e37f, 0xfb4ab7db, + 0x4c77a88e, 0xf1fa3ecb, 0x49ffd417, 0xf50ef5de, 0x454149fc, 0xfa824bfd, 0x4e32a956, 0xefb047f2, + 0x4b418bbe, 0xf383a3e2, 0x45f704f7, 0xf9ba1651, 0x4fd288dc, 0xed6bf9d1, 0x4c77a88e, 0xf1fa3ecb, + 0x46aa0d6d, 0xf8f21e8e, 0x5156b6d9, 0xeb2e1dbe, 0x4da1fab5, 0xf0730342, 0x475a5c77, 0xf82a6c6a, + 0x52beac9f, 0xe8f77acf, 0x4ec05432, 0xeeee2d9d, 0x4807eb4b, 0xf7630799, 0x5409ed4b, 0xe6c8d59c, + 0x4fd288dc, 0xed6bf9d1, 0x48b2b335, 0xf69bf7c9, 0x553805f2, 0xe4a2eff6, 0x50d86e6d, 0xebeca36c, + 0x495aada2, 0xf5d544a7, 0x56488dc5, 0xe28688a4, 0x51d1dc80, 0xea70658a, 0x49ffd417, 0xf50ef5de, + 0x573b2635, 0xe0745b24, 0x52beac9f, 0xe8f77acf, 0x4aa22036, 0xf4491311, 0x580f7b19, 0xde6d1f65, + 0x539eba45, 0xe7821d59, 0x4b418bbe, 0xf383a3e2, 0x58c542c5, 0xdc71898d, 0x5471e2e6, 0xe61086bc, + 0x4bde1089, 0xf2beafed, 0x595c3e2a, 0xda8249b4, 0x553805f2, 0xe4a2eff6, 0x4c77a88e, 0xf1fa3ecb, + 0x59d438e5, 0xd8a00bae, 0x55f104dc, 0xe3399167, 0x4d0e4de2, 0xf136580d, 0x5a2d0957, 0xd6cb76c9, + 0x569cc31b, 0xe1d4a2c8, 0x4da1fab5, 0xf0730342, 0x5a6690ae, 0xd5052d97, 0x573b2635, 0xe0745b24, + 0x4e32a956, 0xefb047f2, 0x5a80baf6, 0xd34dcdb4, 0x57cc15bc, 0xdf18f0ce, 0x4ec05432, 0xeeee2d9d, + 0x5a7b7f1a, 0xd1a5ef90, 0x584f7b58, 0xddc29958, 0x4f4af5d1, 0xee2cbbc1, 0x5a56deec, 0xd00e2639, + 0x58c542c5, 0xdc71898d, 0x4fd288dc, 0xed6bf9d1, 0x5a12e720, 0xce86ff2a, 0x592d59da, 0xdb25f566, + 0x50570819, 0xecabef3d, 0x59afaf4c, 0xcd110216, 0x5987b08a, 0xd9e01006, 0x50d86e6d, 0xebeca36c, + 0x592d59da, 0xcbacb0bf, 0x59d438e5, 0xd8a00bae, 0x5156b6d9, 0xeb2e1dbe, 0x588c1404, 0xca5a86c4, + 0x5a12e720, 0xd76619b6, 0x51d1dc80, 0xea70658a, 0x57cc15bc, 0xc91af976, 0x5a43b190, 0xd6326a88, + 0x5249daa2, 0xe9b38223, 0x56eda1a0, 0xc7ee77b3, 0x5a6690ae, 0xd5052d97, 0x52beac9f, 0xe8f77acf, + 0x55f104dc, 0xc6d569be, 0x5a7b7f1a, 0xd3de9156, 0x53304df6, 0xe83c56cf, 0x54d69714, 0xc5d03118, + 0x5a82799a, 0xd2bec333, 0x539eba45, 0xe7821d59, 0x539eba45, 0xc4df2862, 0x5a7b7f1a, 0xd1a5ef90, + 0x5409ed4b, 0xe6c8d59c, 0x5249daa2, 0xc402a33c, 0x5a6690ae, 0xd09441bb, 0x5471e2e6, 0xe61086bc, + 0x50d86e6d, 0xc33aee27, 0x5a43b190, 0xcf89e3e8, 0x54d69714, 0xe55937d5, 0x4f4af5d1, 0xc2884e6e, + 0x5a12e720, 0xce86ff2a, 0x553805f2, 0xe4a2eff6, 0x4da1fab5, 0xc1eb0209, 0x59d438e5, 0xcd8bbb6d, + 0x55962bc0, 0xe3edb628, 0x4bde1089, 0xc1633f8a, 0x5987b08a, 0xcc983f70, 0x55f104dc, 0xe3399167, + 0x49ffd417, 0xc0f1360b, 0x592d59da, 0xcbacb0bf, 0x56488dc5, 0xe28688a4, 0x4807eb4b, 0xc0950d1d, + 0x58c542c5, 0xcac933ae, 0x569cc31b, 0xe1d4a2c8, 0x45f704f7, 0xc04ee4b8, 0x584f7b58, 0xc9edeb50, + 0x56eda1a0, 0xe123e6ad, 0x43cdd89a, 0xc01ed535, 0x57cc15bc, 0xc91af976, 0x573b2635, 0xe0745b24, + 0x418d2621, 0xc004ef3f, 0x573b2635, 0xc8507ea7, 0x57854ddd, 0xdfc606f1, 0x3f35b59d, 0xc0013bd3, + 0x569cc31b, 0xc78e9a1d, 0x57cc15bc, 0xdf18f0ce, 0x3cc85709, 0xc013bc39, 0x55f104dc, 0xc6d569be, + 0x580f7b19, 0xde6d1f65, 0x3a45e1f7, 0xc03c6a07, 0x553805f2, 0xc6250a18, 0x584f7b58, 0xddc29958, + 0x37af354c, 0xc07b371e, 0x5471e2e6, 0xc57d965d, 0x588c1404, 0xdd196538, 0x350536f1, 0xc0d00db6, + 0x539eba45, 0xc4df2862, 0x58c542c5, 0xdc71898d, 0x3248d382, 0xc13ad060, 0x52beac9f, 0xc449d892, + 0x58fb0568, 0xdbcb0cce, 0x2f7afdfc, 0xc1bb5a11, 0x51d1dc80, 0xc3bdbdf6, 0x592d59da, 0xdb25f566, + 0x2c9caf6c, 0xc2517e31, 0x50d86e6d, 0xc33aee27, 0x595c3e2a, 0xda8249b4, 0x29aee694, 0xc2fd08a9, + 0x4fd288dc, 0xc2c17d52, 0x5987b08a, 0xd9e01006, 0x26b2a794, 0xc3bdbdf6, 0x4ec05432, 0xc2517e31, + 0x59afaf4c, 0xd93f4e9e, 0x23a8fb93, 0xc4935b3c, 0x4da1fab5, 0xc1eb0209, 0x59d438e5, 0xd8a00bae, + 0x2092f05f, 0xc57d965d, 0x4c77a88e, 0xc18e18a7, 0x59f54bee, 0xd8024d59, 0x1d719810, 0xc67c1e18, + 0x4b418bbe, 0xc13ad060, 0x5a12e720, 0xd76619b6, 0x1a4608ab, 0xc78e9a1d, 0x49ffd417, 0xc0f1360b, + 0x5a2d0957, 0xd6cb76c9, 0x17115bc0, 0xc8b4ab32, 0x48b2b335, 0xc0b15502, 0x5a43b190, 0xd6326a88, + 0x13d4ae08, 0xc9edeb50, 0x475a5c77, 0xc07b371e, 0x5a56deec, 0xd59afadb, 0x10911f04, 0xcb39edca, + 0x45f704f7, 0xc04ee4b8, 0x5a6690ae, 0xd5052d97, 0x0d47d096, 0xcc983f70, 0x4488e37f, 0xc02c64a6, + 0x5a72c63b, 0xd4710883, 0x09f9e6a1, 0xce0866b8, 0x43103085, 0xc013bc39, 0x5a7b7f1a, 0xd3de9156, + 0x06a886a0, 0xcf89e3e8, 0x418d2621, 0xc004ef3f, 0x5a80baf6, 0xd34dcdb4, 0x0354d741, 0xd11c3142, + 0x40000000, 0xc0000000, 0x5a82799a, 0xd2bec333, 0x00000000, 0xd2bec333, 0x3e68fb62, 0xc004ef3f, + 0x5a80baf6, 0xd2317756, 0xfcab28bf, 0xd4710883, 0x3cc85709, 0xc013bc39, 0x5a7b7f1a, 0xd1a5ef90, + 0xf9577960, 0xd6326a88, 0x3b1e5335, 0xc02c64a6, 0x5a72c63b, 0xd11c3142, 0xf606195f, 0xd8024d59, + 0x396b3199, 0xc04ee4b8, 0x5a6690ae, 0xd09441bb, 0xf2b82f6a, 0xd9e01006, 0x37af354c, 0xc07b371e, + 0x5a56deec, 0xd00e2639, 0xef6ee0fc, 0xdbcb0cce, 0x35eaa2c7, 0xc0b15502, 0x5a43b190, 0xcf89e3e8, + 0xec2b51f8, 0xddc29958, 0x341dbfd3, 0xc0f1360b, 0x5a2d0957, 0xcf077fe1, 0xe8eea440, 0xdfc606f1, + 0x3248d382, 0xc13ad060, 0x5a12e720, 0xce86ff2a, 0xe5b9f755, 0xe1d4a2c8, 0x306c2624, 0xc18e18a7, + 0x59f54bee, 0xce0866b8, 0xe28e67f0, 0xe3edb628, 0x2e88013a, 0xc1eb0209, 0x59d438e5, 0xcd8bbb6d, + 0xdf6d0fa1, 0xe61086bc, 0x2c9caf6c, 0xc2517e31, 0x59afaf4c, 0xcd110216, 0xdc57046d, 0xe83c56cf, + 0x2aaa7c7f, 0xc2c17d52, 0x5987b08a, 0xcc983f70, 0xd94d586c, 0xea70658a, 0x28b1b544, 0xc33aee27, + 0x595c3e2a, 0xcc217822, 0xd651196c, 0xecabef3d, 0x26b2a794, 0xc3bdbdf6, 0x592d59da, 0xcbacb0bf, + 0xd3635094, 0xeeee2d9d, 0x24ada23d, 0xc449d892, 0x58fb0568, 0xcb39edca, 0xd0850204, 0xf136580d, + 0x22a2f4f8, 0xc4df2862, 0x58c542c5, 0xcac933ae, 0xcdb72c7e, 0xf383a3e2, 0x2092f05f, 0xc57d965d, + 0x588c1404, 0xca5a86c4, 0xcafac90f, 0xf5d544a7, 0x1e7de5df, 0xc6250a18, 0x584f7b58, 0xc9edeb50, + 0xc850cab4, 0xf82a6c6a, 0x1c6427a9, 0xc6d569be, 0x580f7b19, 0xc9836582, 0xc5ba1e09, 0xfa824bfd, + 0x1a4608ab, 0xc78e9a1d, 0x57cc15bc, 0xc91af976, 0xc337a8f7, 0xfcdc1342, 0x1823dc7d, 0xc8507ea7, + 0x57854ddd, 0xc8b4ab32, 0xc0ca4a63, 0xff36f170, 0x15fdf758, 0xc91af976, 0x573b2635, 0xc8507ea7, + 0xbe72d9df, 0x0192155f, 0x13d4ae08, 0xc9edeb50, 0x56eda1a0, 0xc7ee77b3, 0xbc322766, 0x03ecadcf, + 0x11a855df, 0xcac933ae, 0x569cc31b, 0xc78e9a1d, 0xba08fb09, 0x0645e9af, 0x0f7944a7, 0xcbacb0bf, + 0x56488dc5, 0xc730e997, 0xb7f814b5, 0x089cf867, 0x0d47d096, 0xcc983f70, 0x55f104dc, 0xc6d569be, + 0xb6002be9, 0x0af10a22, 0x0b145041, 0xcd8bbb6d, 0x55962bc0, 0xc67c1e18, 0xb421ef77, 0x0d415013, + 0x08df1a8c, 0xce86ff2a, 0x553805f2, 0xc6250a18, 0xb25e054b, 0x0f8cfcbe, 0x06a886a0, 0xcf89e3e8, + 0x54d69714, 0xc5d03118, 0xb0b50a2f, 0x11d3443f, 0x0470ebdc, 0xd09441bb, 0x5471e2e6, 0xc57d965d, + 0xaf279193, 0x14135c94, 0x0238a1c6, 0xd1a5ef90, 0x5409ed4b, 0xc52d3d18, 0xadb6255e, 0x164c7ddd, + 0x00000000, 0xd2bec333, 0x539eba45, 0xc4df2862, 0xac6145bb, 0x187de2a7, 0xfdc75e3a, 0xd3de9156, + 0x53304df6, 0xc4935b3c, 0xab2968ec, 0x1aa6c82b, 0xfb8f1424, 0xd5052d97, 0x52beac9f, 0xc449d892, + 0xaa0efb24, 0x1cc66e99, 0xf9577960, 0xd6326a88, 0x5249daa2, 0xc402a33c, 0xa9125e60, 0x1edc1953, + 0xf720e574, 0xd76619b6, 0x51d1dc80, 0xc3bdbdf6, 0xa833ea44, 0x20e70f32, 0xf4ebafbf, 0xd8a00bae, + 0x5156b6d9, 0xc37b2b6a, 0xa773ebfc, 0x22e69ac8, 0xf2b82f6a, 0xd9e01006, 0x50d86e6d, 0xc33aee27, + 0xa6d2a626, 0x24da0a9a, 0xf086bb59, 0xdb25f566, 0x50570819, 0xc2fd08a9, 0xa65050b4, 0x26c0b162, + 0xee57aa21, 0xdc71898d, 0x4fd288dc, 0xc2c17d52, 0xa5ed18e0, 0x2899e64a, 0xec2b51f8, 0xddc29958, + 0x4f4af5d1, 0xc2884e6e, 0xa5a92114, 0x2a650525, 0xea0208a8, 0xdf18f0ce, 0x4ec05432, 0xc2517e31, + 0xa58480e6, 0x2c216eaa, 0xe7dc2383, 0xe0745b24, 0x4e32a956, 0xc21d0eb8, 0xa57f450a, 0x2dce88aa, + 0xe5b9f755, 0xe1d4a2c8, 0x4da1fab5, 0xc1eb0209, 0xa5996f52, 0x2f6bbe45, 0xe39bd857, 0xe3399167, + 0x4d0e4de2, 0xc1bb5a11, 0xa5d2f6a9, 0x30f8801f, 0xe1821a21, 0xe4a2eff6, 0x4c77a88e, 0xc18e18a7, + 0xa62bc71b, 0x32744493, 0xdf6d0fa1, 0xe61086bc, 0x4bde1089, 0xc1633f8a, 0xa6a3c1d6, 0x33de87de, + 0xdd5d0b08, 0xe7821d59, 0x4b418bbe, 0xc13ad060, 0xa73abd3b, 0x3536cc52, 0xdb525dc3, 0xe8f77acf, + 0x4aa22036, 0xc114ccb9, 0xa7f084e7, 0x367c9a7e, 0xd94d586c, 0xea70658a, 0x49ffd417, 0xc0f1360b, + 0xa8c4d9cb, 0x37af8159, 0xd74e4abc, 0xebeca36c, 0x495aada2, 0xc0d00db6, 0xa9b7723b, 0x38cf1669, + 0xd5558381, 0xed6bf9d1, 0x48b2b335, 0xc0b15502, 0xaac7fa0e, 0x39daf5e8, 0xd3635094, 0xeeee2d9d, + 0x4807eb4b, 0xc0950d1d, 0xabf612b5, 0x3ad2c2e8, 0xd177fec6, 0xf0730342, 0x475a5c77, 0xc07b371e, + 0xad415361, 0x3bb6276e, 0xcf93d9dc, 0xf1fa3ecb, 0x46aa0d6d, 0xc063d405, 0xaea94927, 0x3c84d496, + 0xcdb72c7e, 0xf383a3e2, 0x45f704f7, 0xc04ee4b8, 0xb02d7724, 0x3d3e82ae, 0xcbe2402d, 0xf50ef5de, + 0x454149fc, 0xc03c6a07, 0xb1cd56aa, 0x3de2f148, 0xca155d39, 0xf69bf7c9, 0x4488e37f, 0xc02c64a6, + 0xb3885772, 0x3e71e759, 0xc850cab4, 0xf82a6c6a, 0x43cdd89a, 0xc01ed535, 0xb55ddfca, 0x3eeb3347, + 0xc694ce67, 0xf9ba1651, 0x43103085, 0xc013bc39, 0xb74d4ccb, 0x3f4eaafe, 0xc4e1accb, 0xfb4ab7db, + 0x424ff28f, 0xc00b1a20, 0xb955f293, 0x3f9c2bfb, 0xc337a8f7, 0xfcdc1342, 0x418d2621, 0xc004ef3f, + 0xbb771c81, 0x3fd39b5a, 0xc197049e, 0xfe6deaa1, 0x40c7d2bd, 0xc0013bd3, 0xbdb00d71, 0x3ff4e5e0, +}; + +const uint32_t twidTabEven[4*6 + 16*6 + 64*6] PROGMEM = { + 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x5a82799a, 0xd2bec333, + 0x539eba45, 0xe7821d59, 0x539eba45, 0xc4df2862, 0x40000000, 0xc0000000, 0x5a82799a, 0xd2bec333, + 0x00000000, 0xd2bec333, 0x00000000, 0xd2bec333, 0x539eba45, 0xc4df2862, 0xac6145bb, 0x187de2a7, + 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x4b418bbe, 0xf383a3e2, + 0x45f704f7, 0xf9ba1651, 0x4fd288dc, 0xed6bf9d1, 0x539eba45, 0xe7821d59, 0x4b418bbe, 0xf383a3e2, + 0x58c542c5, 0xdc71898d, 0x58c542c5, 0xdc71898d, 0x4fd288dc, 0xed6bf9d1, 0x5a12e720, 0xce86ff2a, + 0x5a82799a, 0xd2bec333, 0x539eba45, 0xe7821d59, 0x539eba45, 0xc4df2862, 0x58c542c5, 0xcac933ae, + 0x569cc31b, 0xe1d4a2c8, 0x45f704f7, 0xc04ee4b8, 0x539eba45, 0xc4df2862, 0x58c542c5, 0xdc71898d, + 0x3248d382, 0xc13ad060, 0x4b418bbe, 0xc13ad060, 0x5a12e720, 0xd76619b6, 0x1a4608ab, 0xc78e9a1d, + 0x40000000, 0xc0000000, 0x5a82799a, 0xd2bec333, 0x00000000, 0xd2bec333, 0x3248d382, 0xc13ad060, + 0x5a12e720, 0xce86ff2a, 0xe5b9f755, 0xe1d4a2c8, 0x22a2f4f8, 0xc4df2862, 0x58c542c5, 0xcac933ae, + 0xcdb72c7e, 0xf383a3e2, 0x11a855df, 0xcac933ae, 0x569cc31b, 0xc78e9a1d, 0xba08fb09, 0x0645e9af, + 0x00000000, 0xd2bec333, 0x539eba45, 0xc4df2862, 0xac6145bb, 0x187de2a7, 0xee57aa21, 0xdc71898d, + 0x4fd288dc, 0xc2c17d52, 0xa5ed18e0, 0x2899e64a, 0xdd5d0b08, 0xe7821d59, 0x4b418bbe, 0xc13ad060, + 0xa73abd3b, 0x3536cc52, 0xcdb72c7e, 0xf383a3e2, 0x45f704f7, 0xc04ee4b8, 0xb02d7724, 0x3d3e82ae, + 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x43103085, 0xfcdc1342, + 0x418d2621, 0xfe6deaa1, 0x4488e37f, 0xfb4ab7db, 0x45f704f7, 0xf9ba1651, 0x43103085, 0xfcdc1342, + 0x48b2b335, 0xf69bf7c9, 0x48b2b335, 0xf69bf7c9, 0x4488e37f, 0xfb4ab7db, 0x4c77a88e, 0xf1fa3ecb, + 0x4b418bbe, 0xf383a3e2, 0x45f704f7, 0xf9ba1651, 0x4fd288dc, 0xed6bf9d1, 0x4da1fab5, 0xf0730342, + 0x475a5c77, 0xf82a6c6a, 0x52beac9f, 0xe8f77acf, 0x4fd288dc, 0xed6bf9d1, 0x48b2b335, 0xf69bf7c9, + 0x553805f2, 0xe4a2eff6, 0x51d1dc80, 0xea70658a, 0x49ffd417, 0xf50ef5de, 0x573b2635, 0xe0745b24, + 0x539eba45, 0xe7821d59, 0x4b418bbe, 0xf383a3e2, 0x58c542c5, 0xdc71898d, 0x553805f2, 0xe4a2eff6, + 0x4c77a88e, 0xf1fa3ecb, 0x59d438e5, 0xd8a00bae, 0x569cc31b, 0xe1d4a2c8, 0x4da1fab5, 0xf0730342, + 0x5a6690ae, 0xd5052d97, 0x57cc15bc, 0xdf18f0ce, 0x4ec05432, 0xeeee2d9d, 0x5a7b7f1a, 0xd1a5ef90, + 0x58c542c5, 0xdc71898d, 0x4fd288dc, 0xed6bf9d1, 0x5a12e720, 0xce86ff2a, 0x5987b08a, 0xd9e01006, + 0x50d86e6d, 0xebeca36c, 0x592d59da, 0xcbacb0bf, 0x5a12e720, 0xd76619b6, 0x51d1dc80, 0xea70658a, + 0x57cc15bc, 0xc91af976, 0x5a6690ae, 0xd5052d97, 0x52beac9f, 0xe8f77acf, 0x55f104dc, 0xc6d569be, + 0x5a82799a, 0xd2bec333, 0x539eba45, 0xe7821d59, 0x539eba45, 0xc4df2862, 0x5a6690ae, 0xd09441bb, + 0x5471e2e6, 0xe61086bc, 0x50d86e6d, 0xc33aee27, 0x5a12e720, 0xce86ff2a, 0x553805f2, 0xe4a2eff6, + 0x4da1fab5, 0xc1eb0209, 0x5987b08a, 0xcc983f70, 0x55f104dc, 0xe3399167, 0x49ffd417, 0xc0f1360b, + 0x58c542c5, 0xcac933ae, 0x569cc31b, 0xe1d4a2c8, 0x45f704f7, 0xc04ee4b8, 0x57cc15bc, 0xc91af976, + 0x573b2635, 0xe0745b24, 0x418d2621, 0xc004ef3f, 0x569cc31b, 0xc78e9a1d, 0x57cc15bc, 0xdf18f0ce, + 0x3cc85709, 0xc013bc39, 0x553805f2, 0xc6250a18, 0x584f7b58, 0xddc29958, 0x37af354c, 0xc07b371e, + 0x539eba45, 0xc4df2862, 0x58c542c5, 0xdc71898d, 0x3248d382, 0xc13ad060, 0x51d1dc80, 0xc3bdbdf6, + 0x592d59da, 0xdb25f566, 0x2c9caf6c, 0xc2517e31, 0x4fd288dc, 0xc2c17d52, 0x5987b08a, 0xd9e01006, + 0x26b2a794, 0xc3bdbdf6, 0x4da1fab5, 0xc1eb0209, 0x59d438e5, 0xd8a00bae, 0x2092f05f, 0xc57d965d, + 0x4b418bbe, 0xc13ad060, 0x5a12e720, 0xd76619b6, 0x1a4608ab, 0xc78e9a1d, 0x48b2b335, 0xc0b15502, + 0x5a43b190, 0xd6326a88, 0x13d4ae08, 0xc9edeb50, 0x45f704f7, 0xc04ee4b8, 0x5a6690ae, 0xd5052d97, + 0x0d47d096, 0xcc983f70, 0x43103085, 0xc013bc39, 0x5a7b7f1a, 0xd3de9156, 0x06a886a0, 0xcf89e3e8, + 0x40000000, 0xc0000000, 0x5a82799a, 0xd2bec333, 0x00000000, 0xd2bec333, 0x3cc85709, 0xc013bc39, + 0x5a7b7f1a, 0xd1a5ef90, 0xf9577960, 0xd6326a88, 0x396b3199, 0xc04ee4b8, 0x5a6690ae, 0xd09441bb, + 0xf2b82f6a, 0xd9e01006, 0x35eaa2c7, 0xc0b15502, 0x5a43b190, 0xcf89e3e8, 0xec2b51f8, 0xddc29958, + 0x3248d382, 0xc13ad060, 0x5a12e720, 0xce86ff2a, 0xe5b9f755, 0xe1d4a2c8, 0x2e88013a, 0xc1eb0209, + 0x59d438e5, 0xcd8bbb6d, 0xdf6d0fa1, 0xe61086bc, 0x2aaa7c7f, 0xc2c17d52, 0x5987b08a, 0xcc983f70, + 0xd94d586c, 0xea70658a, 0x26b2a794, 0xc3bdbdf6, 0x592d59da, 0xcbacb0bf, 0xd3635094, 0xeeee2d9d, + 0x22a2f4f8, 0xc4df2862, 0x58c542c5, 0xcac933ae, 0xcdb72c7e, 0xf383a3e2, 0x1e7de5df, 0xc6250a18, + 0x584f7b58, 0xc9edeb50, 0xc850cab4, 0xf82a6c6a, 0x1a4608ab, 0xc78e9a1d, 0x57cc15bc, 0xc91af976, + 0xc337a8f7, 0xfcdc1342, 0x15fdf758, 0xc91af976, 0x573b2635, 0xc8507ea7, 0xbe72d9df, 0x0192155f, + 0x11a855df, 0xcac933ae, 0x569cc31b, 0xc78e9a1d, 0xba08fb09, 0x0645e9af, 0x0d47d096, 0xcc983f70, + 0x55f104dc, 0xc6d569be, 0xb6002be9, 0x0af10a22, 0x08df1a8c, 0xce86ff2a, 0x553805f2, 0xc6250a18, + 0xb25e054b, 0x0f8cfcbe, 0x0470ebdc, 0xd09441bb, 0x5471e2e6, 0xc57d965d, 0xaf279193, 0x14135c94, + 0x00000000, 0xd2bec333, 0x539eba45, 0xc4df2862, 0xac6145bb, 0x187de2a7, 0xfb8f1424, 0xd5052d97, + 0x52beac9f, 0xc449d892, 0xaa0efb24, 0x1cc66e99, 0xf720e574, 0xd76619b6, 0x51d1dc80, 0xc3bdbdf6, + 0xa833ea44, 0x20e70f32, 0xf2b82f6a, 0xd9e01006, 0x50d86e6d, 0xc33aee27, 0xa6d2a626, 0x24da0a9a, + 0xee57aa21, 0xdc71898d, 0x4fd288dc, 0xc2c17d52, 0xa5ed18e0, 0x2899e64a, 0xea0208a8, 0xdf18f0ce, + 0x4ec05432, 0xc2517e31, 0xa58480e6, 0x2c216eaa, 0xe5b9f755, 0xe1d4a2c8, 0x4da1fab5, 0xc1eb0209, + 0xa5996f52, 0x2f6bbe45, 0xe1821a21, 0xe4a2eff6, 0x4c77a88e, 0xc18e18a7, 0xa62bc71b, 0x32744493, + 0xdd5d0b08, 0xe7821d59, 0x4b418bbe, 0xc13ad060, 0xa73abd3b, 0x3536cc52, 0xd94d586c, 0xea70658a, + 0x49ffd417, 0xc0f1360b, 0xa8c4d9cb, 0x37af8159, 0xd5558381, 0xed6bf9d1, 0x48b2b335, 0xc0b15502, + 0xaac7fa0e, 0x39daf5e8, 0xd177fec6, 0xf0730342, 0x475a5c77, 0xc07b371e, 0xad415361, 0x3bb6276e, + 0xcdb72c7e, 0xf383a3e2, 0x45f704f7, 0xc04ee4b8, 0xb02d7724, 0x3d3e82ae, 0xca155d39, 0xf69bf7c9, + 0x4488e37f, 0xc02c64a6, 0xb3885772, 0x3e71e759, 0xc694ce67, 0xf9ba1651, 0x43103085, 0xc013bc39, + 0xb74d4ccb, 0x3f4eaafe, 0xc337a8f7, 0xfcdc1342, 0x418d2621, 0xc004ef3f, 0xbb771c81, 0x3fd39b5a, +}; + +/* log2Tab[x] = floor(log2(x)), format = Q28 */ +const int log2Tab[65] PROGMEM = { + 0x00000000, 0x00000000, 0x10000000, 0x195c01a3, 0x20000000, 0x25269e12, 0x295c01a3, 0x2ceaecfe, + 0x30000000, 0x32b80347, 0x35269e12, 0x3759d4f8, 0x395c01a3, 0x3b350047, 0x3ceaecfe, 0x3e829fb6, + 0x40000000, 0x41663f6f, 0x42b80347, 0x43f782d7, 0x45269e12, 0x4646eea2, 0x4759d4f8, 0x48608280, + 0x495c01a3, 0x4a4d3c25, 0x4b350047, 0x4c1404ea, 0x4ceaecfe, 0x4dba4a47, 0x4e829fb6, 0x4f446359, + 0x50000000, 0x50b5d69b, 0x51663f6f, 0x52118b11, 0x52b80347, 0x5359ebc5, 0x53f782d7, 0x549101ea, + 0x55269e12, 0x55b88873, 0x5646eea2, 0x56d1fafd, 0x5759d4f8, 0x57dea15a, 0x58608280, 0x58df988f, + 0x595c01a3, 0x59d5d9fd, 0x5a4d3c25, 0x5ac24113, 0x5b350047, 0x5ba58feb, 0x5c1404ea, 0x5c80730b, + 0x5ceaecfe, 0x5d53847a, 0x5dba4a47, 0x5e1f4e51, 0x5e829fb6, 0x5ee44cd5, 0x5f446359, 0x5fa2f045, + 0x60000000 +}; + +const HuffInfo_t huffTabSpecInfo[11] PROGMEM = { + /* table 0 not used */ + {11, { 1, 0, 0, 0, 8, 0, 24, 0, 24, 8, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 0}, + { 9, { 0, 0, 1, 1, 7, 24, 15, 19, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 81}, + {16, { 1, 0, 0, 4, 2, 6, 3, 5, 15, 15, 8, 9, 3, 3, 5, 2, 0, 0, 0, 0}, 162}, + {12, { 0, 0, 0, 10, 6, 0, 9, 21, 8, 14, 11, 2, 0, 0, 0, 0, 0, 0, 0, 0}, 243}, + {13, { 1, 0, 0, 4, 4, 0, 4, 12, 12, 12, 18, 10, 4, 0, 0, 0, 0, 0, 0, 0}, 324}, + {11, { 0, 0, 0, 9, 0, 16, 13, 8, 23, 8, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 405}, + {12, { 1, 0, 2, 1, 0, 4, 5, 10, 14, 15, 8, 4, 0, 0, 0, 0, 0, 0, 0, 0}, 486}, + {10, { 0, 0, 1, 5, 7, 10, 14, 15, 8, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 550}, + {15, { 1, 0, 2, 1, 0, 4, 3, 8, 11, 20, 31, 38, 32, 14, 4, 0, 0, 0, 0, 0}, 614}, + {12, { 0, 0, 0, 3, 8, 14, 17, 25, 31, 41, 22, 8, 0, 0, 0, 0, 0, 0, 0, 0}, 783}, + {12, { 0, 0, 0, 2, 6, 7, 16, 59, 55, 95, 43, 6, 0, 0, 0, 0, 0, 0, 0, 0}, 952}, +}; + +const short huffTabSpec[1241] PROGMEM = { + /* spectrum table 1 [81] (signed) */ + 0x0000, 0x0200, 0x0e00, 0x0007, 0x0040, 0x0001, 0x0038, 0x0008, 0x01c0, 0x03c0, 0x0e40, 0x0039, 0x0078, 0x01c8, 0x000f, 0x0240, + 0x003f, 0x0fc0, 0x01f8, 0x0238, 0x0047, 0x0e08, 0x0009, 0x0208, 0x01c1, 0x0048, 0x0041, 0x0e38, 0x0201, 0x0e07, 0x0207, 0x0e01, + 0x01c7, 0x0278, 0x0e78, 0x03c8, 0x004f, 0x0079, 0x01c9, 0x01cf, 0x03f8, 0x0239, 0x007f, 0x0e48, 0x0e0f, 0x0fc8, 0x01f9, 0x03c1, + 0x03c7, 0x0e47, 0x0ff8, 0x01ff, 0x0049, 0x020f, 0x0241, 0x0e41, 0x0248, 0x0fc1, 0x0e3f, 0x0247, 0x023f, 0x0e39, 0x0fc7, 0x0e09, + 0x0209, 0x03cf, 0x0e79, 0x0e4f, 0x03f9, 0x0249, 0x0fc9, 0x027f, 0x0fcf, 0x0fff, 0x0279, 0x03c9, 0x0e49, 0x0e7f, 0x0ff9, 0x03ff, + 0x024f, + /* spectrum table 2 [81] (signed) */ + 0x0000, 0x0200, 0x0e00, 0x0001, 0x0038, 0x0007, 0x01c0, 0x0008, 0x0040, 0x01c8, 0x0e40, 0x0078, 0x000f, 0x0047, 0x0039, 0x0e07, + 0x03c0, 0x0238, 0x0fc0, 0x003f, 0x0208, 0x0201, 0x01c1, 0x0e08, 0x0041, 0x01f8, 0x0e01, 0x01c7, 0x0e38, 0x0240, 0x0048, 0x0009, + 0x0207, 0x0079, 0x0239, 0x0e78, 0x01cf, 0x03c8, 0x0247, 0x0209, 0x0e48, 0x01f9, 0x0248, 0x0e0f, 0x0ff8, 0x0e39, 0x03f8, 0x0278, + 0x03c1, 0x0e47, 0x0fc8, 0x0e09, 0x0fc1, 0x0fc7, 0x01ff, 0x020f, 0x023f, 0x007f, 0x0049, 0x0e41, 0x0e3f, 0x004f, 0x03c7, 0x01c9, + 0x0241, 0x03cf, 0x0e79, 0x03f9, 0x0fff, 0x0e4f, 0x0e49, 0x0249, 0x0fcf, 0x03c9, 0x0e7f, 0x0fc9, 0x027f, 0x03ff, 0x0ff9, 0x0279, + 0x024f, + /* spectrum table 3 [81] (unsigned) */ + 0x0000, 0x1200, 0x1001, 0x1040, 0x1008, 0x2240, 0x2009, 0x2048, 0x2041, 0x2208, 0x3049, 0x2201, 0x3248, 0x4249, 0x3209, 0x3241, + 0x1400, 0x1002, 0x200a, 0x2440, 0x3288, 0x2011, 0x3051, 0x2280, 0x304a, 0x3448, 0x1010, 0x2088, 0x2050, 0x1080, 0x2042, 0x2408, + 0x4289, 0x3089, 0x3250, 0x4251, 0x3281, 0x2210, 0x3211, 0x2081, 0x4449, 0x424a, 0x3441, 0x320a, 0x2012, 0x3052, 0x3488, 0x3290, + 0x2202, 0x2401, 0x3091, 0x2480, 0x4291, 0x3242, 0x3409, 0x4252, 0x4489, 0x2090, 0x308a, 0x3212, 0x3481, 0x3450, 0x3490, 0x3092, + 0x4491, 0x4451, 0x428a, 0x4292, 0x2082, 0x2410, 0x3282, 0x3411, 0x444a, 0x3442, 0x4492, 0x448a, 0x4452, 0x340a, 0x2402, 0x3482, + 0x3412, + /* spectrum table 4 [81] (unsigned) */ + 0x4249, 0x3049, 0x3241, 0x3248, 0x3209, 0x1200, 0x2240, 0x0000, 0x2009, 0x2208, 0x2201, 0x2048, 0x1001, 0x2041, 0x1008, 0x1040, + 0x4449, 0x4251, 0x4289, 0x424a, 0x3448, 0x3441, 0x3288, 0x3409, 0x3051, 0x304a, 0x3250, 0x3089, 0x320a, 0x3281, 0x3242, 0x3211, + 0x2440, 0x2408, 0x2280, 0x2401, 0x2042, 0x2088, 0x200a, 0x2050, 0x2081, 0x2202, 0x2011, 0x2210, 0x1400, 0x1002, 0x1080, 0x1010, + 0x4291, 0x4489, 0x4451, 0x4252, 0x428a, 0x444a, 0x3290, 0x3488, 0x3450, 0x3091, 0x3052, 0x3481, 0x308a, 0x3411, 0x3212, 0x4491, + 0x3282, 0x340a, 0x3442, 0x4292, 0x4452, 0x448a, 0x2090, 0x2480, 0x2012, 0x2410, 0x2082, 0x2402, 0x4492, 0x3092, 0x3490, 0x3482, + 0x3412, + /* spectrum table 5 [81] (signed) */ + 0x0000, 0x03e0, 0x0020, 0x0001, 0x001f, 0x003f, 0x03e1, 0x03ff, 0x0021, 0x03c0, 0x0002, 0x0040, 0x001e, 0x03df, 0x0041, 0x03fe, + 0x0022, 0x03c1, 0x005f, 0x03e2, 0x003e, 0x03a0, 0x0060, 0x001d, 0x0003, 0x03bf, 0x0023, 0x0061, 0x03fd, 0x03a1, 0x007f, 0x003d, + 0x03e3, 0x03c2, 0x0042, 0x03de, 0x005e, 0x03be, 0x007e, 0x03c3, 0x005d, 0x0062, 0x0043, 0x03a2, 0x03dd, 0x001c, 0x0380, 0x0081, + 0x0080, 0x039f, 0x0004, 0x009f, 0x03fc, 0x0024, 0x03e4, 0x0381, 0x003c, 0x007d, 0x03bd, 0x03a3, 0x03c4, 0x039e, 0x0082, 0x005c, + 0x0044, 0x0063, 0x0382, 0x03dc, 0x009e, 0x007c, 0x039d, 0x0383, 0x0064, 0x03a4, 0x0083, 0x009d, 0x03bc, 0x009c, 0x0384, 0x0084, + 0x039c, + /* spectrum table 6 [81] (signed) */ + 0x0000, 0x0020, 0x001f, 0x0001, 0x03e0, 0x0021, 0x03e1, 0x003f, 0x03ff, 0x005f, 0x0041, 0x03c1, 0x03df, 0x03c0, 0x03e2, 0x0040, + 0x003e, 0x0022, 0x001e, 0x03fe, 0x0002, 0x005e, 0x03c2, 0x03de, 0x0042, 0x03a1, 0x0061, 0x007f, 0x03e3, 0x03bf, 0x0023, 0x003d, + 0x03fd, 0x0060, 0x03a0, 0x001d, 0x0003, 0x0062, 0x03be, 0x03c3, 0x0043, 0x007e, 0x005d, 0x03dd, 0x03a2, 0x0063, 0x007d, 0x03bd, + 0x03a3, 0x003c, 0x03fc, 0x0081, 0x0381, 0x039f, 0x0024, 0x009f, 0x03e4, 0x001c, 0x0382, 0x039e, 0x0044, 0x03dc, 0x0380, 0x0082, + 0x009e, 0x03c4, 0x0080, 0x005c, 0x0004, 0x03bc, 0x03a4, 0x007c, 0x009d, 0x0064, 0x0083, 0x0383, 0x039d, 0x0084, 0x0384, 0x039c, + 0x009c, + /* spectrum table 7 [64] (unsigned) */ + 0x0000, 0x0420, 0x0401, 0x0821, 0x0841, 0x0822, 0x0440, 0x0402, 0x0861, 0x0823, 0x0842, 0x0460, 0x0403, 0x0843, 0x0862, 0x0824, + 0x0881, 0x0825, 0x08a1, 0x0863, 0x0844, 0x0404, 0x0480, 0x0882, 0x0845, 0x08a2, 0x0405, 0x08c1, 0x04a0, 0x0826, 0x0883, 0x0865, + 0x0864, 0x08a3, 0x0846, 0x08c2, 0x0827, 0x0866, 0x0406, 0x04c0, 0x0884, 0x08e1, 0x0885, 0x08e2, 0x08a4, 0x08c3, 0x0847, 0x08e3, + 0x08c4, 0x08a5, 0x0886, 0x0867, 0x04e0, 0x0407, 0x08c5, 0x08a6, 0x08e4, 0x0887, 0x08a7, 0x08e5, 0x08e6, 0x08c6, 0x08c7, 0x08e7, + /* spectrum table 8 [64] (unsigned) */ + 0x0821, 0x0841, 0x0420, 0x0822, 0x0401, 0x0842, 0x0000, 0x0440, 0x0402, 0x0861, 0x0823, 0x0862, 0x0843, 0x0863, 0x0881, 0x0824, + 0x0882, 0x0844, 0x0460, 0x0403, 0x0883, 0x0864, 0x08a2, 0x08a1, 0x0845, 0x0825, 0x08a3, 0x0865, 0x0884, 0x08a4, 0x0404, 0x0885, + 0x0480, 0x0846, 0x08c2, 0x08c1, 0x0826, 0x0866, 0x08c3, 0x08a5, 0x04a0, 0x08c4, 0x0405, 0x0886, 0x08e1, 0x08e2, 0x0847, 0x08c5, + 0x08e3, 0x0827, 0x08a6, 0x0867, 0x08c6, 0x08e4, 0x04c0, 0x0887, 0x0406, 0x08e5, 0x08e6, 0x08c7, 0x08a7, 0x04e0, 0x0407, 0x08e7, + /* spectrum table 9 [169] (unsigned) */ + 0x0000, 0x0420, 0x0401, 0x0821, 0x0841, 0x0822, 0x0440, 0x0402, 0x0861, 0x0842, 0x0823, 0x0460, 0x0403, 0x0843, 0x0862, 0x0824, + 0x0881, 0x0844, 0x0825, 0x0882, 0x0863, 0x0404, 0x0480, 0x08a1, 0x0845, 0x0826, 0x0864, 0x08a2, 0x08c1, 0x0883, 0x0405, 0x0846, + 0x04a0, 0x0827, 0x0865, 0x0828, 0x0901, 0x0884, 0x08a3, 0x08c2, 0x08e1, 0x0406, 0x0902, 0x0848, 0x0866, 0x0847, 0x0885, 0x0921, + 0x0829, 0x08e2, 0x04c0, 0x08a4, 0x08c3, 0x0903, 0x0407, 0x0922, 0x0868, 0x0886, 0x0867, 0x0408, 0x0941, 0x08c4, 0x0849, 0x08a5, + 0x0500, 0x04e0, 0x08e3, 0x0942, 0x0923, 0x0904, 0x082a, 0x08e4, 0x08c5, 0x08a6, 0x0888, 0x0887, 0x0869, 0x0961, 0x08a8, 0x0520, + 0x0905, 0x0943, 0x084a, 0x0409, 0x0962, 0x0924, 0x08c6, 0x0981, 0x0889, 0x0906, 0x082b, 0x0925, 0x0944, 0x08a7, 0x08e5, 0x084b, + 0x082c, 0x0982, 0x0963, 0x086a, 0x08a9, 0x08c7, 0x0907, 0x0964, 0x040a, 0x08e6, 0x0983, 0x0540, 0x0945, 0x088a, 0x08c8, 0x084c, + 0x0926, 0x0927, 0x088b, 0x0560, 0x08c9, 0x086b, 0x08aa, 0x0908, 0x08e8, 0x0985, 0x086c, 0x0965, 0x08e7, 0x0984, 0x0966, 0x0946, + 0x088c, 0x08e9, 0x08ab, 0x040b, 0x0986, 0x08ca, 0x0580, 0x0947, 0x08ac, 0x08ea, 0x0928, 0x040c, 0x0967, 0x0909, 0x0929, 0x0948, + 0x08eb, 0x0987, 0x08cb, 0x090b, 0x0968, 0x08ec, 0x08cc, 0x090a, 0x0949, 0x090c, 0x092a, 0x092b, 0x092c, 0x094b, 0x0989, 0x094a, + 0x0969, 0x0988, 0x096a, 0x098a, 0x098b, 0x094c, 0x096b, 0x096c, 0x098c, + /* spectrum table 10 [169] (unsigned) */ + 0x0821, 0x0822, 0x0841, 0x0842, 0x0420, 0x0401, 0x0823, 0x0862, 0x0861, 0x0843, 0x0863, 0x0440, 0x0402, 0x0844, 0x0882, 0x0824, + 0x0881, 0x0000, 0x0883, 0x0864, 0x0460, 0x0403, 0x0884, 0x0845, 0x08a2, 0x0825, 0x08a1, 0x08a3, 0x0865, 0x08a4, 0x0885, 0x08c2, + 0x0846, 0x08c3, 0x0480, 0x08c1, 0x0404, 0x0826, 0x0866, 0x08a5, 0x08c4, 0x0886, 0x08c5, 0x08e2, 0x0867, 0x0847, 0x08a6, 0x0902, + 0x08e3, 0x04a0, 0x08e1, 0x0405, 0x0901, 0x0827, 0x0903, 0x08e4, 0x0887, 0x0848, 0x08c6, 0x08e5, 0x0828, 0x0868, 0x0904, 0x0888, + 0x08a7, 0x0905, 0x08a8, 0x08e6, 0x08c7, 0x0922, 0x04c0, 0x08c8, 0x0923, 0x0869, 0x0921, 0x0849, 0x0406, 0x0906, 0x0924, 0x0889, + 0x0942, 0x0829, 0x08e7, 0x0907, 0x0925, 0x08e8, 0x0943, 0x08a9, 0x0944, 0x084a, 0x0941, 0x086a, 0x0926, 0x08c9, 0x0500, 0x088a, + 0x04e0, 0x0962, 0x08e9, 0x0963, 0x0946, 0x082a, 0x0961, 0x0927, 0x0407, 0x0908, 0x0945, 0x086b, 0x08aa, 0x0909, 0x0965, 0x0408, + 0x0964, 0x084b, 0x08ea, 0x08ca, 0x0947, 0x088b, 0x082b, 0x0982, 0x0928, 0x0983, 0x0966, 0x08ab, 0x0984, 0x0967, 0x0985, 0x086c, + 0x08cb, 0x0520, 0x0948, 0x0540, 0x0981, 0x0409, 0x088c, 0x0929, 0x0986, 0x084c, 0x090a, 0x092a, 0x082c, 0x0968, 0x0987, 0x08eb, + 0x08ac, 0x08cc, 0x0949, 0x090b, 0x0988, 0x040a, 0x08ec, 0x0560, 0x094a, 0x0969, 0x096a, 0x040b, 0x096b, 0x092b, 0x094b, 0x0580, + 0x090c, 0x0989, 0x094c, 0x092c, 0x096c, 0x098b, 0x040c, 0x098a, 0x098c, + /* spectrum table 11 [289] (unsigned) */ + 0x0000, 0x2041, 0x2410, 0x1040, 0x1001, 0x2081, 0x2042, 0x2082, 0x2043, 0x20c1, 0x20c2, 0x1080, 0x2083, 0x1002, 0x20c3, 0x2101, + 0x2044, 0x2102, 0x2084, 0x2103, 0x20c4, 0x10c0, 0x1003, 0x2141, 0x2142, 0x2085, 0x2104, 0x2045, 0x2143, 0x20c5, 0x2144, 0x2105, + 0x2182, 0x2086, 0x2181, 0x2183, 0x20c6, 0x2046, 0x2110, 0x20d0, 0x2405, 0x2403, 0x2404, 0x2184, 0x2406, 0x1100, 0x2106, 0x1004, + 0x2090, 0x2145, 0x2150, 0x2407, 0x2402, 0x2408, 0x2087, 0x21c2, 0x20c7, 0x2185, 0x2146, 0x2190, 0x240a, 0x21c3, 0x21c1, 0x2409, + 0x21d0, 0x2050, 0x2047, 0x2107, 0x240b, 0x21c4, 0x240c, 0x2210, 0x2401, 0x2186, 0x2250, 0x2088, 0x2147, 0x2290, 0x240d, 0x2203, + 0x2202, 0x20c8, 0x1140, 0x240e, 0x22d0, 0x21c5, 0x2108, 0x2187, 0x21c6, 0x1005, 0x2204, 0x240f, 0x2310, 0x2048, 0x2201, 0x2390, + 0x2148, 0x2350, 0x20c9, 0x2205, 0x21c7, 0x2089, 0x2206, 0x2242, 0x2243, 0x23d0, 0x2109, 0x2188, 0x1180, 0x2244, 0x2149, 0x2207, + 0x21c8, 0x2049, 0x2283, 0x1006, 0x2282, 0x2241, 0x2245, 0x210a, 0x208a, 0x2246, 0x20ca, 0x2189, 0x2284, 0x2208, 0x2285, 0x2247, + 0x22c3, 0x204a, 0x11c0, 0x2286, 0x21c9, 0x20cb, 0x214a, 0x2281, 0x210b, 0x22c2, 0x2342, 0x218a, 0x2343, 0x208b, 0x1400, 0x214b, + 0x22c5, 0x22c4, 0x2248, 0x21ca, 0x2209, 0x1010, 0x210d, 0x1007, 0x20cd, 0x22c6, 0x2341, 0x2344, 0x2303, 0x208d, 0x2345, 0x220a, + 0x218b, 0x2288, 0x2287, 0x2382, 0x2304, 0x204b, 0x210c, 0x22c1, 0x20cc, 0x204d, 0x2302, 0x21cb, 0x20ce, 0x214c, 0x214d, 0x2384, + 0x210e, 0x22c7, 0x2383, 0x2305, 0x2346, 0x2306, 0x1200, 0x22c8, 0x208c, 0x2249, 0x2385, 0x218d, 0x228a, 0x23c2, 0x220b, 0x224a, + 0x2386, 0x2289, 0x214e, 0x22c9, 0x2381, 0x208e, 0x218c, 0x204c, 0x2348, 0x1008, 0x2347, 0x21cc, 0x2307, 0x21cd, 0x23c3, 0x2301, + 0x218e, 0x208f, 0x23c5, 0x23c4, 0x204e, 0x224b, 0x210f, 0x2387, 0x220d, 0x2349, 0x220c, 0x214f, 0x20cf, 0x228b, 0x22ca, 0x2308, + 0x23c6, 0x23c7, 0x220e, 0x23c1, 0x21ce, 0x1240, 0x1009, 0x224d, 0x224c, 0x2309, 0x2388, 0x228d, 0x2389, 0x230a, 0x218f, 0x21cf, + 0x224e, 0x23c8, 0x22cb, 0x22ce, 0x204f, 0x228c, 0x228e, 0x234b, 0x234a, 0x22cd, 0x22cc, 0x220f, 0x238b, 0x234c, 0x230d, 0x23c9, + 0x238a, 0x1280, 0x230b, 0x224f, 0x100a, 0x230c, 0x12c0, 0x230e, 0x228f, 0x234d, 0x100d, 0x238c, 0x23ca, 0x23cb, 0x22cf, 0x238d, + 0x1340, 0x100b, 0x234e, 0x23cc, 0x23cd, 0x230f, 0x1380, 0x238e, 0x234f, 0x1300, 0x238f, 0x100e, 0x100c, 0x23ce, 0x13c0, 0x100f, + 0x23cf, +}; + +/* coefficient table 4.A.87, format = Q31 + * reordered as cTab[0], cTab[64], cTab[128], ... cTab[576], cTab[1], cTab[65], cTab[129], ... cTab[639] + * keeping full table (not using symmetry) to allow sequential access in synth filter inner loop + * format = Q31 + */ +const uint32_t cTabS[640] PROGMEM = { + 0x00000000, 0x0055dba1, 0x01b2e41d, 0x09015651, 0x2e3a7532, 0x6d474e1d, 0xd1c58ace, 0x09015651, 0xfe4d1be3, 0x0055dba1, + 0xffede50e, 0x005b5371, 0x01d78bfc, 0x08d3e41b, 0x2faa221c, 0x6d41d963, 0xd3337b3d, 0x09299ead, 0xfe70b8d1, 0x0050b177, + 0xffed978a, 0x006090c4, 0x01fd3ba0, 0x08a24899, 0x311af3a4, 0x6d32730f, 0xd49fd55f, 0x094d7ec2, 0xfe933dc0, 0x004b6c46, + 0xffefc9b9, 0x0065fde5, 0x02244a24, 0x086b1eeb, 0x328cc6f0, 0x6d18520e, 0xd60a46e5, 0x096d0e21, 0xfeb48d0d, 0x00465348, + 0xfff0065d, 0x006b47fa, 0x024bf7a1, 0x082f552e, 0x33ff670e, 0x6cf4073e, 0xd7722f04, 0x09881dc5, 0xfed4bec3, 0x004103f4, + 0xffeff6ca, 0x0070c8a5, 0x0274ba43, 0x07ee507c, 0x3572ec70, 0x6cc59bab, 0xd8d7f21f, 0x099ec3dc, 0xfef3f6ab, 0x003c1fa4, + 0xffef7b8b, 0x0075fded, 0x029e35b4, 0x07a8127d, 0x36e69691, 0x6c8c4c7a, 0xda3b176a, 0x09b18a1d, 0xff120d70, 0x003745f9, + 0xffeedfa4, 0x007b3875, 0x02c89901, 0x075ca90c, 0x385a49c4, 0x6c492217, 0xdb9b5b12, 0x09c018ce, 0xff2ef725, 0x00329ab6, + 0xffee1650, 0x00807994, 0x02f3e48d, 0x070bbf58, 0x39ce0477, 0x6bfbdd98, 0xdcf898fb, 0x09caeb0f, 0xff4aabc8, 0x002d8e42, + 0xffed651d, 0x0085c217, 0x03201116, 0x06b559c3, 0x3b415115, 0x6ba4629f, 0xde529086, 0x09d1fa23, 0xff6542d1, 0x00293718, + 0xffecc31b, 0x008a7dd7, 0x034d01f0, 0x06593912, 0x3cb41219, 0x6b42a864, 0xdfa93ab5, 0x09d5560b, 0xff7ee3f1, 0x0024dd50, + 0xffebe77b, 0x008f4bfc, 0x037ad438, 0x05f7fb90, 0x3e25b17e, 0x6ad73e8d, 0xe0fc421e, 0x09d52709, 0xff975c01, 0x002064f8, + 0xffeb50b2, 0x009424c6, 0x03a966bb, 0x0590a67d, 0x3f962fb8, 0x6a619c5e, 0xe24b8f66, 0x09d19ca9, 0xffaea5d6, 0x001c3549, + 0xffea9192, 0x0098b855, 0x03d8afe6, 0x05237f9d, 0x41058bc6, 0x69e29784, 0xe396a45d, 0x09cab9f2, 0xffc4e365, 0x0018703f, + 0xffe9ca76, 0x009d10bf, 0x04083fec, 0x04b0adcb, 0x4272a385, 0x6959709d, 0xe4de0cb0, 0x09c0e59f, 0xffda17f2, 0x001471f8, + 0xffe940f4, 0x00a1039c, 0x043889c6, 0x0437fb0a, 0x43de620a, 0x68c7269b, 0xe620c476, 0x09b3d77f, 0xffee183b, 0x0010bc63, + 0xffe88ba8, 0x00a520bb, 0x04694101, 0x03b8f8dc, 0x4547daea, 0x682b39a4, 0xe75f8bb8, 0x09a3e163, 0x0000e790, 0x000d31b5, + 0xffe83a07, 0x00a8739d, 0x049aa82f, 0x03343533, 0x46aea856, 0x6785c24d, 0xe89971b7, 0x099140a7, 0x00131c75, 0x0009aa3f, + 0xffe79e16, 0x00abe79e, 0x04cc2fcf, 0x02a99097, 0x4812f848, 0x66d76725, 0xe9cea84a, 0x097c1ee8, 0x0023b989, 0x0006b1cf, + 0xffe7746e, 0x00af374c, 0x04fe20be, 0x02186a91, 0x4973fef1, 0x661fd6b8, 0xeafee7f1, 0x0963ed46, 0x0033b927, 0x00039609, + 0xffe6d466, 0x00b1978d, 0x05303f87, 0x01816e06, 0x4ad237a2, 0x655f63f2, 0xec2a3f5f, 0x0949eaac, 0x00426f36, 0x00007134, + 0xffe6afee, 0x00b3d15c, 0x05626209, 0x00e42fa2, 0x4c2ca3df, 0x64964063, 0xed50a31d, 0x092d7970, 0x00504f41, 0xfffdfa25, + 0xffe65416, 0x00b5c867, 0x05950122, 0x0040c496, 0x4d83976c, 0x63c45243, 0xee71b2fe, 0x090ec1fc, 0x005d36df, 0xfffb42b0, + 0xffe681c6, 0x00b74c37, 0x05c76fed, 0xff96db90, 0x4ed62be3, 0x62ea6474, 0xef8d4d7b, 0x08edfeaa, 0x006928a0, 0xfff91fca, + 0xffe66dd0, 0x00b8394b, 0x05f9c051, 0xfee723c6, 0x5024d70e, 0x6207f220, 0xf0a3959f, 0x08cb4e23, 0x007400b8, 0xfff681d6, + 0xffe66fac, 0x00b8fe0d, 0x062bf5ec, 0xfe310657, 0x516eefb9, 0x611d58a3, 0xf1b461ab, 0x08a75da4, 0x007e0393, 0xfff48700, + 0xffe69423, 0x00b8c6b0, 0x065dd56a, 0xfd7475d8, 0x52b449de, 0x602b0c7f, 0xf2bf6ea4, 0x0880ffdd, 0x00872c63, 0xfff294c3, + 0xffe6fed4, 0x00b85f70, 0x068f8b44, 0xfcb1d740, 0x53f495aa, 0x5f30ff5f, 0xf3c4e887, 0x08594887, 0x008f87aa, 0xfff0e7ef, + 0xffe75361, 0x00b73ab0, 0x06c0f0c0, 0xfbe8f5bd, 0x552f8ff7, 0x5e2f6367, 0xf4c473c6, 0x08303897, 0x0096dcc2, 0xffef2395, + 0xffe80414, 0x00b58c8c, 0x06f1825d, 0xfb19b7bd, 0x56654bdd, 0x5d26be9b, 0xf5be0fa9, 0x08061671, 0x009da526, 0xffedc418, + 0xffe85b4b, 0x00b36acd, 0x0721bf22, 0xfa44a069, 0x579505f5, 0x5c16d0ae, 0xf6b1f3c3, 0x07da2b7f, 0x00a3508f, 0xffec8409, + 0xffe954d0, 0x00b06b68, 0x075112a2, 0xf96916f5, 0x58befacd, 0x5b001db8, 0xf79fa13a, 0x07ad8c26, 0x00a85e94, 0xffeb3849, + 0xffea353a, 0x00acbd2f, 0x077fedb3, 0xf887507c, 0x59e2f69e, 0x59e2f69e, 0xf887507c, 0x077fedb3, 0x00acbd2f, 0xffea353a, + 0xffeb3849, 0x00a85e94, 0x07ad8c26, 0xf79fa13a, 0x5b001db8, 0x58befacd, 0xf96916f5, 0x075112a2, 0x00b06b68, 0xffe954d0, + 0xffec8409, 0x00a3508f, 0x07da2b7f, 0xf6b1f3c3, 0x5c16d0ae, 0x579505f5, 0xfa44a069, 0x0721bf22, 0x00b36acd, 0xffe85b4b, + 0xffedc418, 0x009da526, 0x08061671, 0xf5be0fa9, 0x5d26be9b, 0x56654bdd, 0xfb19b7bd, 0x06f1825d, 0x00b58c8c, 0xffe80414, + 0xffef2395, 0x0096dcc2, 0x08303897, 0xf4c473c6, 0x5e2f6367, 0x552f8ff7, 0xfbe8f5bd, 0x06c0f0c0, 0x00b73ab0, 0xffe75361, + 0xfff0e7ef, 0x008f87aa, 0x08594887, 0xf3c4e887, 0x5f30ff5f, 0x53f495aa, 0xfcb1d740, 0x068f8b44, 0x00b85f70, 0xffe6fed4, + 0xfff294c3, 0x00872c63, 0x0880ffdd, 0xf2bf6ea4, 0x602b0c7f, 0x52b449de, 0xfd7475d8, 0x065dd56a, 0x00b8c6b0, 0xffe69423, + 0xfff48700, 0x007e0393, 0x08a75da4, 0xf1b461ab, 0x611d58a3, 0x516eefb9, 0xfe310657, 0x062bf5ec, 0x00b8fe0d, 0xffe66fac, + 0xfff681d6, 0x007400b8, 0x08cb4e23, 0xf0a3959f, 0x6207f220, 0x5024d70e, 0xfee723c6, 0x05f9c051, 0x00b8394b, 0xffe66dd0, + 0xfff91fca, 0x006928a0, 0x08edfeaa, 0xef8d4d7b, 0x62ea6474, 0x4ed62be3, 0xff96db90, 0x05c76fed, 0x00b74c37, 0xffe681c6, + 0xfffb42b0, 0x005d36df, 0x090ec1fc, 0xee71b2fe, 0x63c45243, 0x4d83976c, 0x0040c496, 0x05950122, 0x00b5c867, 0xffe65416, + 0xfffdfa25, 0x00504f41, 0x092d7970, 0xed50a31d, 0x64964063, 0x4c2ca3df, 0x00e42fa2, 0x05626209, 0x00b3d15c, 0xffe6afee, + 0x00007134, 0x00426f36, 0x0949eaac, 0xec2a3f5f, 0x655f63f2, 0x4ad237a2, 0x01816e06, 0x05303f87, 0x00b1978d, 0xffe6d466, + 0x00039609, 0x0033b927, 0x0963ed46, 0xeafee7f1, 0x661fd6b8, 0x4973fef1, 0x02186a91, 0x04fe20be, 0x00af374c, 0xffe7746e, + 0x0006b1cf, 0x0023b989, 0x097c1ee8, 0xe9cea84a, 0x66d76725, 0x4812f848, 0x02a99097, 0x04cc2fcf, 0x00abe79e, 0xffe79e16, + 0x0009aa3f, 0x00131c75, 0x099140a7, 0xe89971b7, 0x6785c24d, 0x46aea856, 0x03343533, 0x049aa82f, 0x00a8739d, 0xffe83a07, + 0x000d31b5, 0x0000e790, 0x09a3e163, 0xe75f8bb8, 0x682b39a4, 0x4547daea, 0x03b8f8dc, 0x04694101, 0x00a520bb, 0xffe88ba8, + 0x0010bc63, 0xffee183b, 0x09b3d77f, 0xe620c476, 0x68c7269b, 0x43de620a, 0x0437fb0a, 0x043889c6, 0x00a1039c, 0xffe940f4, + 0x001471f8, 0xffda17f2, 0x09c0e59f, 0xe4de0cb0, 0x6959709d, 0x4272a385, 0x04b0adcb, 0x04083fec, 0x009d10bf, 0xffe9ca76, + 0x0018703f, 0xffc4e365, 0x09cab9f2, 0xe396a45d, 0x69e29784, 0x41058bc6, 0x05237f9d, 0x03d8afe6, 0x0098b855, 0xffea9192, + 0x001c3549, 0xffaea5d6, 0x09d19ca9, 0xe24b8f66, 0x6a619c5e, 0x3f962fb8, 0x0590a67d, 0x03a966bb, 0x009424c6, 0xffeb50b2, + 0x002064f8, 0xff975c01, 0x09d52709, 0xe0fc421e, 0x6ad73e8d, 0x3e25b17e, 0x05f7fb90, 0x037ad438, 0x008f4bfc, 0xffebe77b, + 0x0024dd50, 0xff7ee3f1, 0x09d5560b, 0xdfa93ab5, 0x6b42a864, 0x3cb41219, 0x06593912, 0x034d01f0, 0x008a7dd7, 0xffecc31b, + 0x00293718, 0xff6542d1, 0x09d1fa23, 0xde529086, 0x6ba4629f, 0x3b415115, 0x06b559c3, 0x03201116, 0x0085c217, 0xffed651d, + 0x002d8e42, 0xff4aabc8, 0x09caeb0f, 0xdcf898fb, 0x6bfbdd98, 0x39ce0477, 0x070bbf58, 0x02f3e48d, 0x00807994, 0xffee1650, + 0x00329ab6, 0xff2ef725, 0x09c018ce, 0xdb9b5b12, 0x6c492217, 0x385a49c4, 0x075ca90c, 0x02c89901, 0x007b3875, 0xffeedfa4, + 0x003745f9, 0xff120d70, 0x09b18a1d, 0xda3b176a, 0x6c8c4c7a, 0x36e69691, 0x07a8127d, 0x029e35b4, 0x0075fded, 0xffef7b8b, + 0x003c1fa4, 0xfef3f6ab, 0x099ec3dc, 0xd8d7f21f, 0x6cc59bab, 0x3572ec70, 0x07ee507c, 0x0274ba43, 0x0070c8a5, 0xffeff6ca, + 0x004103f4, 0xfed4bec3, 0x09881dc5, 0xd7722f04, 0x6cf4073e, 0x33ff670e, 0x082f552e, 0x024bf7a1, 0x006b47fa, 0xfff0065d, + 0x00465348, 0xfeb48d0d, 0x096d0e21, 0xd60a46e5, 0x6d18520e, 0x328cc6f0, 0x086b1eeb, 0x02244a24, 0x0065fde5, 0xffefc9b9, + 0x004b6c46, 0xfe933dc0, 0x094d7ec2, 0xd49fd55f, 0x6d32730f, 0x311af3a4, 0x08a24899, 0x01fd3ba0, 0x006090c4, 0xffed978a, + 0x0050b177, 0xfe70b8d1, 0x09299ead, 0xd3337b3d, 0x6d41d963, 0x2faa221c, 0x08d3e41b, 0x01d78bfc, 0x005b5371, 0xffede50f, +}; + +const HuffInfo_t huffTabScaleFactInfo PROGMEM = + {19, { 1, 0, 1, 3, 2, 4, 3, 5, 4, 6, 6, 6, 5, 8, 4, 7, 3, 7, 46, 0}, 0}; + +/* note - includes offset of -60 (4.6.2.3 in spec) */ +const short huffTabScaleFact[121] PROGMEM = { /* scale factor table [121] */ + 0, -1, 1, -2, 2, -3, 3, -4, 4, -5, 5, 6, -6, 7, -7, 8, + -8, 9, -9, 10, -10, -11, 11, 12, -12, 13, -13, 14, -14, 16, 15, 17, + 18, -15, -17, -16, 19, -18, -19, 20, -20, 21, -21, 22, -22, 23, -23, -25, + 25, -27, -24, -26, 24, -28, 27, 29, -30, -29, 26, -31, -34, -33, -32, -36, + 28, -35, -38, -37, 30, -39, -41, -57, -59, -58, -60, 38, 39, 40, 41, 42, + 57, 37, 31, 32, 33, 34, 35, 36, 44, 51, 52, 53, 54, 55, 56, 50, + 45, 46, 47, 48, 49, 58, -54, -52, -51, -50, -55, 43, 60, 59, -56, -53, + -45, -44, -42, -40, -43, -49, -48, -46, -47, +}; + +/* noise table 4.A.88, format = Q31 */ +const uint32_t noiseTab[512*2] PROGMEM = { + 0x8010fd38, 0xb3dc7948, 0x7c4e2301, 0xa9904192, 0x121622a7, 0x86489625, 0xc3d53d25, 0xd0343fa9, + 0x674d6f70, 0x25f4e9fd, 0xce1a8c8b, 0x72a726c5, 0xfea6efc6, 0xaa4adb1a, 0x8b2dd628, 0xf14029e4, + 0x46321c1a, 0x604889a0, 0x33363b63, 0x815ed069, 0x802b4315, 0x8f2bf7f3, 0x85b86073, 0x745cfb46, + 0xc57886b3, 0xb76731f0, 0xa2a66772, 0x828ca631, 0x60cc145e, 0x1ad1010f, 0x090c83d4, 0x9bd7ba87, + 0x5f5aeea2, 0x8b4dbd99, 0x848e7b1e, 0x86bb9fa2, 0x26f18ae5, 0xc0b81194, 0x553407bf, 0x52c17953, + 0x755f468d, 0x166b04f8, 0xa5687981, 0x4343248b, 0xa6558d5e, 0xc5f6fab7, 0x80a4fb8c, 0x8cb53cb7, + 0x7da68a54, 0x9cd8df8a, 0xba05376c, 0xfcb58ee2, 0xfdd657a4, 0x005e35ca, 0x91c75c55, 0x367651e6, + 0x816abf85, 0x8f831c4f, 0x423f9c9c, 0x55aa919e, 0x80779834, 0xb59f4244, 0x800a095c, 0x7de9e0cc, + 0x46bda5cb, 0x4c184464, 0x2c438f71, 0x797216b5, 0x5035cee6, 0xa0c3a26e, 0x9d3f95fa, 0xd4a100c0, + 0x8ac30dac, 0x04b87397, 0x9e5ac516, 0x8b0b442e, 0x66210ad6, 0x88ba7598, 0x45b9bd33, 0xf0be5087, + 0x9261b85e, 0x364f6a31, 0x891c4b50, 0x23ad08ce, 0xf10366a6, 0x80414276, 0x1b562e06, 0x8be21591, + 0x9e798195, 0x7fb4045c, 0x7d9506cf, 0x854e691f, 0x9207f092, 0x7a94c9d5, 0x88911536, 0x3f45cc61, + 0x27059279, 0xa5b57109, 0x6d2bb67b, 0x3bdc5379, 0x74e662d8, 0x80348f8c, 0xf875e638, 0x5a8caea1, + 0x2459ae75, 0x2c54b939, 0x79ee3203, 0xb9bc8683, 0x9b6f630c, 0x9f45b351, 0x8563b2b9, 0xe5dbba41, + 0x697c7d0d, 0x7bb7c90e, 0xac900866, 0x8e6b5177, 0x8822dd37, 0x7fd5a91e, 0x7506da05, 0x82302aca, + 0xa5e4be04, 0x4b4288eb, 0x00b8bc9f, 0x4f1033e4, 0x7200d612, 0x43900c8c, 0xa815b900, 0x676ed1d4, + 0x5c5f23b2, 0xa758ee11, 0xaf73abfa, 0x11714ec0, 0x265239e0, 0xc50de679, 0x8a84e341, 0xa1438354, + 0x7f1a341f, 0x343ec96b, 0x696e71b0, 0xa13bde39, 0x81e75094, 0x80091111, 0x853a73bf, 0x80f9c1ee, + 0xe4980086, 0x886a8e28, 0xa7e89426, 0xdd93edd7, 0x7592100d, 0x0bfa8123, 0x850a26d4, 0x2e34f395, + 0x421b6c00, 0xa4a462e4, 0x4e3f5090, 0x3c189f4c, 0x3c971a56, 0xdd0376d2, 0x747a5367, 0x7bcbc9d7, + 0x3966be6a, 0x7efda616, 0x55445e15, 0x7ba2ab3f, 0x5fe684f2, 0x8cf42af9, 0x808c61c3, 0x4390c27b, + 0x7cac62ff, 0xea6cab22, 0x5d0902ad, 0xc27b7208, 0x7a27389d, 0x5820a357, 0xa29bbe59, 0x9df0f1fd, + 0x92bd67e5, 0x7195b587, 0x97cac65b, 0x8339807e, 0x8f72d832, 0x5fad8685, 0xa462d9d3, 0x81d46214, + 0x6ae93e1d, 0x6b23a5b9, 0xc2732874, 0x81795268, 0x7c568cb6, 0x668513ea, 0x428d024e, 0x66b78b3a, + 0xfee9ef03, 0x9ddcbb82, 0xa605f07e, 0x46dc55e0, 0x85415054, 0xc89ec271, 0x7c42edfb, 0x0befe59b, + 0x89b8f607, 0x6d732a1a, 0xa7081ebd, 0x7e403258, 0x21feeb7b, 0x5dd7a1e7, 0x23e3a31a, 0x129bc896, + 0xa11a6b54, 0x7f1e031c, 0xfdc1a4d1, 0x96402e53, 0xb9700f1a, 0x8168ecd6, 0x7d63d3cc, 0x87a70d65, + 0x81075a7a, 0x55c8caa7, 0xa95d00b5, 0x102b1652, 0x0bb30215, 0xe5b63237, 0xa446ca44, 0x82d4c333, + 0x67b2e094, 0x44c3d661, 0x33fd6036, 0xde1ea2a1, 0xa95e8e47, 0x78f66eb9, 0x6f2aef1e, 0xe8887247, + 0x80a3b70e, 0xfca0d9d3, 0x6bf0fd20, 0x0d5226de, 0xf4341c87, 0x5902df05, 0x7ff1a38d, 0xf02e5a5b, + 0x99f129af, 0x8ac63d01, 0x7b53f599, 0x7bb32532, 0x99ac59b0, 0x5255a80f, 0xf1320a41, 0x2497aa5c, + 0xcce60bd8, 0x787c634b, 0x7ed58c5b, 0x8a28eb3a, 0x24a5e647, 0x8b79a2c1, 0x955f5ce5, 0xa9d12bc4, + 0x7a1e20c6, 0x3eeda7ac, 0xf7be823a, 0x042924ce, 0x808b3f03, 0x364248da, 0xac2895e5, 0x69a8b5fa, + 0x97fe8b63, 0xbdeac9aa, 0x8073e0ad, 0x6c25dba7, 0x005e51d2, 0x52e74389, 0x59d3988c, 0xe5d1f39c, + 0x7b57dc91, 0x341adbe7, 0xa7d42b8d, 0x74e9f335, 0xd35bf7d8, 0x5b7c0a4b, 0x75bc0874, 0x552129bf, + 0x8144b70d, 0x6de93bbb, 0x5825f14b, 0x473ec5ca, 0x80a8f37c, 0xe6552d69, 0x7898360b, 0x806379b0, + 0xa9b59339, 0x3f6bf60c, 0xc367d731, 0x920ade99, 0x125592f7, 0x877e5ed1, 0xda895d95, 0x075f2ece, + 0x380e5f5e, 0x9b006b62, 0xd17a6dd2, 0x530a0e13, 0xf4cc9a14, 0x7d0a0ed4, 0x847c6e3f, 0xbaee4975, + 0x47131163, 0x64fb2cac, 0x5e2100a6, 0x7b756a42, 0xd87609f4, 0x98bfe48c, 0x0493745e, 0x836c5784, + 0x7e5ccb40, 0x3df6b476, 0x97700d28, 0x8bbd93fd, 0x56de9cdb, 0x680b4e65, 0xebc3d90e, 0x6d286793, + 0x6753712e, 0xe05c98a7, 0x3d2b6b85, 0xc4b18ddb, 0x7b59b869, 0x31435688, 0x811888e9, 0xe011ee7a, + 0x6a5844f9, 0x86ae35ea, 0xb4cbc10b, 0x01a6f5d6, 0x7a49ed64, 0x927caa49, 0x847ddaed, 0xae0d9bb6, + 0x836bdb04, 0x0fd810a6, 0x74fe126b, 0x4a346b5f, 0x80184d36, 0x5afd153c, 0x90cc8102, 0xe606d0e6, + 0xde69aa58, 0xa89f1222, 0xe06df715, 0x8fd16144, 0x0317c3e8, 0x22ce92fc, 0x690c3eca, 0x93166f02, + 0x71573414, 0x8d43cffb, 0xe8bd0bb6, 0xde86770f, 0x0bf99a41, 0x4633a661, 0xba064108, 0x7adafae3, + 0x2f6cde5d, 0xb350a52c, 0xa5ebfb0b, 0x74c57b46, 0xd3b603b5, 0x80b70892, 0xa7f7fa53, 0xd94b566c, + 0xdda3fd86, 0x6a635793, 0x3ed005ca, 0xc5f087d8, 0x31e3a746, 0x7a4278f9, 0x82def1f9, 0x06caa2b2, + 0xe9d2c349, 0x8940e7f7, 0x7feef8dd, 0x4a9b01f0, 0xacde69f8, 0x57ddc280, 0xf09e4ba4, 0xb6d9f729, + 0xb48c18f2, 0xd3654aa9, 0xca7a03c8, 0x14d57545, 0x7fda87a5, 0x0e411366, 0xb77d0df0, 0x8c2aa467, + 0x787f2590, 0x2d292db1, 0x9f12682c, 0x44ac364d, 0x1a4b31a6, 0x871f7ded, 0x7ff99167, 0x6630a1d5, + 0x25385eb9, 0x2d4dd549, 0xaf8a7004, 0x319ebe0f, 0x379ab730, 0x81dc56a4, 0x822d8523, 0x1ae8554c, + 0x18fa0786, 0x875f7de4, 0x85ca350f, 0x7de818dc, 0x7786a38f, 0xa5456355, 0x92e60f88, 0xf5526122, + 0x916039bc, 0xc561e2de, 0x31c42042, 0x7c82e290, 0x75d158b2, 0xb015bda1, 0x7220c750, 0x46565441, + 0xd0da1fdd, 0x7b777481, 0x782e73c6, 0x8cd72b7b, 0x7f1006aa, 0xfb30e51e, 0x87994818, 0x34e7c7db, + 0x7faae06b, 0xea74fbc0, 0xd20c7af4, 0xc44f396b, 0x06b4234e, 0xdf2e2a93, 0x2efb07c8, 0xce861911, + 0x7550ea05, 0xd8d90bbb, 0x58522eec, 0x746b3520, 0xce844ce9, 0x7f5cacc3, 0xda8f17e0, 0x2fedf9cb, + 0xb2f77ec4, 0x6f13f4c0, 0x834de085, 0x7b7ace4b, 0x713b16ac, 0x499c5ab0, 0x06a7961d, 0x1b39a48a, + 0xbb853e6e, 0x7c781cc1, 0xc0baebf5, 0x7dace394, 0x815ceebc, 0xcc7b27d4, 0x8274b181, 0xa2be40a2, + 0xdd01d5dc, 0x7fefeb14, 0x0813ec78, 0xba3077cc, 0xe5cf1e1c, 0xedcfacae, 0x54c43a9b, 0x5cd62a42, + 0x93806b55, 0x03095c5b, 0x8e076ae3, 0x71bfcd2a, 0x7ac1989b, 0x623bc71a, 0x5e15d4d2, 0xfb341dd1, + 0xd75dfbca, 0xd0da32be, 0xd4569063, 0x337869da, 0x3d30606a, 0xcd89cca2, 0x7dd2ae36, 0x028c03cd, + 0xd85e052c, 0xe8dc9ec5, 0x7ffd9241, 0xde5bf4c6, 0x88c4b235, 0x8228be2e, 0x7fe6ec64, 0x996abe6a, + 0xdeb0666d, 0x9eb86611, 0xd249b922, 0x18b3e26b, 0x80211168, 0x5f8bb99c, 0x6ecb0dd2, 0x4728ff8d, + 0x2ac325b8, 0x6e5169d2, 0x7ebbd68d, 0x05e41d17, 0xaaa19f28, 0x8ab238a6, 0x51f105be, 0x140809cc, + 0x7f7345d9, 0x3aae5a9d, 0xaecec6e4, 0x1afb3473, 0xf6229ed1, 0x8d55f467, 0x7e32003a, 0x70f30c14, + 0x6686f33f, 0xd0d45ed8, 0x644fab57, 0x3a3fbbd3, 0x0b255fc4, 0x679a1701, 0x90e17b6e, 0x325d537b, + 0xcd7b9b87, 0xaa7be2a2, 0x7d47c966, 0xa33dbce5, 0x8659c3bb, 0x72a41367, 0x15c446e0, 0x45fe8b0a, + 0x9d8ddf26, 0x84d47643, 0x7fabe0da, 0x36a70122, 0x7a28ebfe, 0x7c29b8b8, 0x7f760406, 0xbabe4672, + 0x23ea216e, 0x92bcc50a, 0x6d20dba2, 0xad5a7c7e, 0xbf3897f5, 0xabb793e1, 0x8391fc7e, 0xe270291c, + 0x7a248d58, 0x80f8fd15, 0x83ef19f3, 0x5e6ece7d, 0x278430c1, 0x35239f4d, 0xe09c073b, 0x50e78cb5, + 0xd4b811bd, 0xce834ee0, 0xf88aaa34, 0xf71da5a9, 0xe2b0a1d5, 0x7c3aef31, 0xe84eabca, 0x3ce25964, + 0xf29336d3, 0x8fa78b2c, 0xa3fc3415, 0x63e1313d, 0x7fbc74e0, 0x7340bc93, 0x49ae583b, 0x8b79de4b, + 0x25011ce9, 0x7b462279, 0x36007db0, 0x3da1599c, 0x77780772, 0xc845c9bb, 0x83ba68be, 0x6ee507d1, + 0x2f0159b8, 0x5392c4ed, 0x98336ff6, 0x0b3c7f11, 0xde697aac, 0x893fc8d0, 0x6b83f8f3, 0x47799a0d, + 0x801d9dfc, 0x8516a83e, 0x5f8d22ec, 0x0f8ba384, 0xa049dc4b, 0xdd920b05, 0x7a99bc9f, 0x9ad19344, + 0x7a345dba, 0xf501a13f, 0x3e58bf19, 0x7fffaf9a, 0x3b4e1511, 0x0e08b991, 0x9e157620, 0x7230a326, + 0x4977f9ff, 0x2d2bbae1, 0x607aa7fc, 0x7bc85d5f, 0xb441bbbe, 0x8d8fa5f2, 0x601cce26, 0xda1884f2, + 0x81c82d64, 0x200b709c, 0xcbd36abe, 0x8cbdddd3, 0x55ab61d3, 0x7e3ee993, 0x833f18aa, 0xffc1aaea, + 0x7362e16a, 0x7fb85db2, 0x904ee04c, 0x7f04dca6, 0x8ad7a046, 0xebe7d8f7, 0xfbc4c687, 0xd0609458, + 0x093ed977, 0x8e546085, 0x7f5b8236, 0x7c47e118, 0xa01f2641, 0x7ffb3e48, 0x05de7cda, 0x7fc281b9, + 0x8e0278fc, 0xd74e6d07, 0x94c24450, 0x7cf9e641, 0x2ad27871, 0x919fa815, 0x805fd205, 0x7758397f, + 0xe2c7e02c, 0x1828e194, 0x5613d6fe, 0xfb55359f, 0xf9699516, 0x8978ee26, 0x7feebad9, 0x77d71d82, + 0x55b28b60, 0x7e997600, 0x80821a6b, 0xc6d78af1, 0x691822ab, 0x7f6982a0, 0x7ef56f99, 0x5c307f40, + 0xac6f8b76, 0x42cc8ba4, 0x782c61d9, 0xa0224dd0, 0x7bd234d1, 0x74576e3b, 0xe38cfe9a, 0x491e66ef, + 0xc78291c5, 0x895bb87f, 0x924f7889, 0x71b89394, 0x757b779d, 0xc4a9c604, 0x5cdf7829, 0x8020e9df, + 0x805e8245, 0x4a82c398, 0x6360bd62, 0x78bb60fc, 0x09e0d014, 0x4b0ea180, 0xb841978b, 0x69a0e864, + 0x7df35977, 0x3284b0dd, 0x3cdc2efd, 0x57d31f5e, 0x541069cc, 0x1776e92e, 0x04309ea3, 0xa015eb2d, + 0xce7bfabc, 0x41b638f8, 0x8365932e, 0x846ab44c, 0xbbcc80cb, 0x8afa6cac, 0x7fc422ea, 0x4e403fc0, + 0xbfac9aee, 0x8e4c6709, 0x028e01fb, 0x6d160a9b, 0x7fe93004, 0x790f9cdc, 0x6a1f37a0, 0xf7e7ef30, + 0xb4ea0f04, 0x7bf4c8e6, 0xe981701f, 0xc258a9d3, 0x6acbbfba, 0xef5479c7, 0x079c8bd8, 0x1a410f56, + 0x6853b799, 0x86cd4f01, 0xc66e23b6, 0x34585565, 0x8d1fe00d, 0x7fcdba1a, 0x32c9717b, 0xa02f9f48, + 0xf64940db, 0x5ed7d8f1, 0x61b823b2, 0x356f8918, 0xa0a7151e, 0x793fc969, 0x530beaeb, 0x34e93270, + 0x4fc4ddb5, 0x88d58b6c, 0x36094774, 0xf620ac80, 0x03763a72, 0xf910c9a6, 0x6666fb2d, 0x752c8be8, + 0x9a6dfdd8, 0xd1a7117d, 0x51c1b1d4, 0x0a67773d, 0x43b32a79, 0x4cdcd085, 0x5f067d30, 0x05bfe92a, + 0x7ed7d203, 0xe71a3c85, 0x99127ce2, 0x8eb3cac4, 0xad4bbcea, 0x5c6a0fd0, 0x0eec04af, 0x94e95cd4, + 0x8654f921, 0x83eabb5d, 0xb058d7ca, 0x69f12d3c, 0x03d881b2, 0x80558ef7, 0x82938cb3, 0x2ec0e1d6, + 0x80044422, 0xd1e47051, 0x720fc6ff, 0x82b20316, 0x0d527b02, 0x63049a15, 0x7ad5b9ad, 0xd2a4641d, + 0x41144f86, 0x7b04917a, 0x15c4a2c0, 0x9da07916, 0x211df54a, 0x7fdd09af, 0xfe924f3f, 0x7e132cfe, + 0x9a1d18d6, 0x7c56508b, 0x80f0f0af, 0x8095ced6, 0x8037d0d7, 0x026719d1, 0xa55fec43, 0x2b1c7cb7, + 0xa5cd5ac1, 0x77639fad, 0x7fcd8b62, 0x81a18c27, 0xaee4912e, 0xeae9eebe, 0xeb3081de, 0x8532aada, + 0xc822362e, 0x86a649a9, 0x8031a71d, 0x7b319dc6, 0xea8022e6, 0x814bc5a9, 0x8f62f7a1, 0xa430ea17, + 0x388deafb, 0x883b5185, 0x776fe13c, 0x801c683f, 0x87c11b98, 0xb7cbc644, 0x8e9ad3e8, 0x3cf5a10c, + 0x7ff6a634, 0x949ef096, 0x9f84aa7c, 0x010af13f, 0x782d1de8, 0xf18e492a, 0x6cf63b01, 0x4301cd81, + 0x32d15c9e, 0x68ad8cef, 0xd09bd2d6, 0x908c5c15, 0xd1e36260, 0x2c5bfdd0, 0x88765a99, 0x93deba1e, + 0xac6ae342, 0xe865b84c, 0x0f4f2847, 0x7fdf0499, 0x78b1c9b3, 0x6a73261e, 0x601a96f6, 0xd2847933, + 0x489aa888, 0xe12e8093, 0x3bfa5a5f, 0xd96ba5f7, 0x7c8f4c8d, 0x80940c6f, 0xcef9dd1a, 0x7e1a055f, + 0x3483558b, 0x02b59cc4, 0x0c56333e, 0x05a5b813, 0x92d66287, 0x7516b679, 0x71bfe03f, 0x8056bf68, + 0xc24d0724, 0x8416bcf3, 0x234afbdb, 0x4b0d6f9c, 0xaba97333, 0x4b4f42b6, 0x7e8343ab, 0x7ffe2603, + 0xe590f73c, 0x45e10c76, 0xb07a6a78, 0xb35609d3, 0x1a027dfd, 0x90cb6e20, 0x82d3fe38, 0x7b409257, + 0x0e395afa, 0x1b802093, 0xcb0c6c59, 0x241e17e7, 0x1ee3ea0a, 0x41a82302, 0xab04350a, 0xf570beb7, + 0xbb444b9b, 0x83021459, 0x838d65dc, 0x1c439c84, 0x6fdcc454, 0xef9ef325, 0x18626c1c, 0x020d251f, + 0xc4aae786, 0x8614cb48, 0xf6f53ca6, 0x8710dbab, 0x89abec0d, 0xf29d41c1, 0x94b50336, 0xfdd49178, + 0x604658d1, 0x800e85be, 0xca1bb079, 0x7fa48eeb, 0xa3b7fafe, 0xd330436b, 0x64eb604c, 0x43a658ae, + 0x7caa1337, 0xddd445e6, 0x7efbf955, 0xb706ec71, 0x624a6b53, 0x9e0e231f, 0x97097248, 0xa1e1a17a, + 0x68dd2e44, 0x7f9d2e14, 0xddcc7074, 0x58324197, 0xc88fc426, 0x6d3640ae, 0x7ef83600, 0x759a0270, + 0x98b6d854, 0xd63c9b84, 0x372474a2, 0xe3f18cfd, 0x56ab0bdb, 0x85c9be7e, 0x47dfcfeb, 0xa5830d41, + 0x0ddd6283, 0xf4f480ad, 0x74c60e38, 0xab8943c3, 0xc1508fe7, 0x480cdc39, 0x8e097362, 0xa44793be, + 0x538b7e18, 0x545f5b41, 0x56529175, 0x9771a97e, 0xc2da7421, 0xea8265f2, 0x805d1163, 0x883c5d28, + 0x8ba94c48, 0x4f676e65, 0xf78735b3, 0xe1853671, 0x7f454f53, 0x18147f85, 0x7d09e15d, 0xdb4f3494, + 0x795c8973, 0x83310632, 0x85d8061c, 0x9a1a0ebf, 0xc125583c, 0x2a1b1a95, 0x7fd9103f, 0x71e98c72, + 0x40932ed7, 0x91ed227a, 0x3c5e560e, 0xe816dee9, 0xb0891b80, 0x600038ba, 0xc7d9a80d, 0x7fff5e09, + 0x7e3f4351, 0xbb6b4424, 0xb14448d4, 0x8d6bb7e1, 0xfb153626, 0xa68ad537, 0xd9782006, 0xf62f6991, + 0x359ba8c1, 0x02ccff0b, 0x91bf2256, 0x7ea71c4d, 0x560ce5df, 0xeeba289b, 0xa574c4e7, 0x9e04f6ee, + 0x7860a5ec, 0x0b8db4a2, 0x968ba3d7, 0x0b6c77df, 0xd6f3157d, 0x402eff1a, 0x49b820b3, 0x8152aebb, + 0xd180b0b6, 0x098604d4, 0x7ff92224, 0xede9c996, 0x89c58061, 0x829624c4, 0xc6e71ea7, 0xba94d915, + 0x389c3cf6, 0x5b4c5a06, 0x04b335e6, 0x516a8aab, 0x42c8d7d9, 0x92b12af6, 0x86c8549f, 0xfda98acf, + 0x819673b6, 0x69545dac, 0x6feaa230, 0x726e6d3f, 0x886ebdfe, 0x34f5730a, 0x7af63ba2, 0x77307bbf, + 0x7cd80630, 0x6e45efe0, 0x7f8ad7eb, 0x59d7df99, 0x86c70946, 0xda233629, 0x753f6cbf, 0x825eeb40, +}; + +/* sample rates (table 4.5.1) */ +const int sampRateTab[12] PROGMEM = { + 96000, 88200, 64000, 48000, 44100, 32000, + 24000, 22050, 16000, 12000, 11025, 8000 +}; + +/* max scalefactor band for prediction (main profile only) */ +const uint8_t predSFBMax[12] PROGMEM = { + 33, 33, 38, 40, 40, 40, 41, 41, 37, 37, 37, 34 +}; + +/* channel mapping (table 1.6.3.4) (-1 = unknown, so need to determine mapping based on rules in 8.5.1) */ +const int8_t channelMapTab[8] PROGMEM = { + -1, 1, 2, 3, 4, 5, 6, 8 +}; + +/* number of channels in each element (SCE, CPE, etc.) + * see AACElementID in aaccommon.h + */ +const uint8_t elementNumChans[8] PROGMEM = { + 1, 2, 0, 1, 0, 0, 0, 0 +}; + +/* total number of scale factor bands in one window */ +const uint8_t /*char*/ sfBandTotalShort[12] PROGMEM = { + 12, 12, 12, 14, 14, 14, 15, 15, 15, 15, 15, 15 +}; + +const uint8_t /*char*/ sfBandTotalLong[12] PROGMEM = { + 41, 41, 47, 49, 49, 51, 47, 47, 43, 43, 43, 40 +}; + +/* scale factor band tables */ +const uint8_t sfBandTabShortOffset[12] PROGMEM = {0, 0, 0, 13, 13, 13, 28, 28, 44, 44, 44, 60}; + +const uint16_t sfBandTabShort[76] PROGMEM = { + /* short block 64, 88, 96 kHz [13] (tables 4.5.24, 4.5.26) */ + 0, 4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 92, 128, + + /* short block 32, 44, 48 kHz [15] (table 4.5.15) */ + 0, 4, 8, 12, 16, 20, 28, 36, 44, 56, 68, 80, 96, 112, 128, + + /* short block 22, 24 kHz [16] (table 4.5.22) */ + 0, 4, 8, 12, 16, 20, 24, 28, 36, 44, 52, 64, 76, 92, 108, 128, + + /* short block 11, 12, 16 kHz [16] (table 4.5.20) */ + 0, 4, 8, 12, 16, 20, 24, 28, 32, 40, 48, 60, 72, 88, 108, 128, + + /* short block 8 kHz [16] (table 4.5.18) */ + 0, 4, 8, 12, 16, 20, 24, 28, 36, 44, 52, 60, 72, 88, 108, 128 +}; + +const uint16_t sfBandTabLongOffset[12] PROGMEM = {0, 0, 42, 90, 90, 140, 192, 192, 240, 240, 240, 284}; + +const uint16_t sfBandTabLong[325] PROGMEM = { + /* long block 88, 96 kHz [42] (table 4.5.25) */ + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, + 56, 64, 72, 80, 88, 96, 108, 120, 132, 144, 156, 172, 188, 212, + 240, 276, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024, + + /* long block 64 kHz [48] (table 4.5.13) */ + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 64, + 72, 80, 88, 100, 112, 124, 140, 156, 172, 192, 216, 240, 268, 304, 344, 384, + 424, 464, 504, 544, 584, 624, 664, 704, 744, 784, 824, 864, 904, 944, 984, 1024, + + /* long block 44, 48 kHz [50] (table 4.5.14) */ + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 48, 56, 64, 72, 80, 88, + 96, 108, 120, 132, 144, 160, 176, 196, 216, 240, 264, 292, 320, 352, 384, 416, 448, + 480, 512, 544, 576, 608, 640, 672, 704, 736, 768, 800, 832, 864, 896, 928, 1024, + + /* long block 32 kHz [52] (table 4.5.16) */ + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 48, 56, 64, 72, 80, 88, 96, + 108, 120, 132, 144, 160, 176, 196, 216, 240, 264, 292, 320, 352, 384, 416, 448, 480, 512, + 544, 576, 608, 640, 672, 704, 736, 768, 800, 832, 864, 896, 928, 960, 992, 1024, + + /* long block 22, 24 kHz [48] (table 4.5.21) */ + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 52, 60, 68, 76, + 84, 92, 100, 108, 116, 124, 136, 148, 160, 172, 188, 204, 220, 240, 260, 284, + 308, 336, 364, 396, 432, 468, 508, 552, 600, 652, 704, 768, 832, 896, 960, 1024, + + /* long block 11, 12, 16 kHz [44] (table 4.5.19) */ + 0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 100, 112, 124, + 136, 148, 160, 172, 184, 196, 212, 228, 244, 260, 280, 300, 320, 344, 368, + 396, 424, 456, 492, 532, 572, 616, 664, 716, 772, 832, 896, 960, 1024, + + /* long block 8 kHz [41] (table 4.5.17) */ + 0, 12, 24, 36, 48, 60, 72, 84, 96, 108, 120, 132, 144, 156, + 172, 188, 204, 220, 236, 252, 268, 288, 308, 328, 348, 372, 396, 420, + 448, 476, 508, 544, 580, 620, 664, 712, 764, 820, 880, 944, 1024 +}; + +/* TNS max bands (table 4.139) and max order (table 4.138) */ +const uint8_t tnsMaxBandsShortOffset[3] PROGMEM = {0, 0, 12}; + +const uint16_t tnsMaxBandsShort[2*12] PROGMEM = { + 9, 9, 10, 14, 14, 14, 14, 14, 14, 14, 14, 14, /* short block, Main/LC */ + 7, 7, 7, 6, 6, 6, 7, 7, 8, 8, 8, 7 /* short block, SSR */ +}; + +const uint8_t tnsMaxOrderShort[3] PROGMEM = {7, 7, 7}; + +const uint8_t tnsMaxBandsLongOffset[3] PROGMEM = {0, 0, 12}; + +const uint16_t tnsMaxBandsLong[2*12] PROGMEM = { + 31, 31, 34, 40, 42, 51, 46, 46, 42, 42, 42, 39, /* long block, Main/LC */ + 28, 28, 27, 26, 26, 26, 29, 29, 23, 23, 23, 19, /* long block, SSR */ +}; + +const uint8_t tnsMaxOrderLong[3] PROGMEM = {20, 12, 12}; + + +/* k0Tab[sampRateIdx][k] = k0 = startMin + offset(bs_start_freq) for given sample rate (4.6.18.3.2.1) + * downsampled (single-rate) SBR not currently supported + */ +const uint8_t k0Tab[NUM_SAMPLE_RATES_SBR][16] = { + { 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 23, 27, 31 }, /* 96 kHz */ + { 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 23, 27, 31 }, /* 88 kHz */ + { 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 21, 23, 26, 30 }, /* 64 kHz */ + { 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 22, 24, 27, 31 }, /* 48 kHz */ + { 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 21, 23, 25, 28, 32 }, /* 44 kHz */ + { 10, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 27, 29, 32 }, /* 32 kHz */ + { 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 27, 29, 32 }, /* 24 kHz */ + { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 26, 28, 30 }, /* 22 kHz */ + { 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 }, /* 16 kHz */ +}; + + +/* k2Tab[sampRateIdx][k] = stopVector(bs_stop_freq) for given sample rate, bs_stop_freq = [0, 13] (4.6.18.3.2.1) + * generated with Matlab script calc_stopvec.m + * downsampled (single-rate) SBR not currently supported + */ +const uint8_t k2Tab[NUM_SAMPLE_RATES_SBR][14] = { + { 13, 15, 17, 19, 21, 24, 27, 31, 35, 39, 44, 50, 57, 64 }, /* 96 kHz */ + { 15, 17, 19, 21, 23, 26, 29, 33, 37, 41, 46, 51, 57, 64 }, /* 88 kHz */ + { 20, 22, 24, 26, 28, 31, 34, 37, 41, 45, 49, 54, 59, 64 }, /* 64 kHz */ + { 21, 23, 25, 27, 29, 32, 35, 38, 41, 45, 49, 54, 59, 64 }, /* 48 kHz */ + { 23, 25, 27, 29, 31, 34, 37, 40, 43, 47, 51, 55, 59, 64 }, /* 44 kHz */ + { 32, 34, 36, 38, 40, 42, 44, 46, 49, 52, 55, 58, 61, 64 }, /* 32 kHz */ + { 32, 34, 36, 38, 40, 42, 44, 46, 49, 52, 55, 58, 61, 64 }, /* 24 kHz */ + { 35, 36, 38, 40, 42, 44, 46, 48, 50, 52, 55, 58, 61, 64 }, /* 22 kHz */ + { 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 60, 62, 64 }, /* 16 kHz */ +}; + +const HuffInfo_t huffTabSBRInfo[10] PROGMEM = { + {19, { 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, 2, 7, 4, 8, 72, 0}, 0}, + {20, { 0, 2, 2, 2, 2, 2, 1, 3, 3, 2, 4, 4, 4, 3, 2, 5, 6, 13, 15, 46}, 121}, + {17, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 0, 0, 1, 25, 10, 0, 0, 0}, 242}, + {19, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 3, 1, 0, 1, 1, 2, 1, 29, 2, 0}, 291}, + {19, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 1, 2, 5, 1, 4, 2, 3, 34, 0}, 340}, + {20, { 1, 1, 1, 1, 1, 1, 0, 2, 2, 2, 2, 2, 1, 2, 3, 4, 4, 7, 10, 16}, 403}, + {14, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 13, 2, 0, 0, 0, 0, 0, 0}, 466}, + {14, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 6, 8, 0, 0, 0, 0, 0, 0}, 491}, + {14, { 1, 1, 1, 1, 1, 1, 0, 2, 0, 1, 1, 0, 51, 2, 0, 0, 0, 0, 0, 0}, 516}, + { 8, { 1, 1, 1, 0, 1, 1, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 579}, +}; + +/* Huffman tables from appendix 4.A.6.1, includes offset of -LAV[i] for table i */ +const short huffTabSBR[604] PROGMEM = { + /* SBR table sbr_tenv15 [121] (signed) */ + 0, -1, 1, -2, 2, -3, 3, -4, 4, -5, 5, -6, 6, -7, 7, -8, + -9, 8, -10, 9, -11, 10, -12, -13, 11, -14, 12, -15, -16, 13, -19, -18, + -17, 14, -24, -20, 16, -26, -21, 15, -23, -25, -22, -60, -59, -58, -57, -56, + -55, -54, -53, -52, -51, -50, -49, -48, -47, -46, -45, -44, -43, -42, -41, -40, + -39, -38, -37, -36, -35, -34, -33, -32, -31, -30, -29, -28, -27, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 59, 60, + /* SBR table sbr_fenv15 [121] (signed) */ + 0, -1, 1, -2, -3, 2, -4, 3, -5, 4, -6, 5, -7, 6, -8, 7, + -9, 8, -10, 9, -11, 10, 11, -12, 12, -13, 13, 14, -14, -15, 15, 16, + 17, -16, -17, -18, -19, 18, 19, -20, -21, 20, 21, -24, -23, -22, -26, -28, + 22, 23, 25, -41, -25, 26, 27, -30, -27, 24, 28, 44, -51, -46, -44, -43, + -37, -33, -31, -29, 30, 37, 42, 47, 48, -60, -59, -58, -57, -56, -55, -54, + -53, -52, -50, -49, -48, -47, -45, -42, -40, -39, -38, -36, -35, -34, -32, 29, + 31, 32, 33, 34, 35, 36, 38, 39, 40, 41, 43, 45, 46, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 59, 60, + /* SBR table sbr_tenv15b [49] (signed) */ + 0, 1, -1, 2, -2, 3, -3, 4, -4, -5, 5, -6, 6, 7, -7, 8, + -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, -9, + -8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, + /* SBR table sbr_fenv15b [49] (signed) */ + 0, -1, 1, -2, 2, 3, -3, -4, 4, -5, 5, -6, 6, -7, 7, 8, + -9, -8, -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, -11, + -10, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, + /* SBR table sbr_tenv30 [63] (signed) */ + 0, -1, 1, -2, 2, -3, 3, -4, 4, -5, 5, -6, -7, 6, -8, 7, + -9, -10, 8, 9, 10, -13, -11, -12, -14, 11, 12, -31, -30, -29, -28, -27, + -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + /* SBR table sbr_fenv30 [63] (signed) */ + 0, -1, 1, -2, 2, -3, 3, -4, 4, -5, 5, -6, 6, -7, 7, -8, + 8, 9, -9, -10, 10, 11, -11, -12, 12, 13, -13, -15, 14, 15, -14, 18, + -18, -24, -19, 16, 17, -22, -21, -16, 20, 21, 22, 25, -23, -20, 24, -31, + -30, -29, -28, -27, -26, -25, -17, 19, 23, 26, 27, 28, 29, 30, 31, + /* SBR table sbr_tenv30b [25] (signed) */ + 0, 1, -1, -2, 2, 3, -3, -4, 4, -5, -12, -11, -10, -9, -8, -7, + -6, 5, 6, 7, 8, 9, 10, 11, 12, + /* SBR table sbr_fenv30b [25] (signed) */ + 0, -1, 1, -2, 2, 3, -3, -4, 4, -5, 5, 6, -12, -11, -10, -9, + -8, -7, -6, 7, 8, 9, 10, 11, 12, + /* SBR table sbr_tnoise30 [63] (signed) */ + 0, 1, -1, -2, 2, -3, 3, -4, 4, -5, 5, 11, -31, -30, -29, -28, + -27, -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, + -11, -10, -9, -8, -7, -6, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + /* SBR table sbr_tnoise30b [25] (signed) */ + 0, -1, 1, -2, 2, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, 3, + 4, 5, 6, 7, 8, 9, 10, 11, 12, +}; + +/* newBWTab[prev invfMode][curr invfMode], format = Q31 (table 4.158) + * sample file which uses all of these: al_sbr_sr_64_2_fsaac32.aac + */ +static const int newBWTab[4][4] PROGMEM = { + {0x00000000, 0x4ccccccd, 0x73333333, 0x7d70a3d7}, + {0x4ccccccd, 0x60000000, 0x73333333, 0x7d70a3d7}, + {0x00000000, 0x60000000, 0x73333333, 0x7d70a3d7}, + {0x00000000, 0x60000000, 0x73333333, 0x7d70a3d7}, +}; + +/* NINT(2.048E6 / Fs) (figure 4.47) + * downsampled (single-rate) SBR not currently supported + */ +const uint8_t goalSBTab[NUM_SAMPLE_RATES_SBR] = { + 21, 23, 32, 43, 46, 64, 85, 93, 128 +}; + +/* twiddle table for radix 4 pass, format = Q31 */ +static const uint32_t twidTabOdd32[8*6] = { + 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x539eba45, 0xe7821d59, + 0x4b418bbe, 0xf383a3e2, 0x58c542c5, 0xdc71898d, 0x5a82799a, 0xd2bec333, 0x539eba45, 0xe7821d59, + 0x539eba45, 0xc4df2862, 0x539eba45, 0xc4df2862, 0x58c542c5, 0xdc71898d, 0x3248d382, 0xc13ad060, + 0x40000000, 0xc0000000, 0x5a82799a, 0xd2bec333, 0x00000000, 0xd2bec333, 0x22a2f4f8, 0xc4df2862, + 0x58c542c5, 0xcac933ae, 0xcdb72c7e, 0xf383a3e2, 0x00000000, 0xd2bec333, 0x539eba45, 0xc4df2862, + 0xac6145bb, 0x187de2a7, 0xdd5d0b08, 0xe7821d59, 0x4b418bbe, 0xc13ad060, 0xa73abd3b, 0x3536cc52, +}; + +/* PostMultiply64() table + * format = Q30 + * reordered for sequential access + * + * for (i = 0; i <= (32/2); i++) { + * angle = i * M_PI / 64; + * x = (cos(angle) + sin(angle)); + * x = sin(angle); + * } + */ +static const int cos1sin1tab64[34] PROGMEM = { + 0x40000000, 0x00000000, 0x43103085, 0x0323ecbe, 0x45f704f7, 0x0645e9af, 0x48b2b335, 0x09640837, + 0x4b418bbe, 0x0c7c5c1e, 0x4da1fab5, 0x0f8cfcbe, 0x4fd288dc, 0x1294062f, 0x51d1dc80, 0x158f9a76, + 0x539eba45, 0x187de2a7, 0x553805f2, 0x1b5d100a, 0x569cc31b, 0x1e2b5d38, 0x57cc15bc, 0x20e70f32, + 0x58c542c5, 0x238e7673, 0x5987b08a, 0x261feffa, 0x5a12e720, 0x2899e64a, 0x5a6690ae, 0x2afad269, + 0x5a82799a, 0x2d413ccd, +}; + +/* coefficient table 4.A.87, format = Q31 + * reordered as: + * cTab[0], cTab[64], cTab[128], cTab[192], cTab[256], + * cTab[2], cTab[66], cTab[130], cTab[194], cTab[258], + * ... + * cTab[64], cTab[128], cTab[192], cTab[256], cTab[320] + * + * NOTE: cTab[1, 2, ... , 318, 319] = cTab[639, 638, ... 322, 321] + * except cTab[384] = -cTab[256], cTab[512] = -cTab[128] + */ +const uint32_t cTabA[165] PROGMEM = { + 0x00000000, 0x0055dba1, 0x01b2e41d, 0x09015651, 0x2e3a7532, 0xffed978a, 0x006090c4, 0x01fd3ba0, 0x08a24899, 0x311af3a4, + 0xfff0065d, 0x006b47fa, 0x024bf7a1, 0x082f552e, 0x33ff670e, 0xffef7b8b, 0x0075fded, 0x029e35b4, 0x07a8127d, 0x36e69691, + 0xffee1650, 0x00807994, 0x02f3e48d, 0x070bbf58, 0x39ce0477, 0xffecc31b, 0x008a7dd7, 0x034d01f0, 0x06593912, 0x3cb41219, + 0xffeb50b2, 0x009424c6, 0x03a966bb, 0x0590a67d, 0x3f962fb8, 0xffe9ca76, 0x009d10bf, 0x04083fec, 0x04b0adcb, 0x4272a385, + 0xffe88ba8, 0x00a520bb, 0x04694101, 0x03b8f8dc, 0x4547daea, 0xffe79e16, 0x00abe79e, 0x04cc2fcf, 0x02a99097, 0x4812f848, + 0xffe6d466, 0x00b1978d, 0x05303f87, 0x01816e06, 0x4ad237a2, 0xffe65416, 0x00b5c867, 0x05950122, 0x0040c496, 0x4d83976c, + 0xffe66dd0, 0x00b8394b, 0x05f9c051, 0xfee723c6, 0x5024d70e, 0xffe69423, 0x00b8c6b0, 0x065dd56a, 0xfd7475d8, 0x52b449de, + 0xffe75361, 0x00b73ab0, 0x06c0f0c0, 0xfbe8f5bd, 0x552f8ff7, 0xffe85b4b, 0x00b36acd, 0x0721bf22, 0xfa44a069, 0x579505f5, + 0xffea353a, 0x00acbd2f, 0x077fedb3, 0xf887507c, 0x59e2f69e, 0xffec8409, 0x00a3508f, 0x07da2b7f, 0xf6b1f3c3, 0x5c16d0ae, + 0xffef2395, 0x0096dcc2, 0x08303897, 0xf4c473c6, 0x5e2f6367, 0xfff294c3, 0x00872c63, 0x0880ffdd, 0xf2bf6ea4, 0x602b0c7f, + 0xfff681d6, 0x007400b8, 0x08cb4e23, 0xf0a3959f, 0x6207f220, 0xfffb42b0, 0x005d36df, 0x090ec1fc, 0xee71b2fe, 0x63c45243, + 0x00007134, 0x00426f36, 0x0949eaac, 0xec2a3f5f, 0x655f63f2, 0x0006b1cf, 0x0023b989, 0x097c1ee8, 0xe9cea84a, 0x66d76725, + 0x000d31b5, 0x0000e790, 0x09a3e163, 0xe75f8bb8, 0x682b39a4, 0x001471f8, 0xffda17f2, 0x09c0e59f, 0xe4de0cb0, 0x6959709d, + 0x001c3549, 0xffaea5d6, 0x09d19ca9, 0xe24b8f66, 0x6a619c5e, 0x0024dd50, 0xff7ee3f1, 0x09d5560b, 0xdfa93ab5, 0x6b42a864, + 0x002d8e42, 0xff4aabc8, 0x09caeb0f, 0xdcf898fb, 0x6bfbdd98, 0x003745f9, 0xff120d70, 0x09b18a1d, 0xda3b176a, 0x6c8c4c7a, + 0x004103f4, 0xfed4bec3, 0x09881dc5, 0xd7722f04, 0x6cf4073e, 0x004b6c46, 0xfe933dc0, 0x094d7ec2, 0xd49fd55f, 0x6d32730f, + 0x0055dba1, 0x01b2e41d, 0x09015651, 0x2e3a7532, 0x6d474e1d, +}; + +/* PreMultiply64() table + * format = Q30 + * reordered for sequential access + * + * for (i = 0; i < 64/4; i++) { + * angle = (i + 0.25) * M_PI / nmdct; + * x = (cos(angle) + sin(angle)); + * x = sin(angle); + * + * angle = (nmdct/2 - 1 - i + 0.25) * M_PI / nmdct; + * x = (cos(angle) + sin(angle)); + * x = sin(angle); + * } + */ +static const int cos4sin4tab64[64] PROGMEM = { + 0x40c7d2bd, 0x00c90e90, 0x424ff28f, 0x3ff4e5e0, 0x43cdd89a, 0x03ecadcf, 0x454149fc, 0x3fc395f9, + 0x46aa0d6d, 0x070de172, 0x4807eb4b, 0x3f6af2e3, 0x495aada2, 0x0a2abb59, 0x4aa22036, 0x3eeb3347, + 0x4bde1089, 0x0d415013, 0x4d0e4de2, 0x3e44a5ef, 0x4e32a956, 0x104fb80e, 0x4f4af5d1, 0x3d77b192, + 0x50570819, 0x135410c3, 0x5156b6d9, 0x3c84d496, 0x5249daa2, 0x164c7ddd, 0x53304df6, 0x3b6ca4c4, + 0x5409ed4b, 0x19372a64, 0x54d69714, 0x3a2fcee8, 0x55962bc0, 0x1c1249d8, 0x56488dc5, 0x38cf1669, + 0x56eda1a0, 0x1edc1953, 0x57854ddd, 0x374b54ce, 0x580f7b19, 0x2192e09b, 0x588c1404, 0x35a5793c, + 0x58fb0568, 0x2434f332, 0x595c3e2a, 0x33de87de, 0x59afaf4c, 0x26c0b162, 0x59f54bee, 0x31f79948, + 0x5a2d0957, 0x29348937, 0x5a56deec, 0x2ff1d9c7, 0x5a72c63b, 0x2b8ef77d, 0x5a80baf6, 0x2dce88aa, +}; + +/* invBandTab[i] = 1.0 / (i + 1), Q31 */ +static const int invBandTab[64] PROGMEM = { + 0x7fffffff, 0x40000000, 0x2aaaaaab, 0x20000000, 0x1999999a, 0x15555555, 0x12492492, 0x10000000, + 0x0e38e38e, 0x0ccccccd, 0x0ba2e8ba, 0x0aaaaaab, 0x09d89d8a, 0x09249249, 0x08888889, 0x08000000, + 0x07878788, 0x071c71c7, 0x06bca1af, 0x06666666, 0x06186186, 0x05d1745d, 0x0590b216, 0x05555555, + 0x051eb852, 0x04ec4ec5, 0x04bda12f, 0x04924925, 0x0469ee58, 0x04444444, 0x04210842, 0x04000000, + 0x03e0f83e, 0x03c3c3c4, 0x03a83a84, 0x038e38e4, 0x03759f23, 0x035e50d8, 0x03483483, 0x03333333, + 0x031f3832, 0x030c30c3, 0x02fa0be8, 0x02e8ba2f, 0x02d82d83, 0x02c8590b, 0x02b93105, 0x02aaaaab, + 0x029cbc15, 0x028f5c29, 0x02828283, 0x02762762, 0x026a439f, 0x025ed098, 0x0253c825, 0x02492492, + 0x023ee090, 0x0234f72c, 0x022b63cc, 0x02222222, 0x02192e2a, 0x02108421, 0x02082082, 0x02000000, +}; + +static const uint32_t poly43lo[5] PROGMEM = { 0x29a0bda9, 0xb02e4828, 0x5957aa1b, 0x236c498d, 0xff581859 }; +static const uint32_t poly43hi[5] PROGMEM = { 0x10852163, 0xd333f6a4, 0x46e9408b, 0x27c2cef0, 0xfef577b4 }; + +/* pow2exp[i] = pow(2, i*4/3) exponent */ +static const uint16_t pow2exp[8] PROGMEM = { 14, 13, 11, 10, 9, 7, 6, 5 }; + +/* pow2exp[i] = pow(2, i*4/3) fraction */ +static const int pow2frac[8] PROGMEM = { + 0x6597fa94, 0x50a28be6, 0x7fffffff, 0x6597fa94, + 0x50a28be6, 0x7fffffff, 0x6597fa94, 0x50a28be6 +}; + +/* pow(2, i/4.0) for i = [0,1,2,3], format = Q30 */ +static const int pow14[4] PROGMEM = { + 0x40000000, 0x4c1bf829, 0x5a82799a, 0x6ba27e65 +}; + +/* pow(2, i/4.0) * pow(j, 4.0/3.0) for i = [0,1,2,3], j = [0,1,2,...,15] + * format = Q28 for j = [0-3], Q25 for j = [4-15] + */ +static const uint32_t pow43_14[4][16] PROGMEM = { + { + 0x00000000, 0x10000000, 0x285145f3, 0x453a5cdb, /* Q28 */ + 0x0cb2ff53, 0x111989d6, 0x15ce31c8, 0x1ac7f203, /* Q25 */ + 0x20000000, 0x257106b9, 0x2b16b4a3, 0x30ed74b4, /* Q25 */ + 0x36f23fa5, 0x3d227bd3, 0x437be656, 0x49fc823c, /* Q25 */ + }, + { + 0x00000000, 0x1306fe0a, 0x2ff221af, 0x52538f52, + 0x0f1a1bf4, 0x1455ccc2, 0x19ee62a8, 0x1fd92396, + 0x260dfc14, 0x2c8694d8, 0x333dcb29, 0x3a2f5c7a, + 0x4157aed5, 0x48b3aaa3, 0x50409f76, 0x57fc3010, + }, + { + 0x00000000, 0x16a09e66, 0x39047c0f, 0x61e734aa, + 0x11f59ac4, 0x182ec633, 0x1ed66a45, 0x25dfc55a, + 0x2d413ccd, 0x34f3462d, 0x3cefc603, 0x4531ab69, + 0x4db4adf8, 0x56752054, 0x5f6fcfcd, 0x68a1eca1, + }, + { + 0x00000000, 0x1ae89f99, 0x43ce3e4b, 0x746d57b2, + 0x155b8109, 0x1cc21cdc, 0x24ac1839, 0x2d0a479e, + 0x35d13f33, 0x3ef80748, 0x48775c93, 0x524938cd, + 0x5c68841d, 0x66d0df0a, 0x717e7bfe, 0x7c6e0305, + }, +}; + +/* pow(j, 4.0 / 3.0) for j = [16,17,18,...,63], format = Q23 */ +static const int pow43[48] PROGMEM = { + 0x1428a2fa, 0x15db1bd6, 0x1796302c, 0x19598d85, + 0x1b24e8bb, 0x1cf7fcfa, 0x1ed28af2, 0x20b4582a, + 0x229d2e6e, 0x248cdb55, 0x26832fda, 0x28800000, + 0x2a832287, 0x2c8c70a8, 0x2e9bc5d8, 0x30b0ff99, + 0x32cbfd4a, 0x34eca001, 0x3712ca62, 0x393e6088, + 0x3b6f47e0, 0x3da56717, 0x3fe0a5fc, 0x4220ed72, + 0x44662758, 0x46b03e7c, 0x48ff1e87, 0x4b52b3f3, + 0x4daaebfd, 0x5007b497, 0x5268fc62, 0x54ceb29c, + 0x5738c721, 0x59a72a59, 0x5c19cd35, 0x5e90a129, + 0x610b9821, 0x638aa47f, 0x660db90f, 0x6894c90b, + 0x6b1fc80c, 0x6daeaa0d, 0x70416360, 0x72d7e8b0, + 0x75722ef9, 0x78102b85, 0x7ab1d3ec, 0x7d571e09, +}; + +/* invTab[x] = 1/(x+1), format = Q30 */ +static const int invTab[5] PROGMEM = {0x40000000, 0x20000000, 0x15555555, 0x10000000, 0x0ccccccd}; + +/* inverse quantization tables for TNS filter coefficients, format = Q31 + * see bottom of file for table generation + * negative (vs. spec) since we use MADD for filter kernel + */ +static const uint32_t invQuant3[16] PROGMEM = { + 0x00000000, 0xc8767f65, 0x9becf22c, 0x83358feb, 0x83358feb, 0x9becf22c, 0xc8767f65, 0x00000000, + 0x2bc750e9, 0x5246dd49, 0x6ed9eba1, 0x7e0e2e32, 0x7e0e2e32, 0x6ed9eba1, 0x5246dd49, 0x2bc750e9, +}; + +static const uint32_t invQuant4[16] PROGMEM = { + 0x00000000, 0xe5632654, 0xcbf00dbe, 0xb4c373ee, 0xa0e0a15f, 0x9126145f, 0x8643c7b3, 0x80b381ac, + 0x7f7437ad, 0x7b1d1a49, 0x7294b5f2, 0x66256db2, 0x563ba8aa, 0x4362210e, 0x2e3d2abb, 0x17851aad, +}; + +static const int8_t sgnMask[3] = {0x02, 0x04, 0x08}; +static const int8_t negMask[3] = {~0x03, ~0x07, ~0x0f}; + +/*********************************************************************************************************************** + * Function: AACDecoder_AllocateBuffers + * + * Description: allocate all the memory needed for the AAC decoder + * try heap first, because it's faster + * + * Inputs: none + * + * Outputs: none + * + * Return: false if not enough memory, otherwise true + * + **********************************************************************************************************************/ +bool AACDecoder_AllocateBuffers(void){ + + if(!m_AACDecInfo) {m_AACDecInfo = (AACDecInfo_t*) malloc(sizeof(AACDecInfo_t));} + if(!m_PSInfoBase) {m_PSInfoBase = (PSInfoBase_t*) malloc(sizeof(PSInfoBase_t));} + if(!m_pce[0]) {m_pce[0] = (ProgConfigElement_t*) malloc(sizeof(ProgConfigElement_t)*16);} + + if(!m_AACDecInfo || !m_PSInfoBase || !m_pce[0]) { + log_i("heap is too small, try PSRAM"); + AACDecoder_FreeBuffers(); + } + else{ + goto nextStep; + } + + if(psramFound()) { + // PSRAM found, Buffer will be allocated in PSRAM + if(!m_AACDecInfo) {m_AACDecInfo = (AACDecInfo_t*) ps_calloc(sizeof(AACDecInfo_t), sizeof(uint8_t));} + if(!m_PSInfoBase) {m_PSInfoBase = (PSInfoBase_t*) ps_calloc(sizeof(PSInfoBase_t), sizeof(uint8_t));} + if(!m_pce[0]) {m_pce[0] = (ProgConfigElement_t*) ps_calloc(sizeof(ProgConfigElement_t)*16, sizeof(uint8_t));} + } + + if(!m_AACDecInfo || !m_PSInfoBase || !m_pce[0]) { + log_e("not enough memory to allocate aacdecoder buffers"); + AACDecoder_FreeBuffers(); + return false; + } + log_i("AAC buffers allocated in PSRAM"); + + nextStep: + + +#ifdef AAC_ENABLE_SBR + // can't allocated in PSRAM, because PSRAM ist too slow + if(!m_PSInfoSBR) {m_PSInfoSBR = (PSInfoSBR_t*)malloc(sizeof(PSInfoSBR_t));} + + if(!m_PSInfoSBR) { + log_e("OOM in SBR, can't allocate %d bytes\n", sizeof(PSInfoSBR_t)); + return false; // ERR_AAC_SBR_INIT; + } +#endif + + + // Clear Buffer + memset( m_AACDecInfo, 0, sizeof(AACDecInfo_t)); //Clear AACDecInfo + memset( m_PSInfoBase, 0, sizeof(PSInfoBase_t)); //Clear PSInfoBase + memset(&m_AACFrameInfo, 0, sizeof(AACFrameInfo_t)); //Clear AACFrameInfo + memset(&m_fhADTS, 0, sizeof(ADTSHeader_t)); //Clear fhADTS + memset(&m_fhADIF, 0, sizeof(ADIFHeader_t)); //Clear fhADIS + memset( m_pce[0], 0, sizeof(ProgConfigElement_t) * 16); //Clear ProgConfigElement + memset(&m_pulseInfo[0], 0, sizeof(PulseInfo_t) *2); //Clear PulseInfo + memset(&m_aac_BitStreamInfo, 0, sizeof(aac_BitStreamInfo_t)); //Clear aac_BitStreamInfo +#ifdef AAC_ENABLE_SBR + memset( m_PSInfoSBR, 0, sizeof(PSInfoSBR_t)); //Clear PSInfoSBR + InitSBRState(); +#endif + + m_AACDecInfo->prevBlockID = AAC_ID_INVALID; + m_AACDecInfo->currBlockID = AAC_ID_INVALID; + m_AACDecInfo->currInstTag = -1; + for(int ch = 0; ch < MAX_NCHANS_ELEM; ch++) + m_AACDecInfo->sbDeinterleaveReqd[ch] = 0; + m_AACDecInfo->adtsBlocksLeft = 0; + m_AACDecInfo->tnsUsed = 0; + m_AACDecInfo->pnsUsed = 0; + + return true; +} + +/************************************************************************************** + * Function: AACFlushCodec + * + * Description: flush internal codec state (after seeking, for example) + * + * Inputs: valid AAC decoder instance pointer (HAACDecoder) + * + * Outputs: updated state variables in aacDecInfo + * + * Return: 0 if successful, error code (< 0) if error + **************************************************************************************/ +int AACFlushCodec() +{ + int ch; + + if (!m_AACDecInfo) + return ERR_AAC_NULL_POINTER; + + /* reset common state variables which change per-frame + * don't touch state variables which are (usually) constant for entire clip + * (nChans, sampRate, profile, format, sbrEnabled) + */ + m_AACDecInfo->prevBlockID = AAC_ID_INVALID; + m_AACDecInfo->currBlockID = AAC_ID_INVALID; + m_AACDecInfo->currInstTag = -1; + for (ch = 0; ch < MAX_NCHANS_ELEM; ch++) + m_AACDecInfo->sbDeinterleaveReqd[ch] = 0; + m_AACDecInfo->adtsBlocksLeft = 0; + m_AACDecInfo->tnsUsed = 0; + m_AACDecInfo->pnsUsed = 0; + + /* reset internal codec state (flush overlap buffers, etc.) */ + memset(m_PSInfoBase->overlap, 0, AAC_MAX_NCHANS * AAC_MAX_NSAMPS * sizeof(int)); + memset(m_PSInfoBase->prevWinShape, 0, AAC_MAX_NCHANS * sizeof(int)); + + return ERR_AAC_NONE; +} +/*********************************************************************************************************************** + * Function: AACDecoder_FreeBuffers + * + * Description: allocate all the memory needed for the AAC decoder + * + * Inputs: none + * + * Outputs: none + * + * Return: none + + **********************************************************************************************************************/ +void AACDecoder_FreeBuffers(void) { + +// uint32_t i = ESP.getFreeHeap(); + + if(m_AACDecInfo) {free(m_AACDecInfo); m_AACDecInfo=NULL;} + if(m_PSInfoBase) {free(m_PSInfoBase); m_PSInfoBase=NULL;} + if(m_pce[0]) {for(int i=0; i<16; i++) free(m_pce[i]); m_pce[0]=NULL;} + +#ifdef AAC_ENABLE_SBR + if(m_PSInfoSBR) {free(m_PSInfoSBR); m_PSInfoSBR=NULL;} //Clear AACDecInfo +#endif + +// log_i("AACDecoder: %lu bytes memory was freed", ESP.getFreeHeap() - i); +} + +/*********************************************************************************************************************** + * Function: AACDecoder_IsInit + * + * Description: returns AAC decoder initialization status + * + * Inputs: none + * + * Outputs: none + * + * Return: true if buffers allocated, otherwise false + + **********************************************************************************************************************/ +bool AACDecoder_IsInit(void) { + if(m_AACDecInfo && m_PSInfoBase && m_pce[0]){ + return true; + } + return false; +} + +/*********************************************************************************************************************** + * Function: AACDecoder_FreeBuffers + * + * Description: allocate all the memory needed for the AAC decoder + * + * Inputs: none + * + * Outputs: none + * + * Return: none + + **********************************************************************************************************************/ + +/*********************************************************************************************************************** + * Function: AACFindSyncWord + * + * Description: locate the next byte-alinged sync word in the raw AAC stream + * + * Inputs: buffer to search for sync word + * max number of bytes to search in buffer + * + * Outputs: none + * + * Return: offset to first sync word (bytes from start of buf) + * -1 if sync not found after searching nBytes + **********************************************************************************************************************/ +int AACFindSyncWord(uint8_t *buf, int nBytes) +{ + int i; + + /* find byte-aligned syncword (12 bits = 0xFFF) */ + for (i = 0; i < nBytes - 1; i++) { + if ( (buf[i+0] & SYNCWORDH) == SYNCWORDH && (buf[i+1] & SYNCWORDL) == SYNCWORDL ) + return i; + } + + return -1; +} +//************************************************************************************** +int AACGetSampRate(){return m_AACDecInfo->sampRate * (m_AACDecInfo->sbrEnabled ? 2 : 1);} +int AACGetChannels(){return m_AACDecInfo->nChans;} +int AACGetBitsPerSample(){return 16;} +int AACGetID() {return m_AACDecInfo->id;} // 0-MPEG4, 1-MPEG2 +uint8_t AACGetProfile() {return (uint8_t)m_AACDecInfo->profile;} // 0-Main, 1-LC, 2-SSR, 3-reserved +uint8_t AACGetFormat() {return (uint8_t)m_AACDecInfo->format;} // 0-unknown 1-ADTS 2-ADIF, 3-RAW +int AACGetOutputSamps(){return m_AACDecInfo->nChans * AAC_MAX_NSAMPS * (m_AACDecInfo->sbrEnabled ? 2 : 1);} +int AACGetBitrate() { + uint32_t br = AACGetBitsPerSample() * AACGetChannels() * AACGetSampRate(); + return (br / m_AACDecInfo->compressionRatio); +} +/************************************************************************************** + * Function: AACSetRawBlockParams + * + * Description: set internal state variables for decoding a stream of raw data blocks + * + * Inputs: flag indicating source of parameters + * nChans, sampRate, + * and profile 0 = main, 1 = LC, 2 = SSR, 3 = reserved + * optionally filled-in + * + * Outputs: updated codec state + * + * Return: 0 if successful, error code (< 0) if error + * + * Notes: if copyLast == 1, then the codec sets up its internal state (for + * decoding raw blocks) based on previously-decoded ADTS header info + * if copyLast == 0, then the codec uses the values passed in + * aacFrameInfo to configure its internal state (useful when the + * source is MP4 format, for example) + **************************************************************************************/ +int AACSetRawBlockParams(int copyLast, int nChans, int sampRateCore, int profile) +{ + if (!m_AACDecInfo) + return ERR_AAC_NULL_POINTER; + + m_AACDecInfo->format = AAC_FF_RAW; + if (copyLast) + return SetRawBlockParams(1, 0, 0, 0); + else + return SetRawBlockParams(0, nChans, sampRateCore, profile); +} + +/*********************************************************************************************************************** + * Function: AACDecode + * + * Description: decode AAC frame + * + * Inputs: double pointer to buffer of AAC data + * pointer to number of valid bytes remaining in inbuf + * pointer to outbuf, big enough to hold one frame of decoded PCM samples + * + * Outputs: PCM data in outbuf, interleaved LRLRLR... if stereo + * number of output samples = 1024 per channel + * updated inbuf pointer + * updated bytesLeft + * + * Return: 0 if successful, error code (< 0) if error + * + * Notes: inbuf pointer and bytesLeft are not updated until whole frame is + * successfully decoded, so if ERR_AAC_INDATA_UNDERFLOW is returned + * just call AACDecode again with more data in inbuf + **********************************************************************************************************************/ +int AACDecode(uint8_t *inbuf, int *bytesLeft, short *outbuf) +{ + int err, offset, bitOffset, bitsAvail; + int ch, baseChan, elementChans; + uint8_t *inptr; + +#ifdef AAC_ENABLE_SBR + int baseChanSBR, elementChansSBR; +#endif + + /* make local copies (see "Notes" above) */ + inptr = inbuf; + bitOffset = 0; + bitsAvail = (*bytesLeft) << 3; + + /* first time through figure out what the file format is */ + if (m_AACDecInfo->format == AAC_FF_Unknown) { + if (bitsAvail < 32) + return ERR_AAC_INDATA_UNDERFLOW; + + if ((inptr)[0] == 'A' && (inptr)[1] == 'D' && (inptr)[2] == 'I' && (inptr)[3] == 'F') { + /* unpack ADIF header */ + m_AACDecInfo->format = AAC_FF_ADIF; + err = UnpackADIFHeader(&inptr, &bitOffset, &bitsAvail); + if (err) + return err; + } else { + /* assume ADTS by default */ + m_AACDecInfo->format = AAC_FF_ADTS; + } + } + /* if ADTS, search for start of next frame */ + if (m_AACDecInfo->format == AAC_FF_ADTS) { + /* can have 1-4 raw data blocks per ADTS frame (header only present for first one) */ + if (m_AACDecInfo->adtsBlocksLeft == 0) { + offset = AACFindSyncWord(inptr, bitsAvail >> 3); + if (offset < 0) + return ERR_AAC_INDATA_UNDERFLOW; + inptr += offset; + bitsAvail -= (offset << 3); + + err = UnpackADTSHeader(&inptr, &bitOffset, &bitsAvail); + if (err) + return err; + + if (m_AACDecInfo->nChans == -1) { + /* figure out implicit channel mapping if necessary */ + err = GetADTSChannelMapping(inptr, bitOffset, bitsAvail); + if (err) + return err; + } + } + m_AACDecInfo->adtsBlocksLeft--; + } else if (m_AACDecInfo->format == AAC_FF_RAW) { + err = PrepareRawBlock(); + if (err) + return err; + } + + /* check for valid number of channels */ + if (m_AACDecInfo->nChans > AAC_MAX_NCHANS || m_AACDecInfo->nChans <= 0) + return ERR_AAC_NCHANS_TOO_HIGH; + + /* will be set later if active in this frame */ + m_AACDecInfo->tnsUsed = 0; + m_AACDecInfo->pnsUsed = 0; + + bitOffset = 0; + baseChan = 0; + +#ifdef AAC_ENABLE_SBR + baseChanSBR = 0; +#endif + + do { + /* parse next syntactic element */ + err = DecodeNextElement(&inptr, &bitOffset, &bitsAvail); + if (err) + return err; + + elementChans = elementNumChans[m_AACDecInfo->currBlockID]; + if (baseChan + elementChans > AAC_MAX_NCHANS) + return ERR_AAC_NCHANS_TOO_HIGH; + + /* noiseless decoder and dequantizer */ + for (ch = 0; ch < elementChans; ch++) { + err = DecodeNoiselessData(&inptr, &bitOffset, &bitsAvail, ch); + + if (err) + return err; + + if (AACDequantize(ch)) + return ERR_AAC_DEQUANT; + } + + /* mid-side and intensity stereo */ + if (m_AACDecInfo->currBlockID == AAC_ID_CPE) { + if (StereoProcess()) + return ERR_AAC_STEREO_PROCESS; + } + + /* PNS, TNS, inverse transform */ + for (ch = 0; ch < elementChans; ch++) { + + if (PNS(ch)) + return ERR_AAC_PNS; + + if (m_AACDecInfo->sbDeinterleaveReqd[ch]) { + /* deinterleave short blocks, if required */ + if (DeinterleaveShortBlocks(ch)) + return ERR_AAC_SHORT_BLOCK_DEINT; + m_AACDecInfo->sbDeinterleaveReqd[ch] = 0; + } + + if (TNSFilter(ch)) + return ERR_AAC_TNS; + + if (IMDCT(ch, baseChan + ch, outbuf)) + return ERR_AAC_IMDCT; + } + +#ifdef AAC_ENABLE_SBR + if (m_AACDecInfo->sbrEnabled && (m_AACDecInfo->currBlockID == AAC_ID_FIL || + m_AACDecInfo->currBlockID == AAC_ID_LFE)) { + if (m_AACDecInfo->currBlockID == AAC_ID_LFE) + elementChansSBR = elementNumChans[AAC_ID_LFE]; + else if (m_AACDecInfo->currBlockID == AAC_ID_FIL && (m_AACDecInfo->prevBlockID == AAC_ID_SCE || + m_AACDecInfo->prevBlockID == AAC_ID_CPE)) + elementChansSBR = elementNumChans[m_AACDecInfo->prevBlockID]; + else + elementChansSBR = 0; + + if (baseChanSBR + elementChansSBR > AAC_MAX_NCHANS) + return ERR_AAC_SBR_NCHANS_TOO_HIGH; + + /* parse SBR extension data if present (contained in a fill element) */ + if (DecodeSBRBitstream(baseChanSBR)) + return ERR_AAC_SBR_BITSTREAM; + + /* apply SBR */ + if (DecodeSBRData(baseChanSBR, outbuf)) + return ERR_AAC_SBR_DATA; + + baseChanSBR += elementChansSBR; + } +#endif + + baseChan += elementChans; + } while (m_AACDecInfo->currBlockID != AAC_ID_END); + + /* byte align after each raw_data_block */ + if (bitOffset) { + inptr++; + bitsAvail -= (8-bitOffset); + bitOffset = 0; + if (bitsAvail < 0) + return ERR_AAC_INDATA_UNDERFLOW; + } + + m_AACDecInfo->compressionRatio = (float)(AACGetOutputSamps()) * 2 / (inptr - inbuf); + + /* update pointers */ + m_AACDecInfo->frameCount++; + *bytesLeft -= (inptr - inbuf); + inbuf = inptr; + + return ERR_AAC_NONE; +} +/*********************************************************************************************************************** + * Function: DecodeLPCCoefs + * + * Description: decode LPC coefficients for TNS + * + * Inputs: order of TNS filter + * resolution of coefficients (3 or 4 bits) + * coefficients unpacked from bitstream + * scratch buffer (b) of size >= order + * + * Outputs: LPC coefficients in Q(FBITS_LPC_COEFS), in 'a' + * + * Return: none + * + * Notes: assumes no guard bits in input transform coefficients + * a[i] = Q(FBITS_LPC_COEFS), don't store a0 = 1.0 + * (so a[0] = first delay tap, etc.) + * max abs(a[i]) < log2(order), so for max order = 20 a[i] < 4.4 + * (up to 3 bits of gain) so a[i] has at least 31 - FBITS_LPC_COEFS - 3 + * guard bits + * to ensure no intermediate overflow in all-pole filter, set + * FBITS_LPC_COEFS such that number of guard bits >= log2(max order) + **********************************************************************************************************************/ +void DecodeLPCCoefs(int order, int res, int8_t *filtCoef, int *a, int *b) +{ + int i, m, t; + const uint32_t *invQuantTab; + + if (res == 3) invQuantTab = invQuant3; + else if (res == 4) invQuantTab = invQuant4; + else return; + + for (m = 0; m < order; m++) { + t = invQuantTab[filtCoef[m] & 0x0f]; /* t = Q31 */ + for (i = 0; i < m; i++) + b[i] = a[i] - (MULSHIFT32(t, a[m-i-1]) << 1); + for (i = 0; i < m; i++) + a[i] = b[i]; + a[m] = t >> (31 - FBITS_LPC_COEFS); + } +} + +/*********************************************************************************************************************** + * Function: FilterRegion + * + * Description: apply LPC filter to one region of coefficients + * + * Inputs: number of transform coefficients in this region + * direction flag (forward = 1, backward = -1) + * order of filter + * 'size' transform coefficients + * 'order' LPC coefficients in Q(FBITS_LPC_COEFS) + * scratch buffer for history (must be >= order samples long) + * + * Outputs: filtered transform coefficients + * + * Return: guard bit mask (OR of abs value of all filtered transform coefs) + * + * Notes: assumes no guard bits in input transform coefficients + * gains 0 int bits + * history buffer does not need to be preserved between regions + **********************************************************************************************************************/ +int FilterRegion(int size, int dir, int order, int *audioCoef, int *a, int *hist) +{ + int i, j, y, hi32, inc, gbMask; + U64 sum64; + + /* init history to 0 every time */ + for (i = 0; i < order; i++) + hist[i] = 0; + + sum64.w64 = 0; /* avoid warning */ + gbMask = 0; + inc = (dir ? -1 : 1); + do { + /* sum64 = a0*y[n] = 1.0*y[n] */ + y = *audioCoef; + sum64.r.hi32 = y >> (32 - FBITS_LPC_COEFS); + sum64.r.lo32 = y << FBITS_LPC_COEFS; + + /* sum64 += (a1*y[n-1] + a2*y[n-2] + ... + a[order-1]*y[n-(order-1)]) */ + for (j = order - 1; j > 0; j--) { + sum64.w64 = MADD64(sum64.w64, hist[j], a[j]); + hist[j] = hist[j-1]; + } + sum64.w64 = MADD64(sum64.w64, hist[0], a[0]); + y = (sum64.r.hi32 << (32 - FBITS_LPC_COEFS)) | (sum64.r.lo32 >> FBITS_LPC_COEFS); + + /* clip output (rare) */ + hi32 = sum64.r.hi32; + if ((hi32 >> 31) != (hi32 >> (FBITS_LPC_COEFS-1))) + y = (hi32 >> 31) ^ 0x7fffffff; + + hist[0] = y; + *audioCoef = y; + audioCoef += inc; + gbMask |= FASTABS(y); + } while (--size); + + return gbMask; +} + +/*********************************************************************************************************************** + * Function: TNSFilter + * + * Description: apply temporal noise shaping, if enabled + * + * Inputs: index of current channel + * + * Outputs: updated transform coefficients + * updated minimum guard bit count for this channel + * + * Return: 0 if successful, -1 if error + **********************************************************************************************************************/ +int TNSFilter(int ch) +{ + int win, winLen, nWindows, nSFB, filt, bottom, top, order, maxOrder, dir; + int start, end, size, tnsMaxBand, numFilt, gbMask; + int *audioCoef; + uint8_t *filtLength, *filtOrder, *filtRes, *filtDir; + int8_t *filtCoef; + const uint16_t *tnsMaxBandTab; + const uint16_t *sfbTab; + ICSInfo_t *icsInfo; + TNSInfo_t *ti; + + icsInfo = (ch == 1 && m_PSInfoBase->commonWin == 1) ? &(m_PSInfoBase->icsInfo[0]) : &(m_PSInfoBase->icsInfo[ch]); + ti = &m_PSInfoBase->tnsInfo[ch]; + + if (!ti->tnsDataPresent) + return 0; + + if (icsInfo->winSequence == 2) { + nWindows = NWINDOWS_SHORT; + winLen = NSAMPS_SHORT; + nSFB = sfBandTotalShort[m_PSInfoBase->sampRateIdx]; + maxOrder = tnsMaxOrderShort[m_AACDecInfo->profile]; + sfbTab = sfBandTabShort + sfBandTabShortOffset[m_PSInfoBase->sampRateIdx]; + tnsMaxBandTab = tnsMaxBandsShort + tnsMaxBandsShortOffset[m_AACDecInfo->profile]; + tnsMaxBand = tnsMaxBandTab[m_PSInfoBase->sampRateIdx]; + } else { + nWindows = NWINDOWS_LONG; + winLen = NSAMPS_LONG; + nSFB = sfBandTotalLong[m_PSInfoBase->sampRateIdx]; + maxOrder = tnsMaxOrderLong[m_AACDecInfo->profile]; + sfbTab = sfBandTabLong + sfBandTabLongOffset[m_PSInfoBase->sampRateIdx]; + tnsMaxBandTab = tnsMaxBandsLong + tnsMaxBandsLongOffset[m_AACDecInfo->profile]; + tnsMaxBand = tnsMaxBandTab[m_PSInfoBase->sampRateIdx]; + } + + if (tnsMaxBand > icsInfo->maxSFB) + tnsMaxBand = icsInfo->maxSFB; + + filtRes = ti->coefRes; + filtLength = ti->length; + filtOrder = ti->order; + filtDir = ti->dir; + filtCoef = ti->coef; + + gbMask = 0; + audioCoef = m_PSInfoBase->coef[ch]; + for (win = 0; win < nWindows; win++) { + bottom = nSFB; + numFilt = ti->numFilt[win]; + for (filt = 0; filt < numFilt; filt++) { + top = bottom; + bottom = top - *filtLength++; + bottom = MAX(bottom, 0); + order = *filtOrder++; + order = MIN(order, maxOrder); + + if (order) { + start = sfbTab[MIN(bottom, tnsMaxBand)]; + end = sfbTab[MIN(top, tnsMaxBand)]; + size = end - start; + if (size > 0) { + dir = *filtDir++; + if (dir) + start = end - 1; + + DecodeLPCCoefs(order, filtRes[win], filtCoef, m_PSInfoBase->tnsLPCBuf, m_PSInfoBase->tnsWorkBuf); + gbMask |= FilterRegion(size, dir, order, audioCoef + start, m_PSInfoBase->tnsLPCBuf, + m_PSInfoBase->tnsWorkBuf); + } + filtCoef += order; + } + } + audioCoef += winLen; + } + + /* update guard bit count if necessary */ + size = CLZ(gbMask) - 1; + if (m_PSInfoBase->gbCurrent[ch] > size) + m_PSInfoBase->gbCurrent[ch] = size; + + return 0; +} + +/*********************************************************************************************************************** + * Function: DecodeSingleChannelElement + * + * Description: decode one SCE + * + * Inputs: none + * + * Outputs: updated element instance tag + * + * Return: 0 if successful, -1 if error + * + * Notes: doesn't decode individual channel stream (part of DecodeNoiselessData) + **********************************************************************************************************************/ +int DecodeSingleChannelElement() +{ + /* read instance tag */ + m_AACDecInfo->currInstTag = GetBits(NUM_INST_TAG_BITS); + + return 0; +} + +/*********************************************************************************************************************** + * Function: DecodeChannelPairElement + * + * Description: decode one CPE + * + * Inputs: none + * + * Outputs: updated element instance tag + * updated commonWin + * updated ICS info, if commonWin == 1 + * updated mid-side stereo info, if commonWin == 1 + * + * Return: 0 if successful, -1 if error + * + * Notes: doesn't decode individual channel stream (part of DecodeNoiselessData) + **********************************************************************************************************************/ +int DecodeChannelPairElement() +{ + int sfb, gp, maskOffset; + uint8_t currBit, *maskPtr; + ICSInfo_t *icsInfo; + + + icsInfo = m_PSInfoBase->icsInfo; + + /* read instance tag */ + m_AACDecInfo->currInstTag = GetBits(NUM_INST_TAG_BITS); + + /* read common window flag and mid-side info (if present) + * store msMask bits in m_PSInfoBase->msMaskBits[] as follows: + * long blocks - pack bits for each SFB in range [0, maxSFB) starting with lsb of msMaskBits[0] + * short blocks - pack bits for each SFB in range [0, maxSFB), for each group [0, 7] + * msMaskPresent = 0 means no M/S coding + * = 1 means m_PSInfoBase->msMaskBits contains 1 bit per SFB to toggle M/S coding + * = 2 means all SFB's are M/S coded (so m_PSInfoBase->msMaskBits is not needed) + */ + m_PSInfoBase->commonWin = GetBits(1); + if (m_PSInfoBase->commonWin) { + DecodeICSInfo(icsInfo, m_PSInfoBase->sampRateIdx); + m_PSInfoBase->msMaskPresent = GetBits(2); + if (m_PSInfoBase->msMaskPresent == 1) { + maskPtr = m_PSInfoBase->msMaskBits; + *maskPtr = 0; + maskOffset = 0; + for (gp = 0; gp < icsInfo->numWinGroup; gp++) { + for (sfb = 0; sfb < icsInfo->maxSFB; sfb++) { + currBit = (uint8_t)GetBits(1); + *maskPtr |= currBit << maskOffset; + if (++maskOffset == 8) { + maskPtr++; + *maskPtr = 0; + maskOffset = 0; + } + } + } + } + } + + return 0; +} + +/*********************************************************************************************************************** + * Function: DecodeLFEChannelElement + * + * Description: decode one LFE + * + * Inputs: none + * + * Outputs: updated element instance tag + * + * Return: 0 if successful, -1 if error + * + * Notes: doesn't decode individual channel stream (part of DecodeNoiselessData) + **********************************************************************************************************************/ +int DecodeLFEChannelElement() +{ + /* read instance tag */ + m_AACDecInfo->currInstTag = GetBits( NUM_INST_TAG_BITS); + + return 0; +} + +/*********************************************************************************************************************** + * Function: DecodeDataStreamElement + * + * Description: decode one DSE + * + * Inputs: none + * + * Outputs: updated element instance tag + * filled in data stream buffer + * + * Return: 0 if successful, -1 if error + **********************************************************************************************************************/ +int DecodeDataStreamElement() +{ + uint32_t byteAlign, dataCount; + uint8_t *dataBuf; + + m_AACDecInfo->currInstTag = GetBits( NUM_INST_TAG_BITS); + byteAlign = GetBits(1); + dataCount = GetBits(8); + if (dataCount == 255) + dataCount += GetBits(8); + + if (byteAlign) + ByteAlignBitstream(); + + m_PSInfoBase->dataCount = dataCount; + dataBuf = m_PSInfoBase->dataBuf; + while (dataCount--) + *dataBuf++ = GetBits(8); + + return 0; +} + +/*********************************************************************************************************************** + * Function: DecodeProgramConfigElement + * + * Description: decode one PCE + * + * Inputs: none + * + * Outputs: filled-in ProgConfigElement_t struct + * updated aac_BitStreamInfo_t struct + * + * Return: 0 if successful, error code (< 0) if error + * + * Notes: #define KEEP_PCE_COMMENTS to save the comment field of the PCE + * (otherwise we just skip it in the bitstream, to save memory) + **********************************************************************************************************************/ +int DecodeProgramConfigElement(uint8_t idx) +{ + int i; + + m_pce[idx]->elemInstTag = GetBits(4); + m_pce[idx]->profile = GetBits(2); + m_pce[idx]->sampRateIdx = GetBits(4); + m_pce[idx]->numFCE = GetBits(4); + m_pce[idx]->numSCE = GetBits(4); + m_pce[idx]->numBCE = GetBits(4); + m_pce[idx]->numLCE = GetBits(2); + m_pce[idx]->numADE = GetBits(3); + m_pce[idx]->numCCE = GetBits(4); + + m_pce[idx]->monoMixdown = GetBits(1) << 4; /* present flag */ + if (m_pce[idx]->monoMixdown) + m_pce[idx]->monoMixdown |= GetBits(4); /* element number */ + + m_pce[idx]->stereoMixdown = GetBits(1) << 4; /* present flag */ + if (m_pce[idx]->stereoMixdown) + m_pce[idx]->stereoMixdown |= GetBits(4); /* element number */ + + m_pce[idx]->matrixMixdown = GetBits(1) << 4; /* present flag */ + if (m_pce[idx]->matrixMixdown) { + m_pce[idx]->matrixMixdown |= GetBits(2) << 1; /* index */ + m_pce[idx]->matrixMixdown |= GetBits(1); /* pseudo-surround enable */ + } + + for (i = 0; i < m_pce[idx]->numFCE; i++) { + m_pce[idx]->fce[i] = GetBits(1) << 4; /* is_cpe flag */ + m_pce[idx]->fce[i] |= GetBits(4); /* tag select */ + } + + for (i = 0; i < m_pce[idx]->numSCE; i++) { + m_pce[idx]->sce[i] = GetBits(1) << 4; /* is_cpe flag */ + m_pce[idx]->sce[i] |= GetBits(4); /* tag select */ + } + + for (i = 0; i < m_pce[idx]->numBCE; i++) { + m_pce[idx]->bce[i] = GetBits(1) << 4; /* is_cpe flag */ + m_pce[idx]->bce[i] |= GetBits(4); /* tag select */ + } + + for (i = 0; i < m_pce[idx]->numLCE; i++) + m_pce[idx]->lce[i] = GetBits(4); /* tag select */ + + for (i = 0; i < m_pce[idx]->numADE; i++) + m_pce[idx]->ade[i] = GetBits(4); /* tag select */ + + for (i = 0; i < m_pce[idx]->numCCE; i++) { + m_pce[idx]->cce[i] = GetBits(1) << 4; /* independent/dependent flag */ + m_pce[idx]->cce[i] |= GetBits(4); /* tag select */ + } + + ByteAlignBitstream(); + /* eat comment bytes and throw away */ + i = GetBits(8); + while (i--) + GetBits(8); + + return 0; +} + +/*********************************************************************************************************************** + * Function: DecodeFillElement + * + * Description: decode one fill element + * + * Inputs: none + * (14496-3, table 4.4.11) + * + * Outputs: updated element instance tag + * unpacked extension payload + * + * Return: 0 if successful, -1 if error + **********************************************************************************************************************/ +int DecodeFillElement() +{ + unsigned int fillCount; + uint8_t *fillBuf; + + fillCount = GetBits(4); + if (fillCount == 15) + fillCount += (GetBits(8) - 1); + + m_PSInfoBase->fillCount = fillCount; + fillBuf = m_PSInfoBase->fillBuf; + while (fillCount--) + *fillBuf++ = GetBits(8); + + m_AACDecInfo->currInstTag = -1; /* fill elements don't have instance tag */ + m_AACDecInfo->fillExtType = 0; + +#ifdef AAC_ENABLE_SBR + /* check for SBR + * aacDecInfo->sbrEnabled is sticky (reset each raw_data_block), so for multichannel + * need to verify that all SCE/CPE/ICCE have valid SBR fill element following, and + * must upsample by 2 for LFE + */ + if (m_PSInfoBase->fillCount > 0) { + m_AACDecInfo->fillExtType = (int)((m_PSInfoBase->fillBuf[0] >> 4) & 0x0f); + if (m_AACDecInfo->fillExtType == EXT_SBR_DATA || m_AACDecInfo->fillExtType == EXT_SBR_DATA_CRC) + m_AACDecInfo->sbrEnabled = 1; + } +#endif + + + m_AACDecInfo->fillBuf = m_PSInfoBase->fillBuf; + m_AACDecInfo->fillCount = m_PSInfoBase->fillCount; + + return 0; +} + +/*********************************************************************************************************************** + * Function: DecodeNextElement + * + * Description: decode next syntactic element in AAC frame + * + * Inputs: double pointer to buffer containing next element + * pointer to bit offset + * pointer to number of valid bits remaining in buf + * + * Outputs: type of element decoded (aacDecInfo->currBlockID) + * type of element decoded last time (aacDecInfo->prevBlockID) + * updated aacDecInfo state, depending on which element was decoded + * updated buffer pointer + * updated bit offset + * updated number of available bits + * + * Return: 0 if successful, error code (< 0) if error + **********************************************************************************************************************/ +int DecodeNextElement(uint8_t **buf, int *bitOffset, int *bitsAvail) +{ + int err, bitsUsed; + + /* init bitstream reader */ + SetBitstreamPointer((*bitsAvail + 7) >> 3, *buf); + GetBits(*bitOffset); + + m_AACDecInfo->prevBlockID = m_AACDecInfo->currBlockID; + m_AACDecInfo->currBlockID = GetBits(NUM_SYN_ID_BITS); + + /* set defaults (could be overwritten by DecodeXXXElement(), depending on currBlockID) */ + m_PSInfoBase->commonWin = 0; + + err = 0; + switch (m_AACDecInfo->currBlockID) { + case AAC_ID_SCE: + err = DecodeSingleChannelElement(); + break; + case AAC_ID_CPE: + err = DecodeChannelPairElement(); + break; + case AAC_ID_CCE: + break; + case AAC_ID_LFE: + err = DecodeLFEChannelElement(); + break; + case AAC_ID_DSE: + err = DecodeDataStreamElement(); + break; + case AAC_ID_PCE: + err = DecodeProgramConfigElement(0); + break; + case AAC_ID_FIL: + err = DecodeFillElement(); + break; + case AAC_ID_END: + break; + } + if (err) + return ERR_AAC_SYNTAX_ELEMENT; + + /* update bitstream reader */ + bitsUsed = CalcBitsUsed(*buf, *bitOffset); + *buf += (bitsUsed + *bitOffset) >> 3; + *bitOffset = (bitsUsed + *bitOffset) & 0x07; + *bitsAvail -= bitsUsed; + + if (*bitsAvail < 0) + return ERR_AAC_INDATA_UNDERFLOW; + + return ERR_AAC_NONE; +} + +/*********************************************************************************************************************** + * Function: PreMultiply + * + * Description: pre-twiddle stage of DCT4 + * + * Inputs: table index (for transform size) + * buffer of nmdct samples + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: minimum 1 GB in, 2 GB out, gains 5 (short) or 8 (long) frac bits + * i.e. gains 2-7= -5 int bits (short) or 2-10 = -8 int bits (long) + * normalization by -1/N is rolled into tables here (see trigtabs.c) + * uses 3-mul, 3-add butterflies instead of 4-mul, 2-add + **********************************************************************************************************************/ +void PreMultiply(int tabidx, int *zbuf1) +{ + int i, nmdct, ar1, ai1, ar2, ai2, z1, z2; + int t, cms2, cps2a, sin2a, cps2b, sin2b; + int *zbuf2; + const uint32_t *csptr; + + nmdct = nmdctTab[tabidx]; + zbuf2 = zbuf1 + nmdct - 1; + csptr = cos4sin4tab + cos4sin4tabOffset[tabidx]; + + /* whole thing should fit in registers - verify that compiler does this */ + for (i = nmdct >> 2; i != 0; i--) { + /* cps2 = (cos+sin), sin2 = sin, cms2 = (cos-sin) */ + cps2a = *csptr++; + sin2a = *csptr++; + cps2b = *csptr++; + sin2b = *csptr++; + + ar1 = *(zbuf1 + 0); + ai2 = *(zbuf1 + 1); + ai1 = *(zbuf2 + 0); + ar2 = *(zbuf2 - 1); + + /* gain 2 ints bit from MULSHIFT32 by Q30, but drop 7 or 10 int bits from table scaling of 1/M + * max per-sample gain (ignoring implicit scaling) = MAX(sin(angle)+cos(angle)) = 1.414 + * i.e. gain 1 GB since worst case is sin(angle) = cos(angle) = 0.707 (Q30), gain 2 from + * extra sign bits, and eat one in adding + */ + t = MULSHIFT32(sin2a, ar1 + ai1); + z2 = MULSHIFT32(cps2a, ai1) - t; + cms2 = cps2a - 2*sin2a; + z1 = MULSHIFT32(cms2, ar1) + t; + *zbuf1++ = z1; /* cos*ar1 + sin*ai1 */ + *zbuf1++ = z2; /* cos*ai1 - sin*ar1 */ + + t = MULSHIFT32(sin2b, ar2 + ai2); + z2 = MULSHIFT32(cps2b, ai2) - t; + cms2 = cps2b - 2*sin2b; + z1 = MULSHIFT32(cms2, ar2) + t; + *zbuf2-- = z2; /* cos*ai2 - sin*ar2 */ + *zbuf2-- = z1; /* cos*ar2 + sin*ai2 */ + } +} + +/*********************************************************************************************************************** + * Function: PostMultiply + * + * Description: post-twiddle stage of DCT4 + * + * Inputs: table index (for transform size) + * buffer of nmdct samples + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: minimum 1 GB in, 2 GB out - gains 2 int bits + * uses 3-mul, 3-add butterflies instead of 4-mul, 2-add + **********************************************************************************************************************/ +void PostMultiply(int tabidx, int *fft1) +{ + int i, nmdct, ar1, ai1, ar2, ai2, skipFactor; + int t, cms2, cps2, sin2; + int *fft2; + const int *csptr; + + nmdct = nmdctTab[tabidx]; + csptr = cos1sin1tab; + skipFactor = postSkip[tabidx]; + fft2 = fft1 + nmdct - 1; + + /* load coeffs for first pass + * cps2 = (cos+sin), sin2 = sin, cms2 = (cos-sin) + */ + cps2 = *csptr++; + sin2 = *csptr; + csptr += skipFactor; + cms2 = cps2 - 2*sin2; + + for (i = nmdct >> 2; i != 0; i--) { + ar1 = *(fft1 + 0); + ai1 = *(fft1 + 1); + ar2 = *(fft2 - 1); + ai2 = *(fft2 + 0); + + /* gain 2 ints bit from MULSHIFT32 by Q30 + * max per-sample gain = MAX(sin(angle)+cos(angle)) = 1.414 + * i.e. gain 1 GB since worst case is sin(angle) = cos(angle) = 0.707 (Q30), gain 2 from + * extra sign bits, and eat one in adding + */ + t = MULSHIFT32(sin2, ar1 + ai1); + *fft2-- = t - MULSHIFT32(cps2, ai1); /* sin*ar1 - cos*ai1 */ + *fft1++ = t + MULSHIFT32(cms2, ar1); /* cos*ar1 + sin*ai1 */ + cps2 = *csptr++; + sin2 = *csptr; + csptr += skipFactor; + + ai2 = -ai2; + t = MULSHIFT32(sin2, ar2 + ai2); + *fft2-- = t - MULSHIFT32(cps2, ai2); /* sin*ar1 - cos*ai1 */ + cms2 = cps2 - 2*sin2; + *fft1++ = t + MULSHIFT32(cms2, ar2); /* cos*ar1 + sin*ai1 */ + } +} + +/*********************************************************************************************************************** + * Function: PreMultiplyRescale + * + * Description: pre-twiddle stage of DCT4, with rescaling for extra guard bits + * + * Inputs: table index (for transform size) + * buffer of nmdct samples + * number of guard bits to add to input before processing + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: see notes on PreMultiply(), above + **********************************************************************************************************************/ +void PreMultiplyRescale(int tabidx, int *zbuf1, int es) +{ + int i, nmdct, ar1, ai1, ar2, ai2, z1, z2; + int t, cms2, cps2a, sin2a, cps2b, sin2b; + int *zbuf2; + const uint32_t *csptr; + + nmdct = nmdctTab[tabidx]; + zbuf2 = zbuf1 + nmdct - 1; + csptr = cos4sin4tab + cos4sin4tabOffset[tabidx]; + + /* whole thing should fit in registers - verify that compiler does this */ + for (i = nmdct >> 2; i != 0; i--) { + /* cps2 = (cos+sin), sin2 = sin, cms2 = (cos-sin) */ + cps2a = *csptr++; + sin2a = *csptr++; + cps2b = *csptr++; + sin2b = *csptr++; + + ar1 = *(zbuf1 + 0) >> es; + ai1 = *(zbuf2 + 0) >> es; + ai2 = *(zbuf1 + 1) >> es; + + t = MULSHIFT32(sin2a, ar1 + ai1); + z2 = MULSHIFT32(cps2a, ai1) - t; + cms2 = cps2a - 2*sin2a; + z1 = MULSHIFT32(cms2, ar1) + t; + *zbuf1++ = z1; + *zbuf1++ = z2; + + ar2 = *(zbuf2 - 1) >> es; /* do here to free up register used for es */ + + t = MULSHIFT32(sin2b, ar2 + ai2); + z2 = MULSHIFT32(cps2b, ai2) - t; + cms2 = cps2b - 2*sin2b; + z1 = MULSHIFT32(cms2, ar2) + t; + *zbuf2-- = z2; + *zbuf2-- = z1; + + } +} + +/*********************************************************************************************************************** + * Function: PostMultiplyRescale + * + * Description: post-twiddle stage of DCT4, with rescaling for extra guard bits + * + * Inputs: table index (for transform size) + * buffer of nmdct samples + * number of guard bits to remove from output + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: clips output to [-2^30, 2^30 - 1], guaranteeing at least 1 guard bit + * see notes on PostMultiply(), above + **********************************************************************************************************************/ +void PostMultiplyRescale(int tabidx, int *fft1, int es) +{ + int i, nmdct, ar1, ai1, ar2, ai2, skipFactor, z; + int t, cs2, sin2; + int *fft2; + const int *csptr; + + nmdct = nmdctTab[tabidx]; + csptr = cos1sin1tab; + skipFactor = postSkip[tabidx]; + fft2 = fft1 + nmdct - 1; + + /* load coeffs for first pass + * cps2 = (cos+sin), sin2 = sin, cms2 = (cos-sin) + */ + cs2 = *csptr++; + sin2 = *csptr; + csptr += skipFactor; + + for (i = nmdct >> 2; i != 0; i--) { + ar1 = *(fft1 + 0); + ai1 = *(fft1 + 1); + ai2 = *(fft2 + 0); + + t = MULSHIFT32(sin2, ar1 + ai1); + z = t - MULSHIFT32(cs2, ai1); + {int sign = (z) >> 31; if (sign != (z) >> (30 - (es))) {(z) = sign ^ (0x3fffffff);} else {(z) = (z) << (es);}} + *fft2-- = z; + cs2 -= 2*sin2; + z = t + MULSHIFT32(cs2, ar1); + {int sign = (z) >> 31; if (sign != (z) >> (30 - (es))) {(z) = sign ^ (0x3fffffff);} else {(z) = (z) << (es);}} + *fft1++ = z; + + cs2 = *csptr++; + sin2 = *csptr; + csptr += skipFactor; + + ar2 = *fft2; + ai2 = -ai2; + t = MULSHIFT32(sin2, ar2 + ai2); + z = t - MULSHIFT32(cs2, ai2); + {int sign = (z) >> 31; if (sign != (z) >> (30 - (es))) {(z) = sign ^ (0x3fffffff);} else {(z) = (z) << (es);}} + *fft2-- = z; + cs2 -= 2*sin2; + z = t + MULSHIFT32(cs2, ar2); + {int sign = (z) >> 31; if (sign != (z) >> (30 - (es))) {(z) = sign ^ (0x3fffffff);} else {(z) = (z) << (es);}} + *fft1++ = z; + cs2 += 2*sin2; + } +} + +/*********************************************************************************************************************** + * Function: DCT4 + * + * Description: type-IV DCT + * + * Inputs: table index (for transform size) + * buffer of nmdct samples + * number of guard bits in the input buffer + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: operates in-place + * if number of guard bits in input is < GBITS_IN_DCT4, the input is + * scaled (>>) before the DCT4 and rescaled (<<, with clipping) after + * the DCT4 (rare) + * the output has FBITS_LOST_DCT4 fewer fraction bits than the input + * the output will always have at least 1 guard bit (GBITS_IN_DCT4 >= 4) + * int bits gained per stage (PreMul + FFT + PostMul) + * short blocks = (-5 + 4 + 2) = 1 total + * long blocks = (-8 + 7 + 2) = 1 total + **********************************************************************************************************************/ +void DCT4(int tabidx, int *coef, int gb) +{ + int es; + + /* fast in-place DCT-IV - adds guard bits if necessary */ + if (gb < GBITS_IN_DCT4) { + es = GBITS_IN_DCT4 - gb; + PreMultiplyRescale(tabidx, coef, es); + R4FFT(tabidx, coef); + PostMultiplyRescale(tabidx, coef, es); + } else { + PreMultiply(tabidx, coef); + R4FFT(tabidx, coef); + PostMultiply(tabidx, coef); + } +} + +/*********************************************************************************************************************** + * Function: BitReverse + * + * Description: Ken's fast in-place bit reverse, using super-small table + * + * Inputs: buffer of samples + * table index (for transform size) + * + * Outputs: bit-reversed samples in same buffer + * + * Return: none + **********************************************************************************************************************/ +void BitReverse(int *inout, int tabidx) +{ + int *part0, *part1; + int a,b, t; + const uint8_t* tab = bitrevtab + bitrevtabOffset[tabidx]; + int nbits = nfftlog2Tab[tabidx]; + + part0 = inout; + part1 = inout + (1 << nbits); + + while ((a = pgm_read_byte(tab++)) != 0) { + b = pgm_read_byte(tab++); + + t=part0[4*a+0]; part0[4*a+0]=part0[4*b+0]; part0[4*b+0]=t; /* 0xxx0 <-> 0yyy0 */ + t=part0[4*a+1]; part0[4*a+1]=part0[4*b+1]; part0[4*b+1]=t; + + t=part0[4*a+2]; part0[4*a+2]=part1[4*b+0]; part1[4*b+0]=t; /* 0xxx0 <-> 0yyy0 */ + t=part0[4*a+3]; part0[4*a+3]=part1[4*b+1]; part1[4*b+1]=t; + + t=part1[4*a+0]; part1[4*a+0]=part0[4*b+2]; part0[4*b+2]=t; /* 1xxx0 <-> 0yyy1 */ + t=part1[4*a+1]; part1[4*a+1]=part0[4*b+3]; part0[4*b+3]=t; + + t=part1[4*a+2]; part1[4*a+2]=part1[4*b+2]; part1[4*b+2]=t; /* 1xxx1 <-> 1yyy1 */ + t=part1[4*a+3]; part1[4*a+3]=part1[4*b+3]; part1[4*b+3]=t; + } + + do { + t=part0[4*a+2]; part0[4*a+2]=part1[4*a+0]; part1[4*a+0]=t; /* 0xxx1 <-> 1xxx0 */ + t=part0[4*a+3]; part0[4*a+3]=part1[4*a+1]; part1[4*a+1]=t; + } while ((a = pgm_read_byte(tab++)) != 0); + + +} + +/*********************************************************************************************************************** + * Function: R4FirstPass + * + * Description: radix-4 trivial pass for decimation-in-time FFT + * + * Inputs: buffer of (bit-reversed) samples + * number of R4 butterflies per group (i.e. nfft / 4) + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: assumes 2 guard bits, gains no integer bits, + * guard bits out = guard bits in - 2 + **********************************************************************************************************************/ +void R4FirstPass(int *x, int bg) +{ + int ar, ai, br, bi, cr, ci, dr, di; + + for (; bg != 0; bg--) { + + ar = x[0] + x[2]; + br = x[0] - x[2]; + ai = x[1] + x[3]; + bi = x[1] - x[3]; + cr = x[4] + x[6]; + dr = x[4] - x[6]; + ci = x[5] + x[7]; + di = x[5] - x[7]; + + /* max per-sample gain = 4.0 (adding 4 inputs together) */ + x[0] = ar + cr; + x[4] = ar - cr; + x[1] = ai + ci; + x[5] = ai - ci; + x[2] = br + di; + x[6] = br - di; + x[3] = bi - dr; + x[7] = bi + dr; + + x += 8; + } +} + +/*********************************************************************************************************************** + * Function: R8FirstPass + * + * Description: radix-8 trivial pass for decimation-in-time FFT + * + * Inputs: buffer of (bit-reversed) samples + * number of R8 butterflies per group (i.e. nfft / 8) + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: assumes 3 guard bits, gains 1 integer bit + * guard bits out = guard bits in - 3 (if inputs are full scale) + * or guard bits in - 2 (if inputs bounded to +/- sqrt(2)/2) + * see scaling comments in code + **********************************************************************************************************************/ +void R8FirstPass(int *x, int bg) +{ + int ar, ai, br, bi, cr, ci, dr, di; + int sr, si, tr, ti, ur, ui, vr, vi; + int wr, wi, xr, xi, yr, yi, zr, zi; + + for (; bg != 0; bg--) { + + ar = x[0] + x[2]; + br = x[0] - x[2]; + ai = x[1] + x[3]; + bi = x[1] - x[3]; + cr = x[4] + x[6]; + dr = x[4] - x[6]; + ci = x[5] + x[7]; + di = x[5] - x[7]; + + sr = ar + cr; + ur = ar - cr; + si = ai + ci; + ui = ai - ci; + tr = br - di; + vr = br + di; + ti = bi + dr; + vi = bi - dr; + + ar = x[ 8] + x[10]; + br = x[ 8] - x[10]; + ai = x[ 9] + x[11]; + bi = x[ 9] - x[11]; + cr = x[12] + x[14]; + dr = x[12] - x[14]; + ci = x[13] + x[15]; + di = x[13] - x[15]; + + /* max gain of wr/wi/yr/yi vs input = 2 + * (sum of 4 samples >> 1) + */ + wr = (ar + cr) >> 1; + yr = (ar - cr) >> 1; + wi = (ai + ci) >> 1; + yi = (ai - ci) >> 1; + + /* max gain of output vs input = 4 + * (sum of 4 samples >> 1 + sum of 4 samples >> 1) + */ + x[ 0] = (sr >> 1) + wr; + x[ 8] = (sr >> 1) - wr; + x[ 1] = (si >> 1) + wi; + x[ 9] = (si >> 1) - wi; + x[ 4] = (ur >> 1) + yi; + x[12] = (ur >> 1) - yi; + x[ 5] = (ui >> 1) - yr; + x[13] = (ui >> 1) + yr; + + ar = br - di; + cr = br + di; + ai = bi + dr; + ci = bi - dr; + + /* max gain of xr/xi/zr/zi vs input = 4*sqrt(2)/2 = 2*sqrt(2) + * (sum of 8 samples, multiply by sqrt(2)/2, implicit >> 1 from Q31) + */ + xr = MULSHIFT32(SQRTHALF, ar - ai); + xi = MULSHIFT32(SQRTHALF, ar + ai); + zr = MULSHIFT32(SQRTHALF, cr - ci); + zi = MULSHIFT32(SQRTHALF, cr + ci); + + /* max gain of output vs input = (2 + 2*sqrt(2) ~= 4.83) + * (sum of 4 samples >> 1, plus xr/xi/zr/zi with gain of 2*sqrt(2)) + * in absolute terms, we have max gain of appx 9.656 (4 + 0.707*8) + * but we also gain 1 int bit (from MULSHIFT32 or from explicit >> 1) + */ + x[ 6] = (tr >> 1) - xr; + x[14] = (tr >> 1) + xr; + x[ 7] = (ti >> 1) - xi; + x[15] = (ti >> 1) + xi; + x[ 2] = (vr >> 1) + zi; + x[10] = (vr >> 1) - zi; + x[ 3] = (vi >> 1) - zr; + x[11] = (vi >> 1) + zr; + + x += 16; + } +} + +/*********************************************************************************************************************** + * Function: R4Core + * + * Description: radix-4 pass for decimation-in-time FFT + * + * Inputs: buffer of samples + * number of R4 butterflies per group + * number of R4 groups per pass + * pointer to twiddle factors tables + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: gain 2 integer bits per pass (see scaling comments in code) + * min 1 GB in + * gbOut = gbIn - 1 (short block) or gbIn - 2 (long block) + * uses 3-mul, 3-add butterflies instead of 4-mul, 2-add + **********************************************************************************************************************/ +void R4Core(int *x, int bg, int gp, int *wtab) +{ + int ar, ai, br, bi, cr, ci, dr, di, tr, ti; + int wd, ws, wi; + int i, j, step; + int *xptr, *wptr; + + for (; bg != 0; gp <<= 2, bg >>= 2) { + + step = 2*gp; + xptr = x; + + /* max per-sample gain, per group < 1 + 3*sqrt(2) ~= 5.25 if inputs x are full-scale + * do 3 groups for long block, 2 groups for short block (gain 2 int bits per group) + * + * very conservative scaling: + * group 1: max gain = 5.25, int bits gained = 2, gb used = 1 (2^3 = 8) + * group 2: max gain = 5.25^2 = 27.6, int bits gained = 4, gb used = 1 (2^5 = 32) + * group 3: max gain = 5.25^3 = 144.7, int bits gained = 6, gb used = 2 (2^8 = 256) + */ + for (i = bg; i != 0; i--) { + + wptr = wtab; + + for (j = gp; j != 0; j--) { + + ar = xptr[0]; + ai = xptr[1]; + xptr += step; + + /* gain 2 int bits for br/bi, cr/ci, dr/di (MULSHIFT32 by Q30) + * gain 1 net GB + */ + ws = wptr[0]; + wi = wptr[1]; + br = xptr[0]; + bi = xptr[1]; + wd = ws + 2*wi; + tr = MULSHIFT32(wi, br + bi); + br = MULSHIFT32(wd, br) - tr; /* cos*br + sin*bi */ + bi = MULSHIFT32(ws, bi) + tr; /* cos*bi - sin*br */ + xptr += step; + + ws = wptr[2]; + wi = wptr[3]; + cr = xptr[0]; + ci = xptr[1]; + wd = ws + 2*wi; + tr = MULSHIFT32(wi, cr + ci); + cr = MULSHIFT32(wd, cr) - tr; + ci = MULSHIFT32(ws, ci) + tr; + xptr += step; + + ws = wptr[4]; + wi = wptr[5]; + dr = xptr[0]; + di = xptr[1]; + wd = ws + 2*wi; + tr = MULSHIFT32(wi, dr + di); + dr = MULSHIFT32(wd, dr) - tr; + di = MULSHIFT32(ws, di) + tr; + wptr += 6; + + tr = ar; + ti = ai; + ar = (tr >> 2) - br; + ai = (ti >> 2) - bi; + br = (tr >> 2) + br; + bi = (ti >> 2) + bi; + + tr = cr; + ti = ci; + cr = tr + dr; + ci = di - ti; + dr = tr - dr; + di = di + ti; + + xptr[0] = ar + ci; + xptr[1] = ai + dr; + xptr -= step; + xptr[0] = br - cr; + xptr[1] = bi - di; + xptr -= step; + xptr[0] = ar - ci; + xptr[1] = ai - dr; + xptr -= step; + xptr[0] = br + cr; + xptr[1] = bi + di; + xptr += 2; + } + xptr += 3*step; + } + wtab += 3*step; + } +} + + +/*********************************************************************************************************************** + * Function: R4FFT + * + * Description: Ken's very fast in-place radix-4 decimation-in-time FFT + * + * Inputs: table index (for transform size) + * buffer of samples (non bit-reversed) + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: assumes 5 guard bits in for nfft <= 512 + * gbOut = gbIn - 4 (assuming input is from PreMultiply) + * gains log2(nfft) - 2 int bits total + * so gain 7 int bits (LONG), 4 int bits (SHORT) + **********************************************************************************************************************/ +void R4FFT(int tabidx, int *x) +{ + int order = nfftlog2Tab[tabidx]; + int nfft = nfftTab[tabidx]; + + /* decimation in time */ + BitReverse(x, tabidx); + + if (order & 0x1) { + /* long block: order = 9, nfft = 512 */ + R8FirstPass(x, nfft >> 3); /* gain 1 int bit, lose 2 GB */ + R4Core(x, nfft >> 5, 8, (int *)twidTabOdd); /* gain 6 int bits, lose 2 GB */ + } else { + /* short block: order = 6, nfft = 64 */ + R4FirstPass(x, nfft >> 2); /* gain 0 int bits, lose 2 GB */ + R4Core(x, nfft >> 4, 4, (int *)twidTabEven); /* gain 4 int bits, lose 1 GB */ + } +} + +/*********************************************************************************************************************** + * Function: UnpackZeros + * + * Description: fill a section of coefficients with zeros + * + * Inputs: number of coefficients + * + * Outputs: nVals zeros, starting at coef + * + * Return: none + * + * Notes: assumes nVals is always a multiple of 4 because all scalefactor bands + * are a multiple of 4 coefficients long + **********************************************************************************************************************/ +void UnpackZeros(int nVals, int *coef) +{ + while (nVals > 0) { + *coef++ = 0; + *coef++ = 0; + *coef++ = 0; + *coef++ = 0; + nVals -= 4; + } +} + +/*********************************************************************************************************************** + * Function: UnpackQuads + * + * Description: decode a section of 4-way vector Huffman coded coefficients + * + * Inputs index of Huffman codebook + * number of coefficients + * + * Outputs: nVals coefficients, starting at coef + * + * Return: none + * + * Notes: assumes nVals is always a multiple of 4 because all scalefactor bands + * are a multiple of 4 coefficients long + **********************************************************************************************************************/ +void UnpackQuads(int cb, int nVals, int *coef) +{ + int w, x, y, z, maxBits, nCodeBits, nSignBits, val; + uint32_t bitBuf; + + maxBits = huffTabSpecInfo[cb - HUFFTAB_SPEC_OFFSET].maxBits + 4; + while (nVals > 0) { + /* decode quad */ + bitBuf = GetBitsNoAdvance(maxBits) << (32 - maxBits); + nCodeBits = DecodeHuffmanScalar(huffTabSpec, &huffTabSpecInfo[cb - HUFFTAB_SPEC_OFFSET], bitBuf, &val); + + w = (((int32_t)(val) << 20) >> 29); /* bits 11-9, sign-extend */ + x = (((int32_t)(val) << 23) >> 29); /* bits 8-6, sign-extend */ + y = (((int32_t)(val) << 26) >> 29); /* bits 5-3, sign-extend */ + z = (((int32_t)(val) << 29) >> 29); /* bits 2-0, sign-extend */ + + bitBuf <<= nCodeBits; + nSignBits = (int)(((uint32_t)(val) << 17) >> 29); /* bits 14-12, unsigned */ + + AdvanceBitstream(nCodeBits + nSignBits); + if (nSignBits) { + if (w) {w ^= ((int32_t)bitBuf >> 31); w -= ((int32_t)bitBuf >> 31); bitBuf <<= 1;} + if (x) {x ^= ((int32_t)bitBuf >> 31); x -= ((int32_t)bitBuf >> 31); bitBuf <<= 1;} + if (y) {y ^= ((int32_t)bitBuf >> 31); y -= ((int32_t)bitBuf >> 31); bitBuf <<= 1;} + if (z) {z ^= ((int32_t)bitBuf >> 31); z -= ((int32_t)bitBuf >> 31); bitBuf <<= 1;} + } + *coef++ = w; *coef++ = x; *coef++ = y; *coef++ = z; + nVals -= 4; + } +} + +/*********************************************************************************************************************** + * Function: UnpackPairsNoEsc + * + * Description: decode a section of 2-way vector Huffman coded coefficients, + * using non-esc tables (5 through 10) + * + * Inputs index of Huffman codebook (must not be the escape codebook) + * number of coefficients + * + * Outputs: nVals coefficients, starting at coef + * + * Return: none + * + * Notes: assumes nVals is always a multiple of 2 because all scalefactor bands + * are a multiple of 4 coefficients long + **********************************************************************************************************************/ +void UnpackPairsNoEsc(int cb, int nVals, int *coef) +{ + int y, z, maxBits, nCodeBits, nSignBits, val; + uint32_t bitBuf; + + maxBits = huffTabSpecInfo[cb - HUFFTAB_SPEC_OFFSET].maxBits + 2; + while (nVals > 0) { + /* decode pair */ + bitBuf = GetBitsNoAdvance(maxBits) << (32 - maxBits); + nCodeBits = DecodeHuffmanScalar(huffTabSpec, &huffTabSpecInfo[cb-HUFFTAB_SPEC_OFFSET], bitBuf, &val); + + y = (((int32_t)(val) << 22) >> 27); /* bits 9-5, sign-extend */ + z = (((int32_t)(val) << 27) >> 27); /* bits 4-0, sign-extend */ + + bitBuf <<= nCodeBits; + nSignBits = (((uint32_t)(val) << 20) >> 30); /* bits 11-10, unsigned */ + AdvanceBitstream(nCodeBits + nSignBits); + if (nSignBits) { + if (y) {y ^= ((int32_t)bitBuf >> 31); y -= ((int32_t)bitBuf >> 31); bitBuf <<= 1;} + if (z) {z ^= ((int32_t)bitBuf >> 31); z -= ((int32_t)bitBuf >> 31); bitBuf <<= 1;} + } + *coef++ = y; *coef++ = z; + nVals -= 2; + } +} + +/*********************************************************************************************************************** + * Function: UnpackPairsEsc + * + * Description: decode a section of 2-way vector Huffman coded coefficients, + * using esc table (11) + * + * Inputs index of Huffman codebook (must be the escape codebook) + * number of coefficients + * + * Outputs: nVals coefficients, starting at coef + * + * Return: none + * + * Notes: assumes nVals is always a multiple of 2 because all scalefactor bands + * are a multiple of 4 coefficients long + **********************************************************************************************************************/ +void UnpackPairsEsc(int cb, int nVals, int *coef) +{ + int y, z, maxBits, nCodeBits, nSignBits, n, val; + uint32_t bitBuf; + + maxBits = huffTabSpecInfo[cb - HUFFTAB_SPEC_OFFSET].maxBits + 2; + while (nVals > 0) { + /* decode pair with escape value */ + bitBuf = GetBitsNoAdvance(maxBits) << (32 - maxBits); + nCodeBits = DecodeHuffmanScalar(huffTabSpec, &huffTabSpecInfo[cb-HUFFTAB_SPEC_OFFSET], bitBuf, &val); + + y = (((int32_t)(val) << 20) >> 26); /* bits 11-6, sign-extend */ + z = (((int32_t)(val) << 26) >> 26); /* bits 5-0, sign-extend */ + + bitBuf <<= nCodeBits; + nSignBits = (((uint32_t)(val) << 18) >> 30); /* bits 13-12, unsigned */ + AdvanceBitstream(nCodeBits + nSignBits); + + if (y == 16) { + n = 4; + while (GetBits(1) == 1) + n++; + y = (1 << n) + GetBits(n); + } + if (z == 16) { + n = 4; + while (GetBits(1) == 1) + n++; + z = (1 << n) + GetBits(n); + } + + if (nSignBits) { + if (y) {y ^= ((int32_t)bitBuf >> 31); y -= ((int32_t)bitBuf >> 31); bitBuf <<= 1;} + if (z) {z ^= ((int32_t)bitBuf >> 31); z -= ((int32_t)bitBuf >> 31); bitBuf <<= 1;} + } + + *coef++ = y; *coef++ = z; + nVals -= 2; + } +} + +/*********************************************************************************************************************** + * Function: DecodeSpectrumLong + * + * Description: decode transform coefficients for frame with one long block + * + * Inputs: index of current channel + * + * Outputs: decoded, quantized coefficients for this channel + * + * Return: none + * + * Notes: adds in pulse data if present + * fills coefficient buffer with zeros in any region not coded with + * codebook in range [1, 11] (including sfb's above sfbMax) + **********************************************************************************************************************/ +void DecodeSpectrumLong(int ch) +{ + int i, sfb, cb, nVals, offset; + const uint16_t *sfbTab; + uint8_t *sfbCodeBook; + int *coef; + ICSInfo_t *icsInfo; + + coef = m_PSInfoBase->coef[ch]; + icsInfo = (ch == 1 && m_PSInfoBase->commonWin == 1) ? &(m_PSInfoBase->icsInfo[0]) : &(m_PSInfoBase->icsInfo[ch]); + + /* decode long block */ + sfbTab = sfBandTabLong + sfBandTabLongOffset[m_PSInfoBase->sampRateIdx]; + sfbCodeBook = m_PSInfoBase->sfbCodeBook[ch]; + for (sfb = 0; sfb < icsInfo->maxSFB; sfb++) { + cb = *sfbCodeBook++; + nVals = sfbTab[sfb+1] - sfbTab[sfb]; + + if (cb == 0) + UnpackZeros(nVals, coef); + else if (cb <= 4) + UnpackQuads(cb, nVals, coef); + else if (cb <= 10) + UnpackPairsNoEsc(cb, nVals, coef); + else if (cb == 11) + UnpackPairsEsc(cb, nVals, coef); + else + UnpackZeros(nVals, coef); + + coef += nVals; + } + + /* fill with zeros above maxSFB */ + nVals = NSAMPS_LONG - sfbTab[sfb]; + UnpackZeros(nVals, coef); + + /* add pulse data, if present */ + if (m_pulseInfo[ch].pulseDataPresent) { + coef = m_PSInfoBase->coef[ch]; + offset = sfbTab[m_pulseInfo[ch].startSFB]; + for (i = 0; i < m_pulseInfo[ch].numPulse; i++) { + offset += m_pulseInfo[ch].offset[i]; + if (coef[offset] > 0) + coef[offset] += m_pulseInfo[ch].amp[i]; + else + coef[offset] -= m_pulseInfo[ch].amp[i]; + } + ASSERT(offset < NSAMPS_LONG); + } +} + +/*********************************************************************************************************************** + * Function: DecodeSpectrumShort + * + * Description: decode transform coefficients for frame with eight short blocks + * + * Inputs: index of current channel + * + * Outputs: decoded, quantized coefficients for this channel + * + * Return: none + * + * Notes: fills coefficient buffer with zeros in any region not coded with + * codebook in range [1, 11] (including sfb's above sfbMax) + * deinterleaves window groups into 8 windows + **********************************************************************************************************************/ +void DecodeSpectrumShort(int ch) +{ + int gp, cb, nVals=0, win, offset, sfb; + const uint16_t *sfbTab; + uint8_t *sfbCodeBook; + int *coef; + ICSInfo_t *icsInfo; + + coef = m_PSInfoBase->coef[ch]; + icsInfo = (ch == 1 && m_PSInfoBase->commonWin == 1) ? &(m_PSInfoBase->icsInfo[0]) : &(m_PSInfoBase->icsInfo[ch]); + + /* decode short blocks, deinterleaving in-place */ + sfbTab = sfBandTabShort + sfBandTabShortOffset[m_PSInfoBase->sampRateIdx]; + sfbCodeBook = m_PSInfoBase->sfbCodeBook[ch]; + for (gp = 0; gp < icsInfo->numWinGroup; gp++) { + for (sfb = 0; sfb < icsInfo->maxSFB; sfb++) { + nVals = sfbTab[sfb+1] - sfbTab[sfb]; + cb = *sfbCodeBook++; + + for (win = 0; win < icsInfo->winGroupLen[gp]; win++) { + offset = win*NSAMPS_SHORT; + if (cb == 0) + UnpackZeros(nVals, coef + offset); + else if (cb <= 4) + UnpackQuads(cb, nVals, coef + offset); + else if (cb <= 10) + UnpackPairsNoEsc(cb, nVals, coef + offset); + else if (cb == 11) + UnpackPairsEsc(cb, nVals, coef + offset); + else + UnpackZeros(nVals, coef + offset); + } + coef += nVals; + } + + /* fill with zeros above maxSFB */ + for (win = 0; win < icsInfo->winGroupLen[gp]; win++) { + offset = win*NSAMPS_SHORT; + nVals = NSAMPS_SHORT - sfbTab[sfb]; + UnpackZeros(nVals, coef + offset); + } + coef += nVals; + coef += (icsInfo->winGroupLen[gp] - 1)*NSAMPS_SHORT; + } + + ASSERT(coef == m_PSInfoBase->coef[ch] + NSAMPS_LONG); +} + +#ifndef AAC_ENABLE_SBR +/*********************************************************************************************************************** + * Function: DecWindowOverlap + * + * Description: apply synthesis window, do overlap-add, clip to 16-bit PCM, + * for winSequence LONG-LONG + * + * Inputs: input buffer (output of type-IV DCT) + * overlap buffer (saved from last time) + * number of channels + * window type (sin or KBD) for input buffer + * window type (sin or KBD) for overlap buffer + * + * Outputs: one channel, one frame of 16-bit PCM, interleaved by nChans + * + * Return: none + * + * Notes: this processes one channel at a time, but skips every other sample in + * the output buffer (pcm) for stereo interleaving + * this should fit in registers on ARM + * + **********************************************************************************************************************/ +void DecWindowOverlap(int *buf0, int *over0, short *pcm0, int nChans, int winTypeCurr, int winTypePrev) +{ + int in, w0, w1, f0, f1; + int *buf1, *over1; + short *pcm1; + const int *wndCurr, *wndPrev; + + buf0 += (1024 >> 1); + buf1 = buf0 - 1; + pcm1 = pcm0 + (1024 - 1) * nChans; + over1 = over0 + 1024 - 1; + + wndPrev = (winTypePrev == 1 ? kbdWindow + kbdWindowOffset[1] : sinWindow + sinWindowOffset[1]); + if (winTypeCurr == winTypePrev) { + /* cut window loads in half since current and overlap sections use same symmetric window */ + do { + w0 = *wndPrev++; + w1 = *wndPrev++; + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *over0; + *pcm0 = CLIPTOSHORT( (in - f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm0 += nChans; + + in = *over1; + *pcm1 = CLIPTOSHORT( (in + f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm1 -= nChans; + + in = *buf1--; + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (over0 < over1); + } else { + /* different windows for current and overlap parts - should still fit in registers on ARM w/o stack spill */ + wndCurr = (winTypeCurr == 1 ? kbdWindow + kbdWindowOffset[1] : sinWindow + sinWindowOffset[1]); + do { + w0 = *wndPrev++; + w1 = *wndPrev++; + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *over0; + *pcm0 = CLIPTOSHORT( (in - f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm0 += nChans; + + in = *over1; + *pcm1 = CLIPTOSHORT( (in + f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm1 -= nChans; + + w0 = *wndCurr++; + w1 = *wndCurr++; + in = *buf1--; + + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (over0 < over1); + } +} + +/*********************************************************************************************************************** + * Function: DecWindowOverlapLongStart + * + * Description: apply synthesis window, do overlap-add, clip to 16-bit PCM, + * for winSequence LONG-START + * + * Inputs: input buffer (output of type-IV DCT) + * overlap buffer (saved from last time) + * number of channels + * window type (sin or KBD) for input buffer + * window type (sin or KBD) for overlap buffer + * + * Outputs: one channel, one frame of 16-bit PCM, interleaved by nChans + * + * Return: none + * + * Notes: this processes one channel at a time, but skips every other sample in + * the output buffer (pcm) for stereo interleaving + * this should fit in registers on ARM + **********************************************************************************************************************/ +void DecWindowOverlapLongStart(int *buf0, int *over0, short *pcm0, int nChans, int winTypeCurr, int winTypePrev) +{ + int i, in, w0, w1, f0, f1; + int *buf1, *over1; + short *pcm1; + const int *wndPrev, *wndCurr; + + buf0 += (1024 >> 1); + buf1 = buf0 - 1; + pcm1 = pcm0 + (1024 - 1) * nChans; + over1 = over0 + 1024 - 1; + + wndPrev = (winTypePrev == 1 ? kbdWindow + kbdWindowOffset[1] : sinWindow + sinWindowOffset[1]); + i = 448; /* 2 outputs, 2 overlaps per loop */ + do { + w0 = *wndPrev++; + w1 = *wndPrev++; + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *over0; + *pcm0 = CLIPTOSHORT( (in - f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm0 += nChans; + + in = *over1; + *pcm1 = CLIPTOSHORT( (in + f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm1 -= nChans; + + in = *buf1--; + + *over1-- = 0; /* Wn = 0 for n = (2047, 2046, ... 1600) */ + *over0++ = in >> 1; /* Wn = 1 for n = (1024, 1025, ... 1471) */ + } while (--i); + + wndCurr = (winTypeCurr == 1 ? kbdWindow + kbdWindowOffset[0] : sinWindow + sinWindowOffset[0]); + + /* do 64 more loops - 2 outputs, 2 overlaps per loop */ + do { + w0 = *wndPrev++; + w1 = *wndPrev++; + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *over0; + *pcm0 = CLIPTOSHORT( (in - f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm0 += nChans; + + in = *over1; + *pcm1 = CLIPTOSHORT( (in + f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm1 -= nChans; + + w0 = *wndCurr++; /* W[0], W[1], ... --> W[255], W[254], ... */ + w1 = *wndCurr++; /* W[127], W[126], ... --> W[128], W[129], ... */ + in = *buf1--; + + *over1-- = MULSHIFT32(w0, in); /* Wn = short window for n = (1599, 1598, ... , 1536) */ + *over0++ = MULSHIFT32(w1, in); /* Wn = short window for n = (1472, 1473, ... , 1535) */ + } while (over0 < over1); +} + +/*********************************************************************************************************************** + * Function: DecWindowOverlapLongStop + * + * Description: apply synthesis window, do overlap-add, clip to 16-bit PCM, + * for winSequence LONG-STOP + * + * Inputs: input buffer (output of type-IV DCT) + * overlap buffer (saved from last time) + * number of channels + * window type (sin or KBD) for input buffer + * window type (sin or KBD) for overlap buffer + * + * Outputs: one channel, one frame of 16-bit PCM, interleaved by nChans + * + * Return: none + * + * Notes: this processes one channel at a time, but skips every other sample in + * the output buffer (pcm) for stereo interleaving + * this should fit in registers on ARM + **********************************************************************************************************************/ +void DecWindowOverlapLongStop(int *buf0, int *over0, short *pcm0, int nChans, int winTypeCurr, int winTypePrev) +{ + int i, in, w0, w1, f0, f1; + int *buf1, *over1; + short *pcm1; + const int *wndPrev, *wndCurr; + + buf0 += (1024 >> 1); + buf1 = buf0 - 1; + pcm1 = pcm0 + (1024 - 1) * nChans; + over1 = over0 + 1024 - 1; + + wndPrev = (winTypePrev == 1 ? kbdWindow + kbdWindowOffset[0] : sinWindow + sinWindowOffset[0]); + wndCurr = (winTypeCurr == 1 ? kbdWindow + kbdWindowOffset[1] : sinWindow + sinWindowOffset[1]); + + i = 448; /* 2 outputs, 2 overlaps per loop */ + do { + /* Wn = 0 for n = (0, 1, ... 447) */ + /* Wn = 1 for n = (576, 577, ... 1023) */ + in = *buf0++; + f1 = in >> 1; /* scale since skipping multiply by Q31 */ + + in = *over0; + *pcm0 = CLIPTOSHORT( (in + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm0 += nChans; + + in = *over1; + *pcm1 = CLIPTOSHORT( (in + f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm1 -= nChans; + + w0 = *wndCurr++; + w1 = *wndCurr++; + in = *buf1--; + + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (--i); + + /* do 64 more loops - 2 outputs, 2 overlaps per loop */ + do { + w0 = *wndPrev++; /* W[0], W[1], ...W[63] */ + w1 = *wndPrev++; /* W[127], W[126], ... W[64] */ + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *over0; + *pcm0 = CLIPTOSHORT( (in - f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm0 += nChans; + + in = *over1; + *pcm1 = CLIPTOSHORT( (in + f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm1 -= nChans; + + w0 = *wndCurr++; + w1 = *wndCurr++; + in = *buf1--; + + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (over0 < over1); +} + +/*********************************************************************************************************************** + * Function: DecWindowOverlapShort + * + * Description: apply synthesis window, do overlap-add, clip to 16-bit PCM, + * for winSequence EIGHT-SHORT (does all 8 short blocks) + * + * Inputs: input buffer (output of type-IV DCT) + * overlap buffer (saved from last time) + * number of channels + * window type (sin or KBD) for input buffer + * window type (sin or KBD) for overlap buffer + * + * Outputs: one channel, one frame of 16-bit PCM, interleaved by nChans + * + * Return: none + * + * Notes: this processes one channel at a time, but skips every other sample in + * the output buffer (pcm) for stereo interleaving + * this should fit in registers on ARM + **********************************************************************************************************************/ +void DecWindowOverlapShort(int *buf0, int *over0, short *pcm0, int nChans, int winTypeCurr, int winTypePrev) +{ + int i, in, w0, w1, f0, f1; + int *buf1, *over1; + short *pcm1; + const int *wndPrev, *wndCurr; + + wndPrev = (winTypePrev == 1 ? kbdWindow + kbdWindowOffset[0] : sinWindow + sinWindowOffset[0]); + wndCurr = (winTypeCurr == 1 ? kbdWindow + kbdWindowOffset[0] : sinWindow + sinWindowOffset[0]); + + /* pcm[0-447] = 0 + overlap[0-447] */ + i = 448; + do { + f0 = *over0++; + f1 = *over0++; + *pcm0 = CLIPTOSHORT( (f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); pcm0 += nChans; + *pcm0 = CLIPTOSHORT( (f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); pcm0 += nChans; + i -= 2; + } while (i); + + /* pcm[448-575] = Wp[0-127] * block0[0-127] + overlap[448-575] */ + pcm1 = pcm0 + (128 - 1) * nChans; + over1 = over0 + 128 - 1; + buf0 += 64; + buf1 = buf0 - 1; + do { + w0 = *wndPrev++; /* W[0], W[1], ...W[63] */ + w1 = *wndPrev++; /* W[127], W[126], ... W[64] */ + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *over0; + *pcm0 = CLIPTOSHORT( (in - f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm0 += nChans; + + in = *over1; + *pcm1 = CLIPTOSHORT( (in + f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm1 -= nChans; + + w0 = *wndCurr++; + w1 = *wndCurr++; + in = *buf1--; + + /* save over0/over1 for next short block, in the slots just vacated */ + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (over0 < over1); + + /* pcm[576-703] = Wc[128-255] * block0[128-255] + Wc[0-127] * block1[0-127] + overlap[576-703] + * pcm[704-831] = Wc[128-255] * block1[128-255] + Wc[0-127] * block2[0-127] + overlap[704-831] + * pcm[832-959] = Wc[128-255] * block2[128-255] + Wc[0-127] * block3[0-127] + overlap[832-959] + */ + for (i = 0; i < 3; i++) { + pcm0 += 64 * nChans; + pcm1 = pcm0 + (128 - 1) * nChans; + over0 += 64; + over1 = over0 + 128 - 1; + buf0 += 64; + buf1 = buf0 - 1; + wndCurr -= 128; + + do { + w0 = *wndCurr++; /* W[0], W[1], ...W[63] */ + w1 = *wndCurr++; /* W[127], W[126], ... W[64] */ + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *(over0 - 128); /* from last short block */ + in += *(over0 + 0); /* from last full frame */ + *pcm0 = CLIPTOSHORT( (in - f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm0 += nChans; + + in = *(over1 - 128); /* from last short block */ + in += *(over1 + 0); /* from last full frame */ + *pcm1 = CLIPTOSHORT( (in + f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm1 -= nChans; + + /* save over0/over1 for next short block, in the slots just vacated */ + in = *buf1--; + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (over0 < over1); + } + + /* pcm[960-1023] = Wc[128-191] * block3[128-191] + Wc[0-63] * block4[0-63] + overlap[960-1023] + * over[0-63] = Wc[192-255] * block3[192-255] + Wc[64-127] * block4[64-127] + */ + pcm0 += 64 * nChans; + over0 -= 832; /* points at overlap[64] */ + over1 = over0 + 128 - 1; /* points at overlap[191] */ + buf0 += 64; + buf1 = buf0 - 1; + wndCurr -= 128; + do { + w0 = *wndCurr++; /* W[0], W[1], ...W[63] */ + w1 = *wndCurr++; /* W[127], W[126], ... W[64] */ + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *(over0 + 768); /* from last short block */ + in += *(over0 + 896); /* from last full frame */ + *pcm0 = CLIPTOSHORT( (in - f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm0 += nChans; + + in = *(over1 + 768); /* from last short block */ + *(over1 - 128) = in + f1; + + in = *buf1--; + *over1-- = MULSHIFT32(w0, in); /* save in overlap[128-191] */ + *over0++ = MULSHIFT32(w1, in); /* save in overlap[64-127] */ + } while (over0 < over1); + + /* over0 now points at overlap[128] */ + + /* over[64-191] = Wc[128-255] * block4[128-255] + Wc[0-127] * block5[0-127] + * over[192-319] = Wc[128-255] * block5[128-255] + Wc[0-127] * block6[0-127] + * over[320-447] = Wc[128-255] * block6[128-255] + Wc[0-127] * block7[0-127] + * over[448-576] = Wc[128-255] * block7[128-255] + */ + for (i = 0; i < 3; i++) { + over0 += 64; + over1 = over0 + 128 - 1; + buf0 += 64; + buf1 = buf0 - 1; + wndCurr -= 128; + do { + w0 = *wndCurr++; /* W[0], W[1], ...W[63] */ + w1 = *wndCurr++; /* W[127], W[126], ... W[64] */ + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + /* from last short block */ + *(over0 - 128) -= f0; + *(over1 - 128)+= f1; + + in = *buf1--; + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (over0 < over1); + } + + /* over[576-1024] = 0 */ + i = 448; + over0 += 64; + do { + *over0++ = 0; + *over0++ = 0; + *over0++ = 0; + *over0++ = 0; + i -= 4; + } while (i); +} + +#endif /* !AAC_ENABLE_SBR */ + +/*********************************************************************************************************************** + * Function: IMDCT + * + * Description: inverse transform and convert to 16-bit PCM + * + * Inputs: index of current channel (0 for SCE/LFE, 0 or 1 for CPE) + * output channel (range = [0, nChans-1]) + * + * Outputs: complete frame of decoded PCM, after inverse transform + * + * Return: 0 if successful, -1 if error + * + * Notes: If AAC_ENABLE_SBR is defined at compile time then window + overlap + * does NOT clip to 16-bit PCM and does NOT interleave channels + * If AAC_ENABLE_SBR is NOT defined at compile time, then window + overlap + * does clip to 16-bit PCM and interleaves channels + * If SBR is enabled at compile time, but we don't know whether it is + * actually used for this frame (e.g. the first frame of a stream), + * we need to produce both clipped 16-bit PCM in outbuf AND + * unclipped 32-bit PCM in the SBR input buffer. In this case we make + * a separate pass over the 32-bit PCM to produce 16-bit PCM output. + * This inflicts a slight performance hit when decoding non-SBR files. + **********************************************************************************************************************/ +int IMDCT(int ch, int chOut, short *outbuf) +{ + int i; + ICSInfo_t *icsInfo; + + icsInfo = (ch == 1 && m_PSInfoBase->commonWin == 1) ? &(m_PSInfoBase->icsInfo[0]) : &(m_PSInfoBase->icsInfo[ch]); + outbuf += chOut; + + /* optimized type-IV DCT (operates inplace) */ + if (icsInfo->winSequence == 2) { + /* 8 short blocks */ + for (i = 0; i < 8; i++) + DCT4(0, m_PSInfoBase->coef[ch] + i*128, m_PSInfoBase->gbCurrent[ch]); + } else { + /* 1 long block */ + DCT4(1, m_PSInfoBase->coef[ch], m_PSInfoBase->gbCurrent[ch]); + } + +#ifdef AAC_ENABLE_SBR + /* window, overlap-add, don't clip to short (send to SBR decoder) + * store the decoded 32-bit samples in top half (second AAC_MAX_NSAMPS samples) of coef buffer + */ + if (icsInfo->winSequence == 0) + DecWindowOverlapNoClip(m_PSInfoBase->coef[ch], m_PSInfoBase->overlap[chOut], + m_PSInfoBase->sbrWorkBuf[ch], icsInfo->winShape, m_PSInfoBase->prevWinShape[chOut]); + else if (icsInfo->winSequence == 1) + DecWindowOverlapLongStartNoClip(m_PSInfoBase->coef[ch], m_PSInfoBase->overlap[chOut], + m_PSInfoBase->sbrWorkBuf[ch], icsInfo->winShape, m_PSInfoBase->prevWinShape[chOut]); + else if (icsInfo->winSequence == 2) + DecWindowOverlapShortNoClip(m_PSInfoBase->coef[ch], m_PSInfoBase->overlap[chOut], + m_PSInfoBase->sbrWorkBuf[ch], icsInfo->winShape, m_PSInfoBase->prevWinShape[chOut]); + else if (icsInfo->winSequence == 3) + DecWindowOverlapLongStopNoClip(m_PSInfoBase->coef[ch], m_PSInfoBase->overlap[chOut], + m_PSInfoBase->sbrWorkBuf[ch], icsInfo->winShape, m_PSInfoBase->prevWinShape[chOut]); + + if (!m_AACDecInfo->sbrEnabled) { + for (i = 0; i < AAC_MAX_NSAMPS; i++) { + *outbuf = CLIPTOSHORT((m_PSInfoBase->sbrWorkBuf[ch][i] + RND_VAL) >> FBITS_OUT_IMDCT); + outbuf += m_AACDecInfo->nChans; + } + } + + m_AACDecInfo->rawSampleBuf[ch] = m_PSInfoBase->sbrWorkBuf[ch]; + m_AACDecInfo->rawSampleBytes = sizeof(int); + m_AACDecInfo->rawSampleFBits = FBITS_OUT_IMDCT; +#else + /* window, overlap-add, round to PCM - optimized for each window sequence */ + if (icsInfo->winSequence == 0) + DecWindowOverlap(m_PSInfoBase->coef[ch], m_PSInfoBase->overlap[chOut], outbuf, m_AACDecInfo->nChans, + icsInfo->winShape, m_PSInfoBase->prevWinShape[chOut]); + else if (icsInfo->winSequence == 1) + DecWindowOverlapLongStart(m_PSInfoBase->coef[ch], m_PSInfoBase->overlap[chOut], outbuf, m_AACDecInfo->nChans, + icsInfo->winShape, m_PSInfoBase->prevWinShape[chOut]); + else if (icsInfo->winSequence == 2) + DecWindowOverlapShort(m_PSInfoBase->coef[ch], m_PSInfoBase->overlap[chOut], outbuf, m_AACDecInfo->nChans, + icsInfo->winShape, m_PSInfoBase->prevWinShape[chOut]); + else if (icsInfo->winSequence == 3) + DecWindowOverlapLongStop(m_PSInfoBase->coef[ch], m_PSInfoBase->overlap[chOut], outbuf, m_AACDecInfo->nChans, + icsInfo->winShape, m_PSInfoBase->prevWinShape[chOut]); + + m_AACDecInfo->rawSampleBuf[ch] = 0; + m_AACDecInfo->rawSampleBytes = 0; + m_AACDecInfo->rawSampleFBits = 0; +#endif + + m_PSInfoBase->prevWinShape[chOut] = icsInfo->winShape; + + return 0; +} + +/*********************************************************************************************************************** + * Function: DecodeICSInfo + * + * Description: decode individual channel stream info + * + * Inputs: sample rate index + * + * Outputs: updated icsInfo struct + * + * Return: none + **********************************************************************************************************************/ +void DecodeICSInfo(ICSInfo_t *icsInfo, int sampRateIdx) +{ + int sfb, g, mask; + + icsInfo->icsResBit = GetBits(1); + icsInfo->winSequence = GetBits(2); + icsInfo->winShape = GetBits(1); + if (icsInfo->winSequence == 2) { + /* short block */ + icsInfo->maxSFB = GetBits(4); + icsInfo->sfGroup = GetBits(7); + icsInfo->numWinGroup = 1; + icsInfo->winGroupLen[0] = 1; + mask = 0x40; /* start with bit 6 */ + for (g = 0; g < 7; g++) { + if (icsInfo->sfGroup & mask) { + icsInfo->winGroupLen[icsInfo->numWinGroup - 1]++; + } else { + icsInfo->numWinGroup++; + icsInfo->winGroupLen[icsInfo->numWinGroup - 1] = 1; + } + mask >>= 1; + } + } else { + /* long block */ + icsInfo->maxSFB = GetBits(6); + icsInfo->predictorDataPresent = GetBits(1); + if (icsInfo->predictorDataPresent) { + icsInfo->predictorReset = GetBits(1); + if (icsInfo->predictorReset) + icsInfo->predictorResetGroupNum = GetBits(5); + for (sfb = 0; sfb < MIN(icsInfo->maxSFB, predSFBMax[sampRateIdx]); sfb++) + icsInfo->predictionUsed[sfb] = GetBits(1); + } + icsInfo->numWinGroup = 1; + icsInfo->winGroupLen[0] = 1; + } +} + +/*********************************************************************************************************************** + * Function: DecodeSectionData + * + * Description: decode section data (scale factor band groupings and + * associated Huffman codebooks) + * + * Inputs: window sequence (short or long blocks) + * number of window groups (1 for long blocks, 1-8 for short blocks) + * max coded scalefactor band + * + * Outputs: index of Huffman codebook for each scalefactor band in each section + * + * Return: none + * + * Notes: sectCB, sectEnd, sfbCodeBook, ordered by window groups for short blocks + **********************************************************************************************************************/ +void DecodeSectionData(int winSequence, int numWinGrp, int maxSFB, uint8_t *sfbCodeBook) +{ + int g, cb, sfb; + int sectLen, sectLenBits, sectLenIncr, sectEscapeVal; + + sectLenBits = (winSequence == 2 ? 3 : 5); + sectEscapeVal = (1 << sectLenBits) - 1; + + for (g = 0; g < numWinGrp; g++) { + sfb = 0; + while (sfb < maxSFB) { + cb = GetBits(4); /* next section codebook */ + sectLen = 0; + do { + sectLenIncr = GetBits(sectLenBits); + sectLen += sectLenIncr; + } while (sectLenIncr == sectEscapeVal); + + sfb += sectLen; + while (sectLen--) + *sfbCodeBook++ = (uint8_t)cb; + } + ASSERT(sfb == maxSFB); + } +} + +/*********************************************************************************************************************** + * Function: DecodeOneScaleFactor + * + * Description: decode one scalefactor using scalefactor Huffman codebook + * + * Inputs: none + * + * Outputs: none + * + * Return: one decoded scalefactor, including index_offset of -60 + **********************************************************************************************************************/ +int DecodeOneScaleFactor() +{ + int nBits, val; + uint32_t bitBuf; + + /* decode next scalefactor from bitstream */ + bitBuf = GetBitsNoAdvance(huffTabScaleFactInfo.maxBits) << (32 - huffTabScaleFactInfo.maxBits); + nBits = DecodeHuffmanScalar(huffTabScaleFact, &huffTabScaleFactInfo, bitBuf, &val); + AdvanceBitstream(nBits); + return val; +} + +/*********************************************************************************************************************** + * Function: DecodeScaleFactors + * + * Description: decode scalefactors, PNS energy, and intensity stereo weights + * + * Inputs: number of window groups (1 for long blocks, 1-8 for short blocks) + * max coded scalefactor band + * global gain (starting value for differential scalefactor coding) + * index of Huffman codebook for each scalefactor band in each section + * + * Outputs: decoded scalefactor for each section + * + * Return: none + * + * Notes: sfbCodeBook, scaleFactors ordered by window groups for short blocks + * for section with codebook 13, scaleFactors buffer has decoded PNS + * energy instead of regular scalefactor + * for section with codebook 14 or 15, scaleFactors buffer has intensity + * stereo weight instead of regular scalefactor + **********************************************************************************************************************/ +void DecodeScaleFactors(int numWinGrp, int maxSFB, int globalGain, + uint8_t *sfbCodeBook, short *scaleFactors) +{ + int g, sfbCB, nrg, npf, val, sf, is; + + /* starting values for differential coding */ + sf = globalGain; + is = 0; + nrg = globalGain - 90 - 256; + npf = 1; + + for (g = 0; g < numWinGrp * maxSFB; g++) { + sfbCB = *sfbCodeBook++; + + if (sfbCB == 14 || sfbCB == 15) { + /* intensity stereo - differential coding */ + val = DecodeOneScaleFactor(); + is += val; + *scaleFactors++ = (short)is; + } else if (sfbCB == 13) { + /* PNS - first energy is directly coded, rest are Huffman coded (npf = noise_pcm_flag) */ + if (npf) { + val = GetBits(9); + npf = 0; + } else { + val = DecodeOneScaleFactor(); + } + nrg += val; + *scaleFactors++ = (short)nrg; + } else if (sfbCB >= 1 && sfbCB <= 11) { + /* regular (non-zero) region - differential coding */ + val = DecodeOneScaleFactor(); + sf += val; + *scaleFactors++ = (short)sf; + } else { + /* inactive scalefactor band if codebook 0 */ + *scaleFactors++ = 0; + } + } +} + +/*********************************************************************************************************************** + * Function: DecodePulseInfo + * + * Description: decode pulse information + * + * Inputs: none + * + * Outputs: updated PulseInfo_t struct + * + * Return: none + **********************************************************************************************************************/ +void DecodePulseInfo(uint8_t ch) +{ + int i; + + m_pulseInfo[ch].numPulse = GetBits(2) + 1; /* add 1 here */ + m_pulseInfo[ch].startSFB = GetBits(6); + for (i = 0; i < m_pulseInfo[ch].numPulse; i++) { + m_pulseInfo[ch].offset[i] = GetBits(5); + m_pulseInfo[ch].amp[i] = GetBits(4); + } +} + +/*********************************************************************************************************************** + * Function: DecodeTNSInfo + * + * Description: decode TNS filter information + * + * Inputs: window sequence (short or long blocks) + * + * Outputs: updated TNSInfo_t struct + * buffer of decoded (signed) TNS filter coefficients + * + * Return: none + **********************************************************************************************************************/ +void DecodeTNSInfo(int winSequence, TNSInfo_t *ti, int8_t *tnsCoef) +{ + int i, w, f, coefBits, compress; + int8_t c, s, n; + uint8_t *filtLength, *filtOrder, *filtDir; + + filtLength = ti->length; + filtOrder = ti->order; + filtDir = ti->dir; + + if (winSequence == 2) { + /* short blocks */ + for (w = 0; w < NWINDOWS_SHORT; w++) { + ti->numFilt[w] = GetBits(1); + if (ti->numFilt[w]) { + ti->coefRes[w] = GetBits(1) + 3; + *filtLength = GetBits(4); + *filtOrder = GetBits(3); + if (*filtOrder) { + *filtDir++ = GetBits(1); + compress = GetBits(1); + coefBits = (int)ti->coefRes[w] - compress; /* 2, 3, or 4 */ + s = sgnMask[coefBits - 2]; + n = negMask[coefBits - 2]; + for (i = 0; i < *filtOrder; i++) { + c = GetBits(coefBits); + if (c & s) c |= n; + *tnsCoef++ = c; + } + } + filtLength++; + filtOrder++; + } + } + } else { + /* long blocks */ + ti->numFilt[0] = GetBits(2); + if (ti->numFilt[0]) + ti->coefRes[0] = GetBits(1) + 3; + for (f = 0; f < ti->numFilt[0]; f++) { + *filtLength = GetBits(6); + *filtOrder = GetBits(5); + if (*filtOrder) { + *filtDir++ = GetBits(1); + compress = GetBits(1); + coefBits = (int)ti->coefRes[0] - compress; /* 2, 3, or 4 */ + s = sgnMask[coefBits - 2]; + n = negMask[coefBits - 2]; + for (i = 0; i < *filtOrder; i++) { + c = GetBits(coefBits); + if (c & s) c |= n; + *tnsCoef++ = c; + } + } + filtLength++; + filtOrder++; + } + } +} + +/* bitstream field lengths for gain control data: + * gainBits[winSequence][0] = maxWindow (how many gain windows there are) + * gainBits[winSequence][1] = locBitsZero (bits for alocCode if window == 0) + * gainBits[winSequence][2] = locBits (bits for alocCode if window != 0) + */ +static const uint8_t gainBits[4][3] = { + {1, 5, 5}, /* long */ + {2, 4, 2}, /* start */ + {8, 2, 2}, /* short */ + {2, 4, 5}, /* stop */ +}; + +/*********************************************************************************************************************** + * Function: DecodeGainControlInfo + * + * Description: decode gain control information (SSR profile only) + * + * Inputs: window sequence (short or long blocks) + * + * Outputs: updated GainControlInfo_t struct + * + * Return: none + **********************************************************************************************************************/ +void DecodeGainControlInfo(int winSequence, GainControlInfo_t *gi) +{ + int bd, wd, ad; + int locBits, locBitsZero, maxWin; + + gi->maxBand = GetBits(2); + maxWin = (int)gainBits[winSequence][0]; + locBitsZero = (int)gainBits[winSequence][1]; + locBits = (int)gainBits[winSequence][2]; + + for (bd = 1; bd <= gi->maxBand; bd++) { + for (wd = 0; wd < maxWin; wd++) { + gi->adjNum[bd][wd] = GetBits(3); + for (ad = 0; ad < gi->adjNum[bd][wd]; ad++) { + gi->alevCode[bd][wd][ad] = GetBits(4); + gi->alocCode[bd][wd][ad] = GetBits(wd == 0 ? locBitsZero : locBits); + } + } + } +} + +/*********************************************************************************************************************** + * Function: DecodeICS + * + * Description: decode individual channel stream + * + * Inputs: index of current channel + * + * Outputs: updated section data, scale factor data, pulse data, TNS data, + * and gain control data + * + * Return: none + **********************************************************************************************************************/ +void DecodeICS(int ch) +{ + int globalGain; + ICSInfo_t *icsInfo; + TNSInfo_t *ti; + GainControlInfo_t *gi; + + icsInfo = (ch == 1 && m_PSInfoBase->commonWin == 1) ? &(m_PSInfoBase->icsInfo[0]) : &(m_PSInfoBase->icsInfo[ch]); + + globalGain = GetBits(8); + if (!m_PSInfoBase->commonWin) + DecodeICSInfo(icsInfo, m_PSInfoBase->sampRateIdx); + + DecodeSectionData(icsInfo->winSequence, icsInfo->numWinGroup, icsInfo->maxSFB, m_PSInfoBase->sfbCodeBook[ch]); + + DecodeScaleFactors(icsInfo->numWinGroup, icsInfo->maxSFB, globalGain, m_PSInfoBase->sfbCodeBook[ch], + m_PSInfoBase->scaleFactors[ch]); + + m_pulseInfo[ch].pulseDataPresent = GetBits(1); + if (m_pulseInfo[ch].pulseDataPresent) + DecodePulseInfo(ch); + + ti = &m_PSInfoBase->tnsInfo[ch]; + ti->tnsDataPresent = GetBits(1); + if (ti->tnsDataPresent) + DecodeTNSInfo(icsInfo->winSequence, ti, ti->coef); + + gi = &m_PSInfoBase->gainControlInfo[ch]; + gi->gainControlDataPresent = GetBits(1); + if (gi->gainControlDataPresent) + DecodeGainControlInfo(icsInfo->winSequence, gi); +} + +/*********************************************************************************************************************** + * Function: DecodeNoiselessData + * + * Description: decode noiseless data (side info and transform coefficients) + * + * Inputs: double pointer to buffer pointing to start of individual channel stream + * (14496-3, table 4.4.24) + * pointer to bit offset + * pointer to number of valid bits remaining in buf + * index of current channel + * + * Outputs: updated global gain, section data, scale factor data, pulse data, + * TNS data, gain control data, and spectral data + * + * Return: 0 if successful, error code (< 0) if error + **********************************************************************************************************************/ +int DecodeNoiselessData(uint8_t **buf, int *bitOffset, int *bitsAvail, int ch) +{ + int bitsUsed; + ICSInfo_t *icsInfo; + + icsInfo = (ch == 1 && m_PSInfoBase->commonWin == 1) ? &(m_PSInfoBase->icsInfo[0]) : &(m_PSInfoBase->icsInfo[ch]); + + SetBitstreamPointer((*bitsAvail+7) >> 3, *buf); + GetBits(*bitOffset); + + DecodeICS(ch); + + if (icsInfo->winSequence == 2) + DecodeSpectrumShort(ch); + else + DecodeSpectrumLong(ch); + + bitsUsed = CalcBitsUsed(*buf, *bitOffset); + *buf += ((bitsUsed + *bitOffset) >> 3); + *bitOffset = ((bitsUsed + *bitOffset) & 0x07); + *bitsAvail -= bitsUsed; + + m_AACDecInfo->sbDeinterleaveReqd[ch] = 0; + m_AACDecInfo->tnsUsed |= m_PSInfoBase->tnsInfo[ch].tnsDataPresent; /* set flag if TNS used for any channel */ + + return ERR_AAC_NONE; +} +/*********************************************************************************************************************** + * Function: DecodeHuffmanScalar + * + * Description: decode one Huffman symbol from bitstream + * + * Inputs: pointers to Huffman table and info struct + * left-aligned bit buffer with >= huffTabInfo->maxBits bits + * + * Outputs: decoded symbol in *val + * + * Return: number of bits in symbol + * + * Notes: assumes canonical Huffman codes: + * first CW always 0, we have "count" CW's of length "nBits" bits + * starting CW for codes of length nBits+1 = + * (startCW[nBits] + count[nBits]) << 1 + * if there are no codes at nBits, then we just keep << 1 each time + * (since count[nBits] = 0) + **********************************************************************************************************************/ +int DecodeHuffmanScalar(const signed short *huffTab, const HuffInfo_t *huffTabInfo, uint32_t bitBuf, int32_t *val) +{ + uint32_t count, start, shift, t; + const uint8_t *countPtr; + const signed short *map; + + map = huffTab + huffTabInfo->offset; + countPtr = huffTabInfo->count; + + start = 0; + count = 0; + shift = 32; + do { + start += count; + start <<= 1; + map += count; + count = *countPtr++; + shift--; + t = (bitBuf >> shift) - start; + } while (t >= count); + + *val = (int32_t)map[t]; + return (countPtr - huffTabInfo->count); +} + +/*********************************************************************************************************************** +* Function: UnpackADTSHeader +* +* Description: parse the ADTS frame header and initialize decoder state, Audio Data Transport Stream +* +* Inputs: double pointer to buffer with complete ADTS frame header (byte aligned) +* header size = 7 bytes, plus 2 if CRC +* +* Outputs: filled in ADTS struct +* updated buffer pointer +* updated bit offset +* updated number of available bits +* +* Return: 0 if successful, error code (< 0) if error +* verify that fixed fields don't change between frames +***********************************************************************************************************************/ +int UnpackADTSHeader(uint8_t **buf, int *bitOffset, int *bitsAvail) +{ + int bitsUsed; + + /* init bitstream reader */ + SetBitstreamPointer((*bitsAvail + 7) >> 3, *buf); + GetBits(*bitOffset); + + /* verify that first 12 bits of header are syncword */ + if (GetBits(12) != 0x0fff) { + return ERR_AAC_INVALID_ADTS_HEADER; + } + + /* fixed fields - should not change from frame to frame */ + m_fhADTS.id = GetBits(1); + m_fhADTS.layer = GetBits(2); + m_fhADTS.protectBit = GetBits(1); + m_fhADTS.profile = GetBits(2); + m_fhADTS.sampRateIdx = GetBits(4); + m_fhADTS.privateBit = GetBits(1); + m_fhADTS.channelConfig = GetBits(3); + m_fhADTS.origCopy = GetBits(1); + m_fhADTS.home = GetBits(1); + + /* variable fields - can change from frame to frame */ + m_fhADTS.copyBit = GetBits(1); + m_fhADTS.copyStart = GetBits(1); + m_fhADTS.frameLength = GetBits(13); + m_fhADTS.bufferFull = GetBits(11); + m_fhADTS.numRawDataBlocks = GetBits(2) + 1; + + /* note - MPEG4 spec, correction 1 changes how CRC is handled when protectBit == 0 and numRawDataBlocks > 1 */ + if (m_fhADTS.protectBit == 0) + m_fhADTS.crcCheckWord = GetBits(16); + + /* byte align */ + ByteAlignBitstream(); /* should always be aligned anyway */ + + /* check validity of header */ + if (m_fhADTS.layer != 0 || m_fhADTS.profile != AAC_PROFILE_LC || + m_fhADTS.sampRateIdx >= NUM_SAMPLE_RATES || m_fhADTS.channelConfig >= NUM_DEF_CHAN_MAPS) + return ERR_AAC_INVALID_ADTS_HEADER; + +#ifndef AAC_ENABLE_MPEG4 + if (m_fhADTS.id != 1) + return ERR_AAC_MPEG4_UNSUPPORTED; +#endif + + + /* update codec info */ + m_PSInfoBase->sampRateIdx = m_fhADTS.sampRateIdx; + if (!m_PSInfoBase->useImpChanMap) + m_PSInfoBase->nChans = channelMapTab[m_fhADTS.channelConfig]; + + /* syntactic element fields will be read from bitstream for each element */ + m_AACDecInfo->prevBlockID = AAC_ID_INVALID; + m_AACDecInfo->currBlockID = AAC_ID_INVALID; + m_AACDecInfo->currInstTag = -1; + + /* fill in user-accessible data */ + m_AACDecInfo->bitRate = 0; + m_AACDecInfo->nChans = m_PSInfoBase->nChans; + m_AACDecInfo->sampRate = sampRateTab[m_PSInfoBase->sampRateIdx]; + m_AACDecInfo->id = m_fhADTS.id; + m_AACDecInfo->profile = m_fhADTS.profile; + m_AACDecInfo->sbrEnabled = 0; + m_AACDecInfo->adtsBlocksLeft = m_fhADTS.numRawDataBlocks; + + /* update bitstream reader */ + bitsUsed = CalcBitsUsed(*buf, *bitOffset); + *buf += (bitsUsed + *bitOffset) >> 3; + *bitOffset = (bitsUsed + *bitOffset) & 0x07; + *bitsAvail -= bitsUsed ; + if (*bitsAvail < 0) + return ERR_AAC_INDATA_UNDERFLOW; + + return ERR_AAC_NONE; +} + +/*********************************************************************************************************************** +* Function: GetADTSChannelMapping +* +* Description: determine the number of channels from implicit mapping rules +* +* Inputs: pointer to start of raw_data_block +* bit offset +* bits available +* +* Outputs: updated number of channels +* +* Return: 0 if successful, error code (< 0) if error +* +* Notes: calculates total number of channels using rules in 14496-3, 4.5.1.2.1 +* does not attempt to deduce speaker geometry +***********************************************************************************************************************/ +int GetADTSChannelMapping(uint8_t *buf, int bitOffset, int bitsAvail) +{ + int ch, nChans, elementChans, err; + + nChans = 0; + do { + /* parse next syntactic element */ + err = DecodeNextElement(&buf, &bitOffset, &bitsAvail); + if (err) + return err; + + elementChans = elementNumChans[m_AACDecInfo->currBlockID]; + nChans += elementChans; + + for (ch = 0; ch < elementChans; ch++) { + err = DecodeNoiselessData(&buf, &bitOffset, &bitsAvail, ch); + if (err) + return err; + } + } while (m_AACDecInfo->currBlockID != AAC_ID_END); + + if (nChans <= 0) + return ERR_AAC_CHANNEL_MAP; + + /* update number of channels in codec state and user-accessible info structs */ + m_PSInfoBase->nChans = nChans; + m_AACDecInfo->nChans = m_PSInfoBase->nChans; + m_PSInfoBase->useImpChanMap = 1; + + return ERR_AAC_NONE; +} + +/*********************************************************************************************************************** +* Function: GetNumChannelsADIF +* +* Description: get number of channels from program config elements in an ADIF file +* +* Inputs: array of filled-in program config element structures +* number of PCE's +* +* Outputs: none +* +* Return: total number of channels in file +* -1 if error (invalid number of PCE's or unsupported mode) +***********************************************************************************************************************/ +int GetNumChannelsADIF(int nPCE) +{ + int i, j, nChans; + + if (nPCE < 1 || nPCE > MAX_NUM_PCE_ADIF) + return -1; + + nChans = 0; + for (i = 0; i < nPCE; i++) { + /* for now: only support LC, no channel coupling */ + if (m_pce[i]->profile != AAC_PROFILE_LC || m_pce[i]->numCCE > 0) + return -1; + + /* add up number of channels in all channel elements (assume all single-channel) */ + nChans += m_pce[i]->numFCE; + nChans += m_pce[i]->numSCE; + nChans += m_pce[i]->numBCE; + nChans += m_pce[i]->numLCE; + + /* add one more for every element which is a channel pair */ + for (j = 0; j < m_pce[i]->numFCE; j++) { + if ((m_pce[i]->fce[j] & 0x10) >> 4) /* bit 4 = SCE/CPE flag */ + nChans++; + } + for (j = 0; j < m_pce[i]->numSCE; j++) { + if ((m_pce[i]->sce[j] & 0x10) >> 4) /* bit 4 = SCE/CPE flag */ + nChans++; + } + for (j = 0; j < m_pce[i]->numBCE; j++) { + if ((m_pce[i]->bce[j] & 0x10) >> 4) /* bit 4 = SCE/CPE flag */ + nChans++; + } + + } + + return nChans; +} + +/*********************************************************************************************************************** +* Function: GetSampleRateIdxADIF +* +* Description: get sampling rate index from program config elements in an ADIF file +* +* Inputs: array of filled-in program config element structures +* number of PCE's +* +* Outputs: none +* +* Return: sample rate of file +* -1 if error (invalid number of PCE's or sample rate mismatch) +***********************************************************************************************************************/ +int GetSampleRateIdxADIF(int nPCE) +{ + int i, idx; + + if (nPCE < 1 || nPCE > MAX_NUM_PCE_ADIF) + return -1; + + /* make sure all PCE's have the same sample rate */ + idx = m_pce[0]->sampRateIdx; + for (i = 1; i < nPCE; i++) { + if (m_pce[i]->sampRateIdx != idx) + return -1; + } + + return idx; +} + +/*********************************************************************************************************************** +* Function: UnpackADIFHeader +* +* Description: parse the ADIF file header and initialize decoder state +* +* Inputs: double pointer to buffer with complete ADIF header +* (starting at 'A' in 'ADIF' tag) +* pointer to bit offset +* pointer to number of valid bits remaining in inbuf +* +* Outputs: filled-in ADIF struct +* updated buffer pointer +* updated bit offset +* updated number of available bits +* +* Return: 0 if successful, error code (< 0) if error +***********************************************************************************************************************/ +int UnpackADIFHeader(uint8_t **buf, int *bitOffset, int *bitsAvail) +{ + uint8_t i; + int bitsUsed; + + /* init bitstream reader */ + SetBitstreamPointer((*bitsAvail + 7) >> 3, *buf); + GetBits(*bitOffset); + + /* verify that first 32 bits of header are "ADIF" */ + if (GetBits(8) != 'A' || GetBits(8) != 'D' || GetBits(8) != 'I' || GetBits(8) != 'F') + return ERR_AAC_INVALID_ADIF_HEADER; + + /* read ADIF header fields */ + m_fhADIF.copyBit = GetBits(1); + if (m_fhADIF.copyBit) { + for (i = 0; i < ADIF_COPYID_SIZE; i++) + m_fhADIF.copyID[i] = GetBits(8); + } + m_fhADIF.origCopy = GetBits(1); + m_fhADIF.home = GetBits(1); + m_fhADIF.bsType = GetBits(1); + m_fhADIF.bitRate = GetBits(23); + m_fhADIF.numPCE = GetBits(4) + 1; /* add 1 (so range = [1, 16]) */ + if (m_fhADIF.bsType == 0) + m_fhADIF.bufferFull = GetBits(20); + + /* parse all program config elements */ + for (i = 0; i < m_fhADIF.numPCE; i++) + DecodeProgramConfigElement(i); + + /* byte align */ + ByteAlignBitstream(); + + /* update codec info */ + m_PSInfoBase->nChans = GetNumChannelsADIF(m_fhADIF.numPCE); + m_PSInfoBase->sampRateIdx = GetSampleRateIdxADIF(m_fhADIF.numPCE); + + /* check validity of header */ + if (m_PSInfoBase->nChans < 0 || m_PSInfoBase->sampRateIdx < 0 || m_PSInfoBase->sampRateIdx >= NUM_SAMPLE_RATES) + return ERR_AAC_INVALID_ADIF_HEADER; + + /* syntactic element fields will be read from bitstream for each element */ + m_AACDecInfo->prevBlockID = AAC_ID_INVALID; + m_AACDecInfo->currBlockID = AAC_ID_INVALID; + m_AACDecInfo->currInstTag = -1; + + /* fill in user-accessible data */ + m_AACDecInfo->bitRate = 0; + m_AACDecInfo->nChans = m_PSInfoBase->nChans; + m_AACDecInfo->sampRate = sampRateTab[m_PSInfoBase->sampRateIdx]; + m_AACDecInfo->profile = m_pce[0]->profile; + m_AACDecInfo->sbrEnabled = 0; + + /* update bitstream reader */ + bitsUsed = CalcBitsUsed(*buf, *bitOffset); + *buf += (bitsUsed + *bitOffset) >> 3; + *bitOffset = (bitsUsed + *bitOffset) & 0x07; + *bitsAvail -= bitsUsed ; + if (*bitsAvail < 0) + return ERR_AAC_INDATA_UNDERFLOW; + + return ERR_AAC_NONE; +} + +/*********************************************************************************************************************** +* Function: SetRawBlockParams +* +* Description: set internal state variables for decoding a stream of raw data blocks +* +* Inputs: flag indicating source of parameters (from previous headers or passed +* explicitly by caller) +* number of channels +* sample rate +* profile ID +* +* Outputs: updated state variables in aacDecInfo +* +* Return: 0 if successful, error code (< 0) if error +* +* Notes: if copyLast == 1, then m_PSInfoBase->nChans, m_PSInfoBase->sampRateIdx, and +* aacDecInfo->profile are not changed (it's assumed that we already +* set them, such as by a previous call to UnpackADTSHeader()) +* if copyLast == 0, then the parameters we passed in are used instead +***********************************************************************************************************************/ +int SetRawBlockParams(int copyLast, int nChans, int sampRate, int profile) +{ + int idx; + + if (!copyLast) { + m_AACDecInfo->profile = profile; + m_PSInfoBase->nChans = nChans; + for (idx = 0; idx < NUM_SAMPLE_RATES; idx++) { + if (sampRate == sampRateTab[idx]) { + m_PSInfoBase->sampRateIdx = idx; + break; + } + } + if (idx == NUM_SAMPLE_RATES) + return ERR_AAC_INVALID_FRAME; + } + m_AACDecInfo->nChans = m_PSInfoBase->nChans; + m_AACDecInfo->sampRate = sampRateTab[m_PSInfoBase->sampRateIdx]; + + /* check validity of header */ + if (m_PSInfoBase->sampRateIdx >= NUM_SAMPLE_RATES || m_PSInfoBase->sampRateIdx < 0 || + m_AACDecInfo->profile != AAC_PROFILE_LC) + return ERR_AAC_RAWBLOCK_PARAMS; + + return ERR_AAC_NONE; +} + +/*********************************************************************************************************************** +* Function: PrepareRawBlock +* +* Description: reset per-block state variables for raw blocks (no ADTS/ADIF headers) +* +* Inputs: none +* +* Outputs: updated state variables in aacDecInfo +* +* Return: 0 if successful, error code (< 0) if error +***********************************************************************************************************************/ +int PrepareRawBlock() +{ + /* syntactic element fields will be read from bitstream for each element */ + m_AACDecInfo->prevBlockID = AAC_ID_INVALID; + m_AACDecInfo->currBlockID = AAC_ID_INVALID; + m_AACDecInfo->currInstTag = -1; + + /* fill in user-accessible data */ + m_AACDecInfo->bitRate = 0; + m_AACDecInfo->sbrEnabled = 0; + + return ERR_AAC_NONE; +} + +/*********************************************************************************************************************** + * Function: DequantBlock + * + * Description: dequantize one block of transform coefficients (in-place) + * + * Inputs: quantized transform coefficients, range = [0, 8191] + * number of samples to dequantize + * scalefactor for this block of data, range = [0, 256] + * + * Outputs: dequantized transform coefficients in Q(FBITS_OUT_DQ_OFF) + * + * Return: guard bit mask (OR of abs value of all dequantized coefs) + * + * Notes: applies dequant formula y = pow(x, 4.0/3.0) * pow(2, (scale - 100)/4.0) + * * pow(2, FBITS_OUT_DQ_OFF) + * clips outputs to Q(FBITS_OUT_DQ_OFF) + * output has no minimum number of guard bits + **********************************************************************************************************************/ +int DequantBlock(int *inbuf, int nSamps, int scale) +{ + int iSamp, scalef, scalei, x, y, gbMask, shift, tab4[4]; + const uint32_t *tab16, *coef; + + if (nSamps <= 0) + return 0; + + scale -= SF_OFFSET; /* new range = [-100, 156] */ + + /* with two's complement numbers, scalei/scalef factorization works for pos and neg values of scale: + * [+4...+7] >> 2 = +1, [ 0...+3] >> 2 = 0, [-4...-1] >> 2 = -1, [-8...-5] >> 2 = -2 ... + * (-1 & 0x3) = 3, (-2 & 0x3) = 2, (-3 & 0x3) = 1, (0 & 0x3) = 0 + * + * Example: 2^(-5/4) = 2^(-1) * 2^(-1/4) = 2^-2 * 2^(3/4) + */ + tab16 = pow43_14[scale & 0x3]; + scalef = pow14[scale & 0x3]; + scalei = (scale >> 2) + FBITS_OUT_DQ_OFF; + + /* cache first 4 values: + * tab16[j] = Q28 for j = [0,3] + * tab4[x] = x^(4.0/3.0) * 2^(0.25*scale), Q(FBITS_OUT_DQ_OFF) + */ + shift = 28 - scalei; + if (shift > 31) { + tab4[0] = tab4[1] = tab4[2] = tab4[3] = 0; + } else if (shift <= 0) { + shift = -shift; + if (shift > 31) + shift = 31; + for (x = 0; x < 4; x++) { + y = tab16[x]; + if (y > (0x7fffffff >> shift)) + y = 0x7fffffff; /* clip (rare) */ + else + y <<= shift; + tab4[x] = y; + } + } else { + tab4[0] = 0; + tab4[1] = tab16[1] >> shift; + tab4[2] = tab16[2] >> shift; + tab4[3] = tab16[3] >> shift; + } + + gbMask = 0; + do { + iSamp = *inbuf; + x = FASTABS(iSamp); + + if (x < 4) { + y = tab4[x]; + } else { + + if (x < 16) { + /* result: y = Q25 (tab16 = Q25) */ + y = tab16[x]; + shift = 25 - scalei; + } else if (x < 64) { + /* result: y = Q21 (pow43tab[j] = Q23, scalef = Q30) */ + y = pow43[x-16]; + shift = 21 - scalei; + y = MULSHIFT32(y, scalef); + } else { + /* normalize to [0x40000000, 0x7fffffff] + * input x = [64, 8191] = [64, 2^13-1] + * ranges: + * shift = 7: 64 - 127 + * shift = 6: 128 - 255 + * shift = 5: 256 - 511 + * shift = 4: 512 - 1023 + * shift = 3: 1024 - 2047 + * shift = 2: 2048 - 4095 + * shift = 1: 4096 - 8191 + */ + x <<= 17; + shift = 0; + if (x < 0x08000000) + x <<= 4, shift += 4; + if (x < 0x20000000) + x <<= 2, shift += 2; + if (x < 0x40000000) + x <<= 1, shift += 1; + + coef = (x < SQRTHALF) ? poly43lo : poly43hi; + + /* polynomial */ + y = coef[0]; + y = MULSHIFT32(y, x) + coef[1]; + y = MULSHIFT32(y, x) + coef[2]; + y = MULSHIFT32(y, x) + coef[3]; + y = MULSHIFT32(y, x) + coef[4]; + y = MULSHIFT32(y, pow2frac[shift]) << 3; + + /* fractional scale + * result: y = Q21 (pow43tab[j] = Q23, scalef = Q30) + */ + y = MULSHIFT32(y, scalef); /* now y is Q24 */ + shift = 24 - scalei - pow2exp[shift]; + } + + /* integer scale */ + if (shift <= 0) { + shift = -shift; + if (shift > 31) + shift = 31; + + if (y > (0x7fffffff >> shift)) + y = 0x7fffffff; /* clip (rare) */ + else + y <<= shift; + } else { + if (shift > 31) + shift = 31; + y >>= shift; + } + } + + /* sign and store (gbMask used to count GB's) */ + gbMask |= y; + + /* apply sign */ + iSamp >>= 31; + y ^= iSamp; + y -= iSamp; + + *inbuf++ = y; + } while (--nSamps); + + return gbMask; +} + +/*********************************************************************************************************************** + * Function: AACDequantize + * + * Description: dequantize all transform coefficients for one channel + * + * Inputs: index of current channel + * + * Outputs: dequantized coefficients, including short-block deinterleaving + * flags indicating if intensity and/or PNS is active + * minimum guard bit count for dequantized coefficients + * + * Return: 0 if successful, error code (< 0) if error + **********************************************************************************************************************/ +int AACDequantize(int ch) +{ + int gp, cb, sfb, win, width, nSamps, gbMask; + int *coef; + const uint16_t *sfbTab; + uint8_t *sfbCodeBook; + short *scaleFactors; + ICSInfo_t *icsInfo; + + icsInfo = (ch == 1 && m_PSInfoBase->commonWin == 1) ? &(m_PSInfoBase->icsInfo[0]) : &(m_PSInfoBase->icsInfo[ch]); + + if (icsInfo->winSequence == 2) { + sfbTab = sfBandTabShort + sfBandTabShortOffset[m_PSInfoBase->sampRateIdx]; + nSamps = NSAMPS_SHORT; + } else { + sfbTab = sfBandTabLong + sfBandTabLongOffset[m_PSInfoBase->sampRateIdx]; + nSamps = NSAMPS_LONG; + } + coef = m_PSInfoBase->coef[ch]; + sfbCodeBook = m_PSInfoBase->sfbCodeBook[ch]; + scaleFactors = m_PSInfoBase->scaleFactors[ch]; + + m_PSInfoBase->intensityUsed[ch] = 0; + m_PSInfoBase->pnsUsed[ch] = 0; + gbMask = 0; + for (gp = 0; gp < icsInfo->numWinGroup; gp++) { + for (win = 0; win < icsInfo->winGroupLen[gp]; win++) { + for (sfb = 0; sfb < icsInfo->maxSFB; sfb++) { + /* dequantize one scalefactor band (not necessary if codebook is intensity or PNS) + * for zero codebook, still run dequantizer in case non-zero pulse data was added + */ + cb = (int)(sfbCodeBook[sfb]); + width = sfbTab[sfb+1] - sfbTab[sfb]; + if (cb >= 0 && cb <= 11) + gbMask |= DequantBlock(coef, width, scaleFactors[sfb]); + else if (cb == 13) + m_PSInfoBase->pnsUsed[ch] = 1; + else if (cb == 14 || cb == 15) + m_PSInfoBase->intensityUsed[ch] = 1; /* should only happen if ch == 1 */ + coef += width; + } + coef += (nSamps - sfbTab[icsInfo->maxSFB]); + } + sfbCodeBook += icsInfo->maxSFB; + scaleFactors += icsInfo->maxSFB; + } + m_AACDecInfo->pnsUsed |= m_PSInfoBase->pnsUsed[ch]; /* set flag if PNS used for any channel */ + + /* calculate number of guard bits in dequantized data */ + m_PSInfoBase->gbCurrent[ch] = CLZ(gbMask) - 1; + + return ERR_AAC_NONE; +} + +/*********************************************************************************************************************** + * Function: DeinterleaveShortBlocks + * + * Description: deinterleave transform coefficients in short blocks for one channel + * + * Inputs: index of current channel + * + * Outputs: deinterleaved coefficients (window groups into 8 separate windows) + * + * Return: 0 if successful, error code (< 0) if error + * + * Notes: only necessary if deinterleaving not part of Huffman decoding + **********************************************************************************************************************/ +int DeinterleaveShortBlocks(int ch) +{ +// (void)aacDecInfo; +// (void)ch; + /* not used for this implementation - short block deinterleaving performed during Huffman decoding */ + return ERR_AAC_NONE; +} + +/*********************************************************************************************************************** + * Function: Get32BitVal + * + * Description: generate 32-bit unsigned random number + * + * Inputs: last number calculated (seed, first time through) + * + * Outputs: new number, saved in *last + * + * Return: 32-bit number, uniformly distributed between [0, 2^32) + * + * Notes: uses simple linear congruential generator + **********************************************************************************************************************/ +unsigned int Get32BitVal(unsigned int *last) +{ + uint32_t r = *last; + + /* use same coefs as MPEG reference code (classic LCG) + * use unsigned multiply to force reliable wraparound behavior in C (mod 2^32) + */ + r = (1664525U * r) + 1013904223U; + *last = r; + + return r; +} + +/*********************************************************************************************************************** + * Function: InvRootR + * + * Description: use Newton's method to solve for x = 1/sqrt(r) + * + * Inputs: r in Q30 format, range = [0.25, 1] (normalize inputs to this range) + * + * Outputs: none + * + * Return: x = Q29, range = (1, 2) + * + * Notes: guaranteed to converge and not overflow for any r in this range + * + * xn+1 = xn - f(xn)/f'(xn) + * f(x) = 1/sqrt(r) - x = 0 (find root) + * = 1/x^2 - r + * f'(x) = -2/x^3 + * + * so xn+1 = xn/2 * (3 - r*xn^2) + * + * NUM_ITER_INVSQRT = 3, maxDiff = 1.3747e-02 + * NUM_ITER_INVSQRT = 4, maxDiff = 3.9832e-04 + **********************************************************************************************************************/ +int InvRootR(int r) +{ + int i, xn, t; + + /* use linear equation for initial guess + * x0 = -2*r + 3 (so x0 always >= correct answer in range [0.25, 1)) + * xn = Q29 (at every step) + */ + xn = (MULSHIFT32(r, X0_COEF_2) << 2) + X0_OFF_2; + + for (i = 0; i < NUM_ITER_INVSQRT; i++) { + t = MULSHIFT32(xn, xn); /* Q26 = Q29*Q29 */ + t = Q26_3 - (MULSHIFT32(r, t) << 2); /* Q26 = Q26 - (Q31*Q26 << 1) */ + xn = MULSHIFT32(xn, t) << (6 - 1); /* Q29 = (Q29*Q26 << 6), and -1 for division by 2 */ + } + + /* clip to range (1.0, 2.0) + * (because of rounding, this can converge to xn slightly > 2.0 when r is near 0.25) + */ + if (xn >> 30) + xn = (1 << 30) - 1; + + return xn; +} + +/*********************************************************************************************************************** + * Function: ScaleNoiseVector + * + * Description: apply scaling to vector of noise coefficients for one scalefactor band + * + * Inputs: unscaled coefficients + * number of coefficients in vector (one scalefactor band of coefs) + * scalefactor for this band (i.e. noise energy) + * + * Outputs: nVals coefficients in Q(FBITS_OUT_DQ_OFF) + * + * Return: guard bit mask (OR of abs value of all noise coefs) + **********************************************************************************************************************/ +int ScaleNoiseVector(int *coef, int nVals, int sf) +{ + +/* pow(2, i/4.0) for i = [0,1,2,3], format = Q30 */ +static const int pow14[4] PROGMEM = { + 0x40000000, 0x4c1bf829, 0x5a82799a, 0x6ba27e65 +}; + + int i, c, spec, energy, sq, scalef, scalei, invSqrtEnergy, z, gbMask; + + energy = 0; + for (i = 0; i < nVals; i++) { + spec = coef[i]; + + /* max nVals = max SFB width = 96, so energy can gain < 2^7 bits in accumulation */ + sq = (spec * spec) >> 8; /* spec*spec range = (-2^30, 2^30) */ + energy += sq; + } + + /* unless nVals == 1 (or the number generator is broken...), this should not happen */ + if (energy == 0) + return 0; /* coef[i] must = 0 for i = [0, nVals-1], so gbMask = 0 */ + + /* pow(2, sf/4) * pow(2, FBITS_OUT_DQ_OFF) */ + scalef = pow14[sf & 0x3]; + scalei = (sf >> 2) + FBITS_OUT_DQ_OFF; + + /* energy has implied factor of 2^-8 since we shifted the accumulator + * normalize energy to range [0.25, 1.0), calculate 1/sqrt(1), and denormalize + * i.e. divide input by 2^(30-z) and convert to Q30 + * output of 1/sqrt(i) now has extra factor of 2^((30-z)/2) + * for energy > 0, z is an even number between 0 and 28 + * final scaling of invSqrtEnergy: + * 2^(15 - z/2) to compensate for implicit 2^(30-z) factor in input + * +4 to compensate for implicit 2^-8 factor in input + */ + z = CLZ(energy) - 2; /* energy has at least 2 leading zeros (see acc loop) */ + z &= 0xfffffffe; /* force even */ + invSqrtEnergy = InvRootR(energy << z); /* energy << z must be in range [0x10000000, 0x40000000] */ + scalei -= (15 - z/2 + 4); /* nInt = 1/sqrt(energy) in Q29 */ + + /* normalize for final scaling */ + z = CLZ(invSqrtEnergy) - 1; + invSqrtEnergy <<= z; + scalei -= (z - 3 - 2); /* -2 for scalef, z-3 for invSqrtEnergy */ + scalef = MULSHIFT32(scalef, invSqrtEnergy); /* scalef (input) = Q30, invSqrtEnergy = Q29 * 2^z */ + gbMask = 0; + + if (scalei < 0) { + scalei = -scalei; + if (scalei > 31) + scalei = 31; + for (i = 0; i < nVals; i++) { + c = MULSHIFT32(coef[i], scalef) >> scalei; + gbMask |= FASTABS(c); + coef[i] = c; + } + } else { + /* for scalei <= 16, no clipping possible (coef[i] is < 2^15 before scaling) + * for scalei > 16, just saturate exponent (rare) + * scalef is close to full-scale (since we normalized invSqrtEnergy) + * remember, we are just producing noise here + */ + if (scalei > 16) + scalei = 16; + for (i = 0; i < nVals; i++) { + c = MULSHIFT32(coef[i] << scalei, scalef); + coef[i] = c; + gbMask |= FASTABS(c); + } + } + + return gbMask; +} + +/*********************************************************************************************************************** + * Function: GenerateNoiseVector + * + * Description: create vector of noise coefficients for one scalefactor band + * + * Inputs: seed for number generator + * number of coefficients to generate + * + * Outputs: buffer of nVals coefficients, range = [-2^15, 2^15) + * updated seed for number generator + * + * Return: none + **********************************************************************************************************************/ +void GenerateNoiseVector(int *coef, int *last, int nVals) +{ + int i; + + for (i = 0; i < nVals; i++) + coef[i] = ((int32_t)Get32BitVal((uint32_t *)last)) >> 16; +} + +/*********************************************************************************************************************** + * Function: CopyNoiseVector + * + * Description: copy vector of noise coefficients for one scalefactor band from L to R + * + * Inputs: buffer of left coefficients + * number of coefficients to copy + * + * Outputs: buffer of right coefficients + * + * Return: none + **********************************************************************************************************************/ +void CopyNoiseVector(int *coefL, int *coefR, int nVals) +{ + int i; + + for (i = 0; i < nVals; i++) + coefR[i] = coefL[i]; +} + +/*********************************************************************************************************************** + * Function: PNS + * + * Description: apply perceptual noise substitution, if enabled (MPEG-4 only) + * + * Inputs: index of current channel + * + * Outputs: shaped noise in scalefactor bands where PNS is active + * updated minimum guard bit count for this channel + * + * Return: 0 if successful, -1 if error + **********************************************************************************************************************/ +int PNS(int ch) +{ + int gp, sfb, win, width, nSamps, gb, gbMask; + int *coef; + const uint16_t *sfbTab; + uint8_t *sfbCodeBook; + short *scaleFactors; + int msMaskOffset, checkCorr, genNew; + uint8_t msMask; + uint8_t *msMaskPtr; + ICSInfo_t *icsInfo; + + icsInfo = (ch == 1 && m_PSInfoBase->commonWin == 1) ? &(m_PSInfoBase->icsInfo[0]) : &(m_PSInfoBase->icsInfo[ch]); + + if (!m_PSInfoBase->pnsUsed[ch]) + return 0; + + if (icsInfo->winSequence == 2) { + sfbTab = sfBandTabShort + sfBandTabShortOffset[m_PSInfoBase->sampRateIdx]; + nSamps = NSAMPS_SHORT; + } else { + sfbTab = sfBandTabLong + sfBandTabLongOffset[m_PSInfoBase->sampRateIdx]; + nSamps = NSAMPS_LONG; + } + coef = m_PSInfoBase->coef[ch]; + sfbCodeBook = m_PSInfoBase->sfbCodeBook[ch]; + scaleFactors = m_PSInfoBase->scaleFactors[ch]; + checkCorr = (m_AACDecInfo->currBlockID == AAC_ID_CPE && m_PSInfoBase->commonWin == 1 ? 1 : 0); + + gbMask = 0; + for (gp = 0; gp < icsInfo->numWinGroup; gp++) { + for (win = 0; win < icsInfo->winGroupLen[gp]; win++) { + msMaskPtr = m_PSInfoBase->msMaskBits + ((gp*icsInfo->maxSFB) >> 3); + msMaskOffset = ((gp*icsInfo->maxSFB) & 0x07); + msMask = (*msMaskPtr++) >> msMaskOffset; + + for (sfb = 0; sfb < icsInfo->maxSFB; sfb++) { + width = sfbTab[sfb+1] - sfbTab[sfb]; + if (sfbCodeBook[sfb] == 13) { + if (ch == 0) { + /* generate new vector, copy into ch 1 if it's possible that the channels will be correlated + * if ch 1 has PNS enabled for this SFB but it's uncorrelated (i.e. ms_used == 0), + * the copied values will be overwritten when we process ch 1 + */ + GenerateNoiseVector(coef, &m_PSInfoBase->pnsLastVal, width); + if (checkCorr && m_PSInfoBase->sfbCodeBook[1][gp*icsInfo->maxSFB + sfb] == 13) + CopyNoiseVector(coef, m_PSInfoBase->coef[1] + (coef - m_PSInfoBase->coef[0]), width); + } else { + /* generate new vector if no correlation between channels */ + genNew = 1; + if (checkCorr && m_PSInfoBase->sfbCodeBook[0][gp*icsInfo->maxSFB + sfb] == 13) { + if((m_PSInfoBase->msMaskPresent==1 && (msMask & 0x01)) || m_PSInfoBase->msMaskPresent == 2 ) + genNew = 0; + } + if (genNew) + GenerateNoiseVector(coef, &m_PSInfoBase->pnsLastVal, width); + } + gbMask |= ScaleNoiseVector(coef, width, m_PSInfoBase->scaleFactors[ch][gp*icsInfo->maxSFB + sfb]); + } + coef += width; + + /* get next mask bit (should be branchless on ARM) */ + msMask >>= 1; + if (++msMaskOffset == 8) { + msMask = *msMaskPtr++; + msMaskOffset = 0; + } + } + coef += (nSamps - sfbTab[icsInfo->maxSFB]); + } + sfbCodeBook += icsInfo->maxSFB; + scaleFactors += icsInfo->maxSFB; + } + + /* update guard bit count if necessary */ + gb = CLZ(gbMask) - 1; + if (m_PSInfoBase->gbCurrent[ch] > gb) + m_PSInfoBase->gbCurrent[ch] = gb; + + return 0; +} + +/*********************************************************************************************************************** + * Function: GetSampRateIdx + * + * Description: get index of given sample rate + * + * Inputs: sample rate (in Hz) + * + * Outputs: none + * + * Return: index of sample rate (table 1.15 in 14496-3:2001(E)) + * -1 if sample rate not found in table + **********************************************************************************************************************/ +int GetSampRateIdx(int sampRate) +{ + int idx; + + for (idx = 0; idx < NUM_SAMPLE_RATES; idx++) { + if (sampRate == sampRateTab[idx]) + return idx; + } + + return -1; +} + +/*********************************************************************************************************************** + * Function: StereoProcessGroup + * + * Description: apply mid-side and intensity stereo to group of transform coefficients + * + * Inputs: dequantized transform coefficients for both channels + * pointer to appropriate scalefactor band table + * mid-side mask enabled flag + * buffer with mid-side mask (one bit for each scalefactor band) + * bit offset into mid-side mask buffer + * max coded scalefactor band + * buffer of codebook indices for right channel + * buffer of scalefactors for right channel, range = [0, 256] + * + * Outputs: updated transform coefficients in Q(FBITS_OUT_DQ_OFF) + * updated minimum guard bit count for both channels + * + * Return: none + * + * Notes: assume no guard bits in input + * gains 0 int bits + **********************************************************************************************************************/ +void StereoProcessGroup(int *coefL, int *coefR, const uint16_t *sfbTab, + int msMaskPres, uint8_t *msMaskPtr, int msMaskOffset, int maxSFB, + uint8_t *cbRight, short *sfRight, int *gbCurrent) +{ +//fb +static const uint32_t pow14[2][4] PROGMEM = { + { 0xc0000000, 0xb3e407d7, 0xa57d8666, 0x945d819b }, + { 0x40000000, 0x4c1bf829, 0x5a82799a, 0x6ba27e65 } +}; + + int sfb, width, cbIdx, sf, cl, cr, scalef, scalei; + int gbMaskL, gbMaskR; + uint8_t msMask; + + msMask = (*msMaskPtr++) >> msMaskOffset; + gbMaskL = 0; + gbMaskR = 0; + + for (sfb = 0; sfb < maxSFB; sfb++) { + width = sfbTab[sfb+1] - sfbTab[sfb]; /* assume >= 0 (see sfBandTabLong/sfBandTabShort) */ + cbIdx = cbRight[sfb]; + + if (cbIdx == 14 || cbIdx == 15) { + /* intensity stereo */ + if (msMaskPres == 1 && (msMask & 0x01)) + cbIdx ^= 0x01; /* invert_intensity(): 14 becomes 15, or 15 becomes 14 */ + sf = -sfRight[sfb]; /* negative since we use identity 0.5^(x) = 2^(-x) (see spec) */ + cbIdx &= 0x01; /* choose - or + scale factor */ + scalef = pow14[cbIdx][sf & 0x03]; + scalei = (sf >> 2) + 2; /* +2 to compensate for scalef = Q30 */ + + if (scalei > 0) { + if (scalei > 30) + scalei = 30; + do { + cr = MULSHIFT32(*coefL++, scalef); + {int sign = (cr) >> 31; if (sign != (cr) >> (31-scalei)) {(cr) = sign ^ ((1 << (31-scalei)) - 1);}} + cr <<= scalei; + gbMaskR |= FASTABS(cr); + *coefR++ = cr; + } while (--width); + } else { + scalei = -scalei; + if (scalei > 31) + scalei = 31; + do { + cr = MULSHIFT32(*coefL++, scalef) >> scalei; + gbMaskR |= FASTABS(cr); + *coefR++ = cr; + } while (--width); + } + } else if ( cbIdx != 13 && ((msMaskPres == 1 && (msMask & 0x01)) || msMaskPres == 2) ) { + /* mid-side stereo (assumes no GB in inputs) */ + do { + cl = *coefL; + cr = *coefR; + + if ( (FASTABS(cl) | FASTABS(cr)) >> 30 ) { + /* avoid overflow (rare) */ + cl >>= 1; + sf = cl + (cr >> 1); + {int sign = (sf) >> 31; if (sign != (sf) >> (30)) {(sf) = sign ^ ((1 << (30)) - 1);}} + sf <<= 1; + cl = cl - (cr >> 1); + {int sign = (cl) >> 31; if (sign != (cl) >> (30)) {(cl) = sign ^ ((1 << (30)) - 1);}} + cl <<= 1; + } else { + /* usual case */ + sf = cl + cr; + cl -= cr; + } + + *coefL++ = sf; + gbMaskL |= FASTABS(sf); + *coefR++ = cl; + gbMaskR |= FASTABS(cl); + } while (--width); + + } else { + /* nothing to do */ + coefL += width; + coefR += width; + } + + /* get next mask bit (should be branchless on ARM) */ + msMask >>= 1; + if (++msMaskOffset == 8) { + msMask = *msMaskPtr++; + msMaskOffset = 0; + } + } + + cl = CLZ(gbMaskL) - 1; + if (gbCurrent[0] > cl) + gbCurrent[0] = cl; + + cr = CLZ(gbMaskR) - 1; + if (gbCurrent[1] > cr) + gbCurrent[1] = cr; + + return; +} + +/*********************************************************************************************************************** + * Function: StereoProcess + * + * Description: apply mid-side and intensity stereo, if enabled + * + * Inputs: none + * + * Outputs: updated transform coefficients in Q(FBITS_OUT_DQ_OFF) + * updated minimum guard bit count for both channels + * + * Return: 0 if successful, -1 if error + **********************************************************************************************************************/ +int StereoProcess() +{ + ICSInfo_t *icsInfo; + int gp, win, nSamps, msMaskOffset; + int *coefL, *coefR; + uint8_t *msMaskPtr; + const uint16_t *sfbTab; + + + /* mid-side and intensity stereo require common_window == 1 (see MPEG4 spec, Correction 2, 2004) */ + if (m_PSInfoBase->commonWin != 1 || m_AACDecInfo->currBlockID != AAC_ID_CPE) + return 0; + + /* nothing to do */ + if (!m_PSInfoBase->msMaskPresent && !m_PSInfoBase->intensityUsed[1]) + return 0; + + icsInfo = &(m_PSInfoBase->icsInfo[0]); + if (icsInfo->winSequence == 2) { + sfbTab = sfBandTabShort + sfBandTabShortOffset[m_PSInfoBase->sampRateIdx]; + nSamps = NSAMPS_SHORT; + } else { + sfbTab = sfBandTabLong + sfBandTabLongOffset[m_PSInfoBase->sampRateIdx]; + nSamps = NSAMPS_LONG; + } + coefL = m_PSInfoBase->coef[0]; + coefR = m_PSInfoBase->coef[1]; + + /* do fused mid-side/intensity processing for each block (one long or eight short) */ + msMaskOffset = 0; + msMaskPtr = m_PSInfoBase->msMaskBits; + for (gp = 0; gp < icsInfo->numWinGroup; gp++) { + for (win = 0; win < icsInfo->winGroupLen[gp]; win++) { + StereoProcessGroup(coefL, coefR, sfbTab, m_PSInfoBase->msMaskPresent, + msMaskPtr, msMaskOffset, icsInfo->maxSFB, m_PSInfoBase->sfbCodeBook[1] + gp*icsInfo->maxSFB, + m_PSInfoBase->scaleFactors[1] + gp*icsInfo->maxSFB, m_PSInfoBase->gbCurrent); + coefL += nSamps; + coefR += nSamps; + } + /* we use one bit per sfb, so there are maxSFB bits for each window group */ + msMaskPtr += (msMaskOffset + icsInfo->maxSFB) >> 3; + msMaskOffset = (msMaskOffset + icsInfo->maxSFB) & 0x07; + } + + ASSERT(coefL == m_PSInfoBase->coef[0] + 1024); + ASSERT(coefR == m_PSInfoBase->coef[1] + 1024); + + return 0; +} + +/*********************************************************************************************************************** + * Function: RatioPowInv + * + * Description: use Taylor (MacLaurin) series expansion to calculate (a/b) ^ (1/c) + * + * Inputs: a = [1, 64], b = [1, 64], c = [1, 64], a >= b + * + * Outputs: none + * + * Return: y = Q24, range ~= [0.015625, 64] + **********************************************************************************************************************/ +int RatioPowInv(int a, int b, int c) +{ + int lna, lnb, i, p, t, y; + + if (a < 1 || b < 1 || c < 1 || a > 64 || b > 64 || c > 64 || a < b) + return 0; + + lna = MULSHIFT32(log2Tab[a], LOG2_EXP_INV) << 1; /* ln(a), Q28 */ + lnb = MULSHIFT32(log2Tab[b], LOG2_EXP_INV) << 1; /* ln(b), Q28 */ + p = (lna - lnb) / c; /* Q28 */ + + /* sum in Q24 */ + y = (1 << 24); + t = p >> 4; /* t = p^1 * 1/1! (Q24)*/ + y += t; + + for (i = 2; i <= NUM_TERMS_RPI; i++) { + t = MULSHIFT32(invTab[i-1], t) << 2; + t = MULSHIFT32(p, t) << 4; /* t = p^i * 1/i! (Q24) */ + y += t; + } + + return y; +} + +/*********************************************************************************************************************** + * Function: SqrtFix + * + * Description: use binary search to calculate sqrt(q) + * + * Inputs: q = Q30 + * number of fraction bits in input + * + * Outputs: number of fraction bits in output + * + * Return: lo = Q(fBitsOut) + * + * Notes: absolute precision varies depending on fBitsIn + * normalizes input to range [0x200000000, 0x7fffffff] and takes + * floor(sqrt(input)), and sets fBitsOut appropriately + **********************************************************************************************************************/ +int SqrtFix(int q, int fBitsIn, int *fBitsOut) +{ + int z, lo, hi, mid; + + if (q <= 0) { + *fBitsOut = fBitsIn; + return 0; + } + + /* force even fBitsIn */ + z = fBitsIn & 0x01; + q >>= z; + fBitsIn -= z; + + /* for max precision, normalize to [0x20000000, 0x7fffffff] */ + z = (CLZ(q) - 1); + z >>= 1; + q <<= (2*z); + + /* choose initial bounds */ + lo = 1; + if (q >= 0x10000000) + lo = 16384; /* (int)sqrt(0x10000000) */ + hi = 46340; /* (int)sqrt(0x7fffffff) */ + + /* do binary search with 32x32->32 multiply test */ + do { + mid = (lo + hi) >> 1; + if (mid*mid > q) + hi = mid - 1; + else + lo = mid + 1; + } while (hi >= lo); + lo--; + + *fBitsOut = ((fBitsIn + 2*z) >> 1); + return lo; +} + +/*********************************************************************************************************************** + * Function: InvRNormalized + * + * Description: use Newton's method to solve for x = 1/r + * + * Inputs: r = Q31, range = [0.5, 1) (normalize your inputs to this range) + * + * Outputs: none + * + * Return: x = Q29, range ~= [1.0, 2.0] + * + * Notes: guaranteed to converge and not overflow for any r in [0.5, 1) + * + * xn+1 = xn - f(xn)/f'(xn) + * f(x) = 1/r - x = 0 (find root) + * = 1/x - r + * f'(x) = -1/x^2 + * + * so xn+1 = xn - (1/xn - r) / (-1/xn^2) + * = xn * (2 - r*xn) + * + * NUM_ITER_IRN = 2, maxDiff = 6.2500e-02 (precision of about 4 bits) + * NUM_ITER_IRN = 3, maxDiff = 3.9063e-03 (precision of about 8 bits) + * NUM_ITER_IRN = 4, maxDiff = 1.5288e-05 (precision of about 16 bits) + * NUM_ITER_IRN = 5, maxDiff = 3.0034e-08 (precision of about 24 bits) + **********************************************************************************************************************/ +int InvRNormalized(int r) +{ + int i, xn, t; + + /* r = [0.5, 1.0) + * 1/r = (1.0, 2.0] + * so use 1.5 as initial guess + */ + xn = Q28_15; + + /* xn = xn*(2.0 - r*xn) */ + for (i = NUM_ITER_IRN; i != 0; i--) { + t = MULSHIFT32(r, xn); /* Q31*Q29 = Q28 */ + t = Q28_2 - t; /* Q28 */ + xn = MULSHIFT32(xn, t) << 4; /* Q29*Q28 << 4 = Q29 */ + } + + return xn; +} + + + +/*********************************************************************************************************************** + * Function: BitReverse32 + * + * Description: Ken's fast in-place bit reverse + * + * Inputs: buffer of 32 complex samples + * + * Outputs: bit-reversed samples in same buffer + * + * Return: none +***********************************************************************************************************************/ +void BitReverse32(int *inout) +{ + int t; + t=inout[2] ; inout[2]=inout[32]; inout[32]=t; + t=inout[3] ; inout[3]=inout[33]; inout[33]=t; + + t=inout[4] ; inout[4]=inout[16]; inout[16]=t; + t=inout[5] ; inout[5]=inout[17]; inout[17]=t; + + t=inout[6] ; inout[6]=inout[48]; inout[48]=t; + t=inout[7] ; inout[7]=inout[49]; inout[49]=t; + + t=inout[10]; inout[10]=inout[40]; inout[40]=t; + t=inout[11]; inout[11]=inout[41]; inout[41]=t; + + t=inout[12]; inout[12]=inout[24]; inout[24]=t; + t=inout[13]; inout[13]=inout[25]; inout[25]=t; + + t=inout[14]; inout[14]=inout[56]; inout[56]=t; + t=inout[15]; inout[15]=inout[57]; inout[57]=t; + + t=inout[18]; inout[18]=inout[36]; inout[36]=t; + t=inout[19]; inout[19]=inout[37]; inout[37]=t; + + t=inout[22]; inout[22]=inout[52]; inout[52]=t; + t=inout[23]; inout[23]=inout[53]; inout[53]=t; + + t=inout[26]; inout[26]=inout[44]; inout[44]=t; + t=inout[27]; inout[27]=inout[45]; inout[45]=t; + + t=inout[30]; inout[30]=inout[60]; inout[60]=t; + t=inout[31]; inout[31]=inout[61]; inout[61]=t; + + t=inout[38]; inout[38]=inout[50]; inout[50]=t; + t=inout[39]; inout[39]=inout[51]; inout[51]=t; + + t=inout[46]; inout[46]=inout[58]; inout[58]=t; + t=inout[47]; inout[47]=inout[59]; inout[59]=t; + +} + +/*********************************************************************************************************************** + * Function: R8FirstPass32 + * + * Description: radix-8 trivial pass for decimation-in-time FFT (log2(N) = 5) + * + * Inputs: buffer of (bit-reversed) samples + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: assumes 3 guard bits, gains 1 integer bit + * guard bits out = guard bits in - 3 (if inputs are full scale) + * or guard bits in - 2 (if inputs bounded to +/- sqrt(2)/2) + * see scaling comments in fft.c for base AAC + * should compile with no stack spills on ARM (verify compiled output) + * current instruction count (per pass): 16 LDR, 16 STR, 4 SMULL, 61 ALU + **********************************************************************************************************************/ +void R8FirstPass32(int *r0) +{ + int r1, r2, r3, r4, r5, r6, r7; + int r8, r9, r10, r11, r12, r14; + + /* number of passes = fft size / 8 = 32 / 8 = 4 */ + r1 = (32 >> 3); + do { + + r2 = r0[8]; + r3 = r0[9]; + r4 = r0[10]; + r5 = r0[11]; + r6 = r0[12]; + r7 = r0[13]; + r8 = r0[14]; + r9 = r0[15]; + + r10 = r2 + r4; + r11 = r3 + r5; + r12 = r6 + r8; + r14 = r7 + r9; + + r2 -= r4; + r3 -= r5; + r6 -= r8; + r7 -= r9; + + r4 = r2 - r7; + r5 = r2 + r7; + r8 = r3 - r6; + r9 = r3 + r6; + + r2 = r4 - r9; + r3 = r4 + r9; + r6 = r5 - r8; + r7 = r5 + r8; + + r2 = MULSHIFT32(SQRTHALF, r2); /* can use r4, r5, r8, or r9 for constant and lo32 scratch reg */ + r3 = MULSHIFT32(SQRTHALF, r3); + r6 = MULSHIFT32(SQRTHALF, r6); + r7 = MULSHIFT32(SQRTHALF, r7); + + r4 = r10 + r12; + r5 = r10 - r12; + r8 = r11 + r14; + r9 = r11 - r14; + + r10 = r0[0]; + r11 = r0[2]; + r12 = r0[4]; + r14 = r0[6]; + + r10 += r11; + r12 += r14; + + r4 >>= 1; + r10 += r12; + r4 += (r10 >> 1); + r0[ 0] = r4; + r4 -= (r10 >> 1); + r4 = (r10 >> 1) - r4; + r0[ 8] = r4; + + r9 >>= 1; + r10 -= 2*r12; + r4 = (r10 >> 1) + r9; + r0[ 4] = r4; + r4 = (r10 >> 1) - r9; + r0[12] = r4; + r10 += r12; + + r10 -= 2*r11; + r12 -= 2*r14; + + r4 = r0[1]; + r9 = r0[3]; + r11 = r0[5]; + r14 = r0[7]; + + r4 += r9; + r11 += r14; + + r8 >>= 1; + r4 += r11; + r8 += (r4 >> 1); + r0[ 1] = r8; + r8 -= (r4 >> 1); + r8 = (r4 >> 1) - r8; + r0[ 9] = r8; + + r5 >>= 1; + r4 -= 2*r11; + r8 = (r4 >> 1) - r5; + r0[ 5] = r8; + r8 = (r4 >> 1) + r5; + r0[13] = r8; + r4 += r11; + + r4 -= 2*r9; + r11 -= 2*r14; + + r9 = r10 - r11; + r10 += r11; + r14 = r4 + r12; + r4 -= r12; + + r5 = (r10 >> 1) + r7; + r8 = (r4 >> 1) - r6; + r0[ 2] = r5; + r0[ 3] = r8; + + r5 = (r9 >> 1) - r2; + r8 = (r14 >> 1) - r3; + r0[ 6] = r5; + r0[ 7] = r8; + + r5 = (r10 >> 1) - r7; + r8 = (r4 >> 1) + r6; + r0[10] = r5; + r0[11] = r8; + + r5 = (r9 >> 1) + r2; + r8 = (r14 >> 1) + r3; + r0[14] = r5; + r0[15] = r8; + + r0 += 16; + r1--; + } while (r1 != 0); +} + +/*********************************************************************************************************************** + * Function: R4Core32 + * + * Description: radix-4 pass for 32-point decimation-in-time FFT + * + * Inputs: buffer of samples + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: gain 2 integer bits + * guard bits out = guard bits in - 1 (if inputs are full scale) + * see scaling comments in fft.c for base AAC + * uses 3-mul, 3-add butterflies instead of 4-mul, 2-add + * should compile with no stack spills on ARM (verify compiled output) + * current instruction count (per pass): 16 LDR, 16 STR, 4 SMULL, 61 ALU + **********************************************************************************************************************/ +void R4Core32(int *r0) +{ + int r2, r3, r4, r5, r6, r7; + int r8, r9, r10, r12, r14; + int *r1; + + r1 = (int *)twidTabOdd32; + r10 = 8; + do { + /* can use r14 for lo32 scratch register in all MULSHIFT32 */ + r2 = r1[0]; + r3 = r1[1]; + r4 = r0[16]; + r5 = r0[17]; + r12 = r4 + r5; + r12 = MULSHIFT32(r3, r12); + r5 = MULSHIFT32(r2, r5) + r12; + r2 += 2*r3; + r4 = MULSHIFT32(r2, r4) - r12; + + r2 = r1[2]; + r3 = r1[3]; + r6 = r0[32]; + r7 = r0[33]; + r12 = r6 + r7; + r12 = MULSHIFT32(r3, r12); + r7 = MULSHIFT32(r2, r7) + r12; + r2 += 2*r3; + r6 = MULSHIFT32(r2, r6) - r12; + + r2 = r1[4]; + r3 = r1[5]; + r8 = r0[48]; + r9 = r0[49]; + r12 = r8 + r9; + r12 = MULSHIFT32(r3, r12); + r9 = MULSHIFT32(r2, r9) + r12; + r2 += 2*r3; + r8 = MULSHIFT32(r2, r8) - r12; + + r2 = r0[0]; + r3 = r0[1]; + + r12 = r6 + r8; + r8 = r6 - r8; + r14 = r9 - r7; + r9 = r9 + r7; + + r6 = (r2 >> 2) - r4; + r7 = (r3 >> 2) - r5; + r4 += (r2 >> 2); + r5 += (r3 >> 2); + + r2 = r4 + r12; + r3 = r5 + r9; + r0[0] = r2; + r0[1] = r3; + r2 = r6 - r14; + r3 = r7 - r8; + r0[16] = r2; + r0[17] = r3; + r2 = r4 - r12; + r3 = r5 - r9; + r0[32] = r2; + r0[33] = r3; + r2 = r6 + r14; + r3 = r7 + r8; + r0[48] = r2; + r0[49] = r3; + + r0 += 2; + r1 += 6; + r10--; + } while (r10 != 0); +} + +/*********************************************************************************************************************** + * Function: FFT32C + * + * Description: Ken's very fast in-place radix-4 decimation-in-time FFT + * + * Inputs: buffer of 32 complex samples (before bit-reversal) + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: assumes 3 guard bits in, gains 3 integer bits + * guard bits out = guard bits in - 2 + * (guard bit analysis includes assumptions about steps immediately + * before and after, i.e. PreMul and PostMul for DCT) + **********************************************************************************************************************/ +void FFT32C(int *x) +{ + /* decimation in time */ + BitReverse32(x); + + /* 32-point complex FFT */ + R8FirstPass32(x); /* gain 1 int bit, lose 2 GB (making assumptions about input) */ + R4Core32(x); /* gain 2 int bits, lose 0 GB (making assumptions about input) */ +} + +/*********************************************************************************************************************** + * Function: CVKernel1 + * + * Description: kernel of covariance matrix calculation for p01, p11, p12, p22 + * + * Inputs: buffer of low-freq samples, starting at time index = 0, + * freq index = patch subband + * + * Outputs: 64-bit accumulators for p01re, p01im, p12re, p12im, p11re, p22re + * stored in accBuf + * + * Return: none + * + * Notes: this is carefully written to be efficient on ARM + * use the assembly code version in sbrcov.s when building for ARM! + **********************************************************************************************************************/ +void CVKernel1(int *XBuf, int *accBuf) +{ + U64 p01re, p01im, p12re, p12im, p11re, p22re; + int n, x0re, x0im, x1re, x1im; + + x0re = XBuf[0]; + x0im = XBuf[1]; + XBuf += (2*64); + x1re = XBuf[0]; + x1im = XBuf[1]; + XBuf += (2*64); + + p01re.w64 = p01im.w64 = 0; + p12re.w64 = p12im.w64 = 0; + p11re.w64 = 0; + p22re.w64 = 0; + + p12re.w64 = MADD64(p12re.w64, x1re, x0re); + p12re.w64 = MADD64(p12re.w64, x1im, x0im); + p12im.w64 = MADD64(p12im.w64, x0re, x1im); + p12im.w64 = MADD64(p12im.w64, -x0im, x1re); + p22re.w64 = MADD64(p22re.w64, x0re, x0re); + p22re.w64 = MADD64(p22re.w64, x0im, x0im); + for (n = (NUM_TIME_SLOTS*SAMPLES_PER_SLOT + 6); n != 0; n--) { + /* 4 input, 3*2 acc, 1 ptr, 1 loop counter = 12 registers (use same for x0im, -x0im) */ + x0re = x1re; + x0im = x1im; + x1re = XBuf[0]; + x1im = XBuf[1]; + + p01re.w64 = MADD64(p01re.w64, x1re, x0re); + p01re.w64 = MADD64(p01re.w64, x1im, x0im); + p01im.w64 = MADD64(p01im.w64, x0re, x1im); + p01im.w64 = MADD64(p01im.w64, -x0im, x1re); + p11re.w64 = MADD64(p11re.w64, x0re, x0re); + p11re.w64 = MADD64(p11re.w64, x0im, x0im); + + XBuf += (2*64); + } + /* these can be derived by slight changes to account for boundary conditions */ + p12re.w64 += p01re.w64; + p12re.w64 = MADD64(p12re.w64, x1re, -x0re); + p12re.w64 = MADD64(p12re.w64, x1im, -x0im); + p12im.w64 += p01im.w64; + p12im.w64 = MADD64(p12im.w64, x0re, -x1im); + p12im.w64 = MADD64(p12im.w64, x0im, x1re); + p22re.w64 += p11re.w64; + p22re.w64 = MADD64(p22re.w64, x0re, -x0re); + p22re.w64 = MADD64(p22re.w64, x0im, -x0im); + + accBuf[0] = p01re.r.lo32; accBuf[1] = p01re.r.hi32; + accBuf[2] = p01im.r.lo32; accBuf[3] = p01im.r.hi32; + accBuf[4] = p11re.r.lo32; accBuf[5] = p11re.r.hi32; + accBuf[6] = p12re.r.lo32; accBuf[7] = p12re.r.hi32; + accBuf[8] = p12im.r.lo32; accBuf[9] = p12im.r.hi32; + accBuf[10] = p22re.r.lo32; accBuf[11] = p22re.r.hi32; +} + +/*********************************************************************************************************************** + * Function: CVKernel2 + * + * Description: kernel of covariance matrix calculation for p02 + * + * Inputs: buffer of low-freq samples, starting at time index = 0, + * freq index = patch subband + * + * Outputs: 64-bit accumulators for p02re, p02im stored in accBuf + * + * Return: none + * + * Notes: this is carefully written to be efficient on ARM + * use the assembly code version in sbrcov.s when building for ARM! + **********************************************************************************************************************/ +void CVKernel2(int *XBuf, int *accBuf) +{ + U64 p02re, p02im; + int n, x0re, x0im, x1re, x1im, x2re, x2im; + + p02re.w64 = p02im.w64 = 0; + + x0re = XBuf[0]; + x0im = XBuf[1]; + XBuf += (2*64); + x1re = XBuf[0]; + x1im = XBuf[1]; + XBuf += (2*64); + + for (n = (NUM_TIME_SLOTS*SAMPLES_PER_SLOT + 6); n != 0; n--) { + /* 6 input, 2*2 acc, 1 ptr, 1 loop counter = 12 registers (use same for x0im, -x0im) */ + x2re = XBuf[0]; + x2im = XBuf[1]; + + p02re.w64 = MADD64(p02re.w64, x2re, x0re); + p02re.w64 = MADD64(p02re.w64, x2im, x0im); + p02im.w64 = MADD64(p02im.w64, x0re, x2im); + p02im.w64 = MADD64(p02im.w64, -x0im, x2re); + + x0re = x1re; + x0im = x1im; + x1re = x2re; + x1im = x2im; + XBuf += (2*64); + } + + accBuf[0] = p02re.r.lo32; + accBuf[1] = p02re.r.hi32; + accBuf[2] = p02im.r.lo32; + accBuf[3] = p02im.r.hi32; +} + +/*********************************************************************************************************************** + * Function: SetBitstreamPointer + * + * Description: initialize bitstream reader + * + * Inputs: number of bytes in bitstream + * pointer to byte-aligned buffer of data to read from + * + * Outputs: initialized bitstream info struct + * + * Return: none + **********************************************************************************************************************/ +void SetBitstreamPointer(int nBytes, uint8_t *buf) +{ + /* init bitstream */ + m_aac_BitStreamInfo.bytePtr = buf; + m_aac_BitStreamInfo.iCache = 0; /* 4-byte uint32_t */ + m_aac_BitStreamInfo.cachedBits = 0; /* i.e. zero bits in cache */ + m_aac_BitStreamInfo.nBytes = nBytes; +} + +/*********************************************************************************************************************** + * Function: RefillBitstreamCache + * + * Description: read new data from bitstream buffer into 32-bit cache + * + * Inputs: none + * + * Outputs: updated bitstream info struct + * + * Return: none + * + * Notes: only call when iCache is completely drained (resets bitOffset to 0) + * always loads 4 new bytes except when bsi->nBytes < 4 (end of buffer) + * stores data as big-endian in cache, regardless of machine endian-ness + **********************************************************************************************************************/ +//Optimized for REV16, REV32 (FB) +inline void RefillBitstreamCache() +{ + int nBytes = m_aac_BitStreamInfo.nBytes; + if (nBytes >= 4) { + /* optimize for common case, independent of machine endian-ness */ + m_aac_BitStreamInfo.iCache = (*m_aac_BitStreamInfo.bytePtr++) << 24; + m_aac_BitStreamInfo.iCache |= (*m_aac_BitStreamInfo.bytePtr++) << 16; + m_aac_BitStreamInfo.iCache |= (*m_aac_BitStreamInfo.bytePtr++) << 8; + m_aac_BitStreamInfo.iCache |= (*m_aac_BitStreamInfo.bytePtr++); + + m_aac_BitStreamInfo.cachedBits = 32; + m_aac_BitStreamInfo.nBytes -= 4; + } else { + m_aac_BitStreamInfo.iCache = 0; + while (nBytes--) { + m_aac_BitStreamInfo.iCache |= (*m_aac_BitStreamInfo.bytePtr++); + m_aac_BitStreamInfo.iCache <<= 8; + } + m_aac_BitStreamInfo.iCache <<= ((3 - m_aac_BitStreamInfo.nBytes)*8); + m_aac_BitStreamInfo.cachedBits = 8*m_aac_BitStreamInfo.nBytes; + m_aac_BitStreamInfo.nBytes = 0; + } +} + +/*********************************************************************************************************************** + * Function: GetBits + * + * Description: get bits from bitstream, advance bitstream pointer + * + * Inputs: pointer to initialized aac_BitStreamInfo_t struct + * number of bits to get from bitstream + * + * Outputs: updated bitstream info struct + * + * Return: the next nBits bits of data from bitstream buffer + * + * Notes: nBits must be in range [0, 31], nBits outside this range masked by 0x1f + * for speed, does not indicate error if you overrun bit buffer + * if nBits == 0, returns 0 + **********************************************************************************************************************/ +unsigned int GetBits(int nBits) +{ + uint32_t data, lowBits; + + nBits &= 0x1f; /* nBits mod 32 to avoid unpredictable results like >> by negative amount */ + data = m_aac_BitStreamInfo.iCache >> (31 - nBits); /* unsigned >> so zero-extend */ + data >>= 1; /* do as >> 31, >> 1 so that nBits = 0 works okay (returns 0) */ + m_aac_BitStreamInfo.iCache <<= nBits; /* left-justify cache */ + m_aac_BitStreamInfo.cachedBits -= nBits; /* how many bits have we drawn from the cache so far */ + + /* if we cross an int boundary, refill the cache */ + if (m_aac_BitStreamInfo.cachedBits < 0) { + lowBits = -m_aac_BitStreamInfo.cachedBits; + RefillBitstreamCache(); + data |= m_aac_BitStreamInfo.iCache >> (32 - lowBits); /* get the low-order bits */ + + m_aac_BitStreamInfo.cachedBits -= lowBits; /* how many bits have we drawn from the cache so far */ + m_aac_BitStreamInfo.iCache <<= lowBits; /* left-justify cache */ + } + + return data; +} + +/*********************************************************************************************************************** + * Function: GetBitsNoAdvance + * + * Description: get bits from bitstream, do not advance bitstream pointer + * + * Inputs: pointer to initialized aac_BitStreamInfo_t struct + * number of bits to get from bitstream + * + * Outputs: none (state of aac_BitStreamInfo_t struct left unchanged) + * + * Return: the next nBits bits of data from bitstream buffer + * + * Notes: nBits must be in range [0, 31], nBits outside this range masked by 0x1f + * for speed, does not indicate error if you overrun bit buffer + * if nBits == 0, returns 0 + **********************************************************************************************************************/ +unsigned int GetBitsNoAdvance(int nBits) +{ + uint8_t *buf; + uint32_t data, iCache; + int32_t lowBits; + + nBits &= 0x1f; /* nBits mod 32 to avoid unpredictable results like >> by negative amount */ + data = m_aac_BitStreamInfo.iCache >> (31 - nBits); /* unsigned >> so zero-extend */ + data >>= 1; /* do as >> 31, >> 1 so that nBits = 0 works okay (returns 0) */ + lowBits = nBits - m_aac_BitStreamInfo.cachedBits; /* how many bits do we have left to read */ + + /* if we cross an int boundary, read next bytes in buffer */ + if (lowBits > 0) { + iCache = 0; + buf = m_aac_BitStreamInfo.bytePtr; + while (lowBits > 0) { + iCache <<= 8; + if (buf < m_aac_BitStreamInfo.bytePtr + m_aac_BitStreamInfo.nBytes) + iCache |= (uint32_t)*buf++; + lowBits -= 8; + } + lowBits = -lowBits; + data |= iCache >> lowBits; + } + + return data; +} + +/*********************************************************************************************************************** + * Function: AdvanceBitstream + * + * Description: move bitstream pointer ahead + * + * Inputs: number of bits to advance bitstream + * + * Outputs: updated bitstream info struct + * + * Return: none + * + * Notes: generally used following GetBitsNoAdvance(bsi, maxBits) + **********************************************************************************************************************/ +void AdvanceBitstream(int nBits) +{ + nBits &= 0x1f; + if (nBits > m_aac_BitStreamInfo.cachedBits) { + nBits -= m_aac_BitStreamInfo.cachedBits; + RefillBitstreamCache(); + } + m_aac_BitStreamInfo.iCache <<= nBits; + m_aac_BitStreamInfo.cachedBits -= nBits; +} + +/*********************************************************************************************************************** + * Function: CalcBitsUsed + * + * Description: calculate how many bits have been read from bitstream + * + * Inputs: pointer to start of bitstream buffer + * bit offset into first byte of startBuf (0-7) + * + * Outputs: none + * + * Return: number of bits read from bitstream, as offset from startBuf:startOffset + **********************************************************************************************************************/ +int CalcBitsUsed(uint8_t *startBuf, int startOffset) { + + int bitsUsed; + + bitsUsed = (m_aac_BitStreamInfo.bytePtr - startBuf) * 8; + bitsUsed -= m_aac_BitStreamInfo.cachedBits; + bitsUsed -= startOffset; + + return bitsUsed; +} +/*********************************************************************************************************************** + * Function: ByteAlignBitstream + * + * Description: bump bitstream pointer to start of next byte + * + * Inputs: none + * + * Outputs: byte-aligned bitstream aac_BitStreamInfo_t struct + * + * Return: none + * + * Notes: if bitstream is already byte-aligned, do nothing + **********************************************************************************************************************/ +void ByteAlignBitstream(){ + + int offset; + + offset = m_aac_BitStreamInfo.cachedBits & 0x07; + AdvanceBitstream(offset); +} + +#ifdef AAC_ENABLE_SBR + +/************************************************************************************** + * Function: InitSBRState + * + * Description: initialize PSInfoSBR struct at start of stream or after flush + * + * Inputs: valid AACDecInfo struct + * + * Outputs: PSInfoSBR struct with proper initial state + * + * Return: none + **************************************************************************************/ +void InitSBRState() { + + int i, ch; + uint8_t *c; + + if (!m_PSInfoSBR) + return; + + /* clear SBR state structure */ + c = (uint8_t *)m_PSInfoSBR; + for (i = 0; i < (int)sizeof(m_PSInfoSBR); i++) + *c++ = 0; + + /* initialize non-zero state variables */ + for (ch = 0; ch < AAC_MAX_NCHANS; ch++) { + m_PSInfoSBR->sbrChan[ch].reset = 1; + m_PSInfoSBR->sbrChan[ch].laPrev = -1; + } +} +#endif + +/*********************************************************************************************************************** + * Function: DecodeSBRBitstream + * + * Description: decode sideband information for SBR + * + * Inputs: base output channel (range = [0, nChans-1]) + * + * Outputs: initialized state structs (SBRHdr, SBRGrid, SBRFreq, SBRChan) + * + * Return: 0 if successful, error code (< 0) if error + * + * Notes: SBR payload should be in aacDecInfo->fillBuf + * returns with no error if fill buffer is not an SBR extension block, + * or if current block is not a fill block (e.g. for LFE upsampling) + **********************************************************************************************************************/ +int DecodeSBRBitstream(int chBase) { + + int headerFlag; + + if(m_AACDecInfo->currBlockID != AAC_ID_FIL + || (m_AACDecInfo->fillExtType != EXT_SBR_DATA && m_AACDecInfo->fillExtType != EXT_SBR_DATA_CRC)) + return ERR_AAC_NONE; + + SetBitstreamPointer(m_AACDecInfo->fillCount, m_AACDecInfo->fillBuf); + if(GetBits(4) != (unsigned int) m_AACDecInfo->fillExtType) return ERR_AAC_SBR_BITSTREAM; + + if(m_AACDecInfo->fillExtType == EXT_SBR_DATA_CRC) m_PSInfoSBR->crcCheckWord = GetBits(10); + + headerFlag = GetBits(1); + if(headerFlag) { + /* get sample rate index for output sample rate (2x base rate) */ + m_PSInfoSBR->sampRateIdx = GetSampRateIdx(2 * m_AACDecInfo->sampRate); + if(m_PSInfoSBR->sampRateIdx < 0 || m_PSInfoSBR->sampRateIdx >= NUM_SAMPLE_RATES) + return ERR_AAC_SBR_BITSTREAM; + else if(m_PSInfoSBR->sampRateIdx >= NUM_SAMPLE_RATES_SBR) return ERR_AAC_SBR_SINGLERATE_UNSUPPORTED; + + /* reset flag = 1 if header values changed */ + if(UnpackSBRHeader(&(m_PSInfoSBR->sbrHdr[chBase]))) m_PSInfoSBR->sbrChan[chBase].reset = 1; + + /* first valid SBR header should always trigger CalcFreqTables(), since psi->reset was set in InitSBR() */ + if(m_PSInfoSBR->sbrChan[chBase].reset) + CalcFreqTables(&(m_PSInfoSBR->sbrHdr[chBase + 0]), &(m_PSInfoSBR->sbrFreq[chBase]), + m_PSInfoSBR->sampRateIdx); + + /* copy and reset state to right channel for CPE */ + if(m_AACDecInfo->prevBlockID == AAC_ID_CPE) + m_PSInfoSBR->sbrChan[chBase + 1].reset = m_PSInfoSBR->sbrChan[chBase + 0].reset; + } + + /* if no header has been received, upsample only */ + if(m_PSInfoSBR->sbrHdr[chBase].count == 0) return ERR_AAC_NONE; + + if(m_AACDecInfo->prevBlockID == AAC_ID_SCE) { + UnpackSBRSingleChannel(chBase); + } + else if(m_AACDecInfo->prevBlockID == AAC_ID_CPE) { + UnpackSBRChannelPair(chBase); + } + else { + return ERR_AAC_SBR_BITSTREAM; + } + + ByteAlignBitstream(); + + return ERR_AAC_NONE; +} + +#ifdef AAC_ENABLE_SBR + +/*********************************************************************************************************************** + * Function: DecodeSBRData + * + * Description: apply SBR to one frame of PCM data + * + * Inputs: 1024 samples of decoded 32-bit PCM, before SBR + * size of input PCM samples (must be 4 bytes) + * number of fraction bits in input PCM samples + * base output channel (range = [0, nChans-1]) + * initialized state structs (SBRHdr, SBRGrid, SBRFreq, SBRChan) + * + * Outputs: 2048 samples of decoded 16-bit PCM, after SBR + * + * Return: 0 if successful, error code (< 0) if error + **********************************************************************************************************************/ +int DecodeSBRData(int chBase, short *outbuf) { + + int k, l, ch, chBlock, qmfaBands, qmfsBands; + int upsampleOnly, gbIdx, gbMask; + int *inbuf; + short *outptr; + + SBRHeader *sbrHdr; + SBRGrid *sbrGrid; + SBRFreq *sbrFreq; + SBRChan *sbrChan; + + /* same header and freq tables for both channels in CPE */ + sbrHdr = &(m_PSInfoSBR->sbrHdr[chBase]); + sbrFreq = &(m_PSInfoSBR->sbrFreq[chBase]); + + /* upsample only if we haven't received an SBR header yet or if we have an LFE block */ + if(m_AACDecInfo->currBlockID == AAC_ID_LFE) { + chBlock = 1; + upsampleOnly = 1; + } + else if(m_AACDecInfo->currBlockID == AAC_ID_FIL) { + if(m_AACDecInfo->prevBlockID == AAC_ID_SCE) + chBlock = 1; + else if(m_AACDecInfo->prevBlockID == AAC_ID_CPE) + chBlock = 2; + else + return ERR_AAC_NONE; + + upsampleOnly = (sbrHdr->count == 0 ? 1 : 0); + if(m_AACDecInfo->fillExtType != EXT_SBR_DATA && m_AACDecInfo->fillExtType != EXT_SBR_DATA_CRC) + return ERR_AAC_NONE; + } + else { + /* ignore non-SBR blocks */ + return ERR_AAC_NONE; + } + + if(upsampleOnly) { + sbrFreq->kStart = 32; + sbrFreq->numQMFBands = 0; + } + + for(ch = 0; ch < chBlock; ch++) { + sbrGrid = &(m_PSInfoSBR->sbrGrid[chBase + ch]); + sbrChan = &(m_PSInfoSBR->sbrChan[chBase + ch]); + + if(m_AACDecInfo->rawSampleBuf[ch] == 0 || m_AACDecInfo->rawSampleBytes != 4) return ERR_AAC_SBR_PCM_FORMAT; + inbuf = (int*) m_AACDecInfo->rawSampleBuf[ch]; + outptr = outbuf + chBase + ch; + + /* restore delay buffers (could use ring buffer or keep in temp buffer for nChans == 1) */ + for(l = 0; l < HF_GEN; l++) { + for(k = 0; k < 64; k++) { + m_PSInfoSBR->XBuf[l][k][0] = m_PSInfoSBR->XBufDelay[chBase + ch][l][k][0]; + m_PSInfoSBR->XBuf[l][k][1] = m_PSInfoSBR->XBufDelay[chBase + ch][l][k][1]; + } + } + + /* step 1 - analysis QMF */ + qmfaBands = sbrFreq->kStart; + for(l = 0; l < 32; l++) { + gbMask = QMFAnalysis(inbuf + l * 32, m_PSInfoSBR->delayQMFA[chBase + ch], m_PSInfoSBR->XBuf[l + HF_GEN][0], + m_AACDecInfo->rawSampleFBits, &(m_PSInfoSBR->delayIdxQMFA[chBase + ch]), qmfaBands); + + gbIdx = ((l + HF_GEN) >> 5) & 0x01; + sbrChan->gbMask[gbIdx] |= gbMask; /* gbIdx = (0 if i < 32), (1 if i >= 32) */ + } + + if(upsampleOnly) { + /* no SBR - just run synthesis QMF to upsample by 2x */ + qmfsBands = 32; + for(l = 0; l < 32; l++) { + /* step 4 - synthesis QMF */ + QMFSynthesis(m_PSInfoSBR->XBuf[l + HF_ADJ][0], m_PSInfoSBR->delayQMFS[chBase + ch], + &(m_PSInfoSBR->delayIdxQMFS[chBase + ch]), qmfsBands, outptr, m_AACDecInfo->nChans); + outptr += 64 * m_AACDecInfo->nChans; + } + } + else { + /* if previous frame had lower SBR starting freq than current, zero out the synthesized QMF + * bands so they aren't used as sources for patching + * after patch generation, restore from delay buffer + * can only happen after header reset + */ + for(k = sbrFreq->kStartPrev; k < sbrFreq->kStart; k++) { + for(l = 0; l < sbrGrid->envTimeBorder[0] + HF_ADJ; l++) { + m_PSInfoSBR->XBuf[l][k][0] = 0; + m_PSInfoSBR->XBuf[l][k][1] = 0; + } + } + + /* step 2 - HF generation */ + GenerateHighFreq(sbrGrid, sbrFreq, sbrChan, ch); + + /* restore SBR bands that were cleared before patch generation (time slots 0, 1 no longer needed) */ + for(k = sbrFreq->kStartPrev; k < sbrFreq->kStart; k++) { + for(l = HF_ADJ; l < sbrGrid->envTimeBorder[0] + HF_ADJ; l++) { + m_PSInfoSBR->XBuf[l][k][0] = m_PSInfoSBR->XBufDelay[chBase + ch][l][k][0]; + m_PSInfoSBR->XBuf[l][k][1] = m_PSInfoSBR->XBufDelay[chBase + ch][l][k][1]; + } + } + + /* step 3 - HF adjustment */ + AdjustHighFreq(sbrHdr, sbrGrid, sbrFreq, sbrChan, ch); + + /* step 4 - synthesis QMF */ + qmfsBands = sbrFreq->kStartPrev + sbrFreq->numQMFBandsPrev; + for(l = 0; l < sbrGrid->envTimeBorder[0]; l++) { + /* if new envelope starts mid-frame, use old settings until start of first envelope in this frame */ + QMFSynthesis(m_PSInfoSBR->XBuf[l + HF_ADJ][0], m_PSInfoSBR->delayQMFS[chBase + ch], + &(m_PSInfoSBR->delayIdxQMFS[chBase + ch]), qmfsBands, outptr, m_AACDecInfo->nChans); + outptr += 64 * m_AACDecInfo->nChans; + } + + qmfsBands = sbrFreq->kStart + sbrFreq->numQMFBands; + for(; l < 32; l++) { + /* use new settings for rest of frame (usually the entire frame, unless the first envelope starts mid-frame) */ + QMFSynthesis(m_PSInfoSBR->XBuf[l + HF_ADJ][0], m_PSInfoSBR->delayQMFS[chBase + ch], + &(m_PSInfoSBR->delayIdxQMFS[chBase + ch]), qmfsBands, outptr, m_AACDecInfo->nChans); + outptr += 64 * m_AACDecInfo->nChans; + } + } + + /* save delay */ + for(l = 0; l < HF_GEN; l++) { + for(k = 0; k < 64; k++) { + m_PSInfoSBR->XBufDelay[chBase + ch][l][k][0] = m_PSInfoSBR->XBuf[l + 32][k][0]; + m_PSInfoSBR->XBufDelay[chBase + ch][l][k][1] = m_PSInfoSBR->XBuf[l + 32][k][1]; + } + } + sbrChan->gbMask[0] = sbrChan->gbMask[1]; + sbrChan->gbMask[1] = 0; + + if(sbrHdr->count > 0) sbrChan->reset = 0; + } + sbrFreq->kStartPrev = sbrFreq->kStart; + sbrFreq->numQMFBandsPrev = sbrFreq->numQMFBands; + + if(m_AACDecInfo->nChans > 0 && (chBase + ch) == m_AACDecInfo->nChans) m_PSInfoSBR->frameCount++; + + return ERR_AAC_NONE; +} + +#endif + +/*********************************************************************************************************************** + * Function: BubbleSort + * + * Description: in-place sort of uint8_ts + * + * Inputs: buffer of elements to sort + * number of elements to sort + * + * Outputs: sorted buffer + * + * Return: none + **********************************************************************************************************************/ +void BubbleSort(uint8_t *v, int nItems) { + + int i; + uint8_t t; + + while(nItems >= 2) { + for(i = 0; i < nItems - 1; i++) { + if(v[i + 1] < v[i]) { + t = v[i + 1]; + v[i + 1] = v[i]; + v[i] = t; + } + } + nItems--; + } +} +/*********************************************************************************************************************** + * Function: VMin + * + * Description: find smallest element in a buffer of uint8_ts + * + * Inputs: buffer of elements to search + * number of elements to search + * + * Outputs: none + * + * Return: smallest element in buffer + **********************************************************************************************************************/ +uint8_t VMin(uint8_t *v, int nItems) { + + int i; + uint8_t vMin; + + vMin = v[0]; + for(i = 1; i < nItems; i++) { + if(v[i] < vMin) vMin = v[i]; + } + return vMin; +} +/*********************************************************************************************************************** + * Function: VMax + * + * Description: find largest element in a buffer of uint8_ts + * + * Inputs: buffer of elements to search + * number of elements to search + * + * Outputs: none + * + * Return: largest element in buffer + **********************************************************************************************************************/ +uint8_t VMax(uint8_t *v, int nItems) { + + int i; + uint8_t vMax; + + vMax = v[0]; + for(i = 1; i < nItems; i++) { + if(v[i] > vMax) vMax = v[i]; + } + return vMax; +} +/*********************************************************************************************************************** + * Function: CalcFreqMasterScaleZero + * + * Description: calculate master frequency table when freqScale == 0 + * (4.6.18.3.2.1, figure 4.39) + * + * Inputs: alterScale flag + * index of first QMF subband in master freq table (k0) + * index of last QMF subband (k2) + * + * Outputs: master frequency table + * + * Return: number of bands in master frequency table + * + * Notes: assumes k2 - k0 <= 48 and k2 >= k0 (4.6.18.3.6) + **********************************************************************************************************************/ +int CalcFreqMasterScaleZero(uint8_t *freqMaster, int alterScale, int k0, int k2) { + + int nMaster, k, nBands, k2Achieved, dk, vDk[64], k2Diff; + + if(alterScale) { + dk = 2; + nBands = 2 * ((k2 - k0 + 2) >> 2); + } + else { + dk = 1; + nBands = 2 * ((k2 - k0) >> 1); + } + + if(nBands <= 0) return 0; + + k2Achieved = k0 + nBands * dk; + k2Diff = k2 - k2Achieved; + for(k = 0; k < nBands; k++) + vDk[k] = dk; + + if(k2Diff > 0) { + k = nBands - 1; + while(k2Diff) { + vDk[k]++; + k--; + k2Diff--; + } + } + else if(k2Diff < 0) { + k = 0; + while(k2Diff) { + vDk[k]--; + k++; + k2Diff++; + } + } + + nMaster = nBands; + freqMaster[0] = k0; + for(k = 1; k <= nBands; k++) + freqMaster[k] = freqMaster[k - 1] + vDk[k - 1]; + + return nMaster; +} + +/* mBandTab[i] = temp1[i] / 2 */ +static const int mBandTab[3] PROGMEM = {6, 5, 4}; + +/* invWarpTab[i] = 1.0 / temp2[i], Q30 (see 4.6.18.3.2.1) */ +static const int invWarpTab[2] PROGMEM = {0x40000000, 0x313b13b1}; + +/*********************************************************************************************************************** + * Function: CalcFreqMasterScale + * + * Description: calculate master frequency table when freqScale > 0 + * (4.6.18.3.2.1, figure 4.39) + * + * Inputs: alterScale flag + * freqScale flag + * index of first QMF subband in master freq table (k0) + * index of last QMF subband (k2) + * + * Outputs: master frequency table + * + * Return: number of bands in master frequency table + * + * Notes: assumes k2 - k0 <= 48 and k2 >= k0 (4.6.18.3.6) + **********************************************************************************************************************/ +int CalcFreqMaster(uint8_t *freqMaster, int freqScale, int alterScale, int k0, int k2) { + + int bands, twoRegions, k, k1, t, vLast, vCurr, pCurr; + int invWarp, nBands0, nBands1, change; + uint8_t vDk1Min, vDk0Max; + uint8_t *vDelta; + + if(freqScale < 1 || freqScale > 3) return -1; + + bands = mBandTab[freqScale - 1]; + invWarp = invWarpTab[alterScale]; + + /* tested for all k0 = [5, 64], k2 = [k0, 64] */ + if(k2 * 10000 > 22449 * k0) { + twoRegions = 1; + k1 = 2 * k0; + } + else { + twoRegions = 0; + k1 = k2; + } + + /* tested for all k0 = [5, 64], k1 = [k0, 64], freqScale = [1,3] */ + t = (log2Tab[k1] - log2Tab[k0]) >> 3; /* log2(k1/k0), Q28 to Q25 */ + nBands0 = 2 * (((bands * t) + (1 << 24)) >> 25); /* multiply by bands/2, round to nearest int (mBandTab has factor of 1/2 rolled in) */ + + /* tested for all valid combinations of k0, k1, nBands (from sampRate, freqScale, alterScale) + * roundoff error can be a problem with fixpt (e.g. pCurr = 12.499999 instead of 12.50003) + * because successive multiplication always undershoots a little bit, but this + * doesn't occur in any of the ratios we encounter from the valid k0/k1 bands in the spec + */ + t = RatioPowInv(k1, k0, nBands0); + pCurr = k0 << 24; + vLast = k0; + vDelta = freqMaster + 1; /* operate in-place */ + for(k = 0; k < nBands0; k++) { + pCurr = MULSHIFT32(pCurr, t) << 8; /* keep in Q24 */ + vCurr = (pCurr + (1 << 23)) >> 24; + vDelta[k] = (vCurr - vLast); + vLast = vCurr; + } + + /* sort the deltas and find max delta for first region */ + BubbleSort(vDelta, nBands0); + vDk0Max = VMax(vDelta, nBands0); + + /* fill master frequency table with bands from first region */ + freqMaster[0] = k0; + for(k = 1; k <= nBands0; k++) + freqMaster[k] += freqMaster[k - 1]; + + /* if only one region, then the table is complete */ + if(!twoRegions) return nBands0; + + /* tested for all k1 = [10, 64], k2 = [k0, 64], freqScale = [1,3] */ + t = (log2Tab[k2] - log2Tab[k1]) >> 3; /* log2(k1/k0), Q28 to Q25 */ + t = MULSHIFT32(bands * t, invWarp) << 2; /* multiply by bands/2, divide by warp factor, keep Q25 */ + nBands1 = 2 * ((t + (1 << 24)) >> 25); /* round to nearest int */ + + /* see comments above for calculations in first region */ + t = RatioPowInv(k2, k1, nBands1); + pCurr = k1 << 24; + vLast = k1; + vDelta = freqMaster + nBands0 + 1; /* operate in-place */ + for(k = 0; k < nBands1; k++) { + pCurr = MULSHIFT32(pCurr, t) << 8; /* keep in Q24 */ + vCurr = (pCurr + (1 << 23)) >> 24; + vDelta[k] = (vCurr - vLast); + vLast = vCurr; + } + + /* sort the deltas, adjusting first and last if the second region has smaller deltas than the first */ + vDk1Min = VMin(vDelta, nBands1); + if(vDk1Min < vDk0Max) { + BubbleSort(vDelta, nBands1); + change = vDk0Max - vDelta[0]; + if(change > ((vDelta[nBands1 - 1] - vDelta[0]) >> 1)) change = ((vDelta[nBands1 - 1] - vDelta[0]) >> 1); + vDelta[0] += change; + vDelta[nBands1 - 1] -= change; + } + BubbleSort(vDelta, nBands1); + + /* fill master frequency table with bands from second region + * Note: freqMaster[nBands0] = k1 + */ + for(k = 1; k <= nBands1; k++) + freqMaster[k + nBands0] += freqMaster[k + nBands0 - 1]; + + return (nBands0 + nBands1); +} +/*********************************************************************************************************************** + * Function: CalcFreqHigh + * + * Description: calculate high resolution frequency table (4.6.18.3.2.2) + * + * Inputs: master frequency table + * number of bands in master frequency table + * crossover band from header + * + * Outputs: high resolution frequency table + * + * Return: number of bands in high resolution frequency table + **********************************************************************************************************************/ +int CalcFreqHigh(uint8_t *freqHigh, uint8_t *freqMaster, int nMaster, int crossOverBand) { + + int k, nHigh; + + nHigh = nMaster - crossOverBand; + + for(k = 0; k <= nHigh; k++) + freqHigh[k] = freqMaster[k + crossOverBand]; + + return nHigh; +} +/*********************************************************************************************************************** + * Function: CalcFreqLow + * + * Description: calculate low resolution frequency table (4.6.18.3.2.2) + * + * Inputs: high resolution frequency table + * number of bands in high resolution frequency table + * + * Outputs: low resolution frequency table + * + * Return: number of bands in low resolution frequency table + **********************************************************************************************************************/ +int CalcFreqLow(uint8_t *freqLow, uint8_t *freqHigh, int nHigh) { + + int k, nLow, oddFlag; + + nLow = nHigh - (nHigh >> 1); + freqLow[0] = freqHigh[0]; + oddFlag = nHigh & 0x01; + + for(k = 1; k <= nLow; k++) + freqLow[k] = freqHigh[2 * k - oddFlag]; + + return nLow; +} +/*********************************************************************************************************************** + * Function: CalcFreqNoise + * + * Description: calculate noise floor frequency table (4.6.18.3.2.2) + * + * Inputs: low resolution frequency table + * number of bands in low resolution frequency table + * index of starting QMF subband for SBR (kStart) + * index of last QMF subband (k2) + * number of noise bands + * + * Outputs: noise floor frequency table + * + * Return: number of bands in noise floor frequency table + **********************************************************************************************************************/ +int CalcFreqNoise(uint8_t *freqNoise, uint8_t *freqLow, int nLow, int kStart, int k2, int noiseBands) { + + int i, iLast, k, nQ, lTop, lBottom; + + lTop = log2Tab[k2]; + lBottom = log2Tab[kStart]; + nQ = noiseBands * ((lTop - lBottom) >> 2); /* Q28 to Q26, noiseBands = [0,3] */ + nQ = (nQ + (1 << 25)) >> 26; + if(nQ < 1) nQ = 1; + + ASSERT(nQ <= MAX_NUM_NOISE_FLOOR_BANDS); /* required from 4.6.18.3.6 */ + + iLast = 0; + freqNoise[0] = freqLow[0]; + for(k = 1; k <= nQ; k++) { + i = iLast + (nLow - iLast) / (nQ + 1 - k); /* truncating division */ + freqNoise[k] = freqLow[i]; + iLast = i; + } + + return nQ; +} +/*********************************************************************************************************************** + * Function: BuildPatches + * + * Description: build high frequency patches (4.6.18.6.3) + * + * Inputs: master frequency table + * number of bands in low resolution frequency table + * index of first QMF subband in master freq table (k0) + * index of starting QMF subband for SBR (kStart) + * number of QMF bands in high resolution frequency table + * sample rate index + * + * Outputs: starting subband for each patch + * number of subbands in each patch + * + * Return: number of patches + **********************************************************************************************************************/ +int BuildPatches(uint8_t *patchNumSubbands, uint8_t *patchStartSubband, uint8_t *freqMaster, int nMaster, int k0, + int kStart, int numQMFBands, int sampRateIdx) { + + int i, j, k; + int msb, sb, usb, numPatches, goalSB, oddFlag; + + msb = k0; + usb = kStart; + numPatches = 0; + goalSB = goalSBTab[sampRateIdx]; + + if(nMaster == 0) { + patchNumSubbands[0] = 0; + patchStartSubband[0] = 0; + return 0; + } + + if(goalSB < kStart + numQMFBands) { + k = 0; + for(i = 0; freqMaster[i] < goalSB; i++) + k = i + 1; + } + else { + k = nMaster; + } + + do { + j = k + 1; + do { + j--; + sb = freqMaster[j]; + oddFlag = (sb - 2 + k0) & 0x01; + } while(sb > k0 - 1 + msb - oddFlag); + + patchNumSubbands[numPatches] = MAX(sb - usb, 0); + patchStartSubband[numPatches] = k0 - oddFlag - patchNumSubbands[numPatches]; + + /* from MPEG reference code - slightly different from spec */ + if((patchNumSubbands[numPatches] < 3) && (numPatches > 0)) break; + + if(patchNumSubbands[numPatches] > 0) { + usb = sb; + msb = sb; + numPatches++; + } + else { + msb = kStart; + } + + if(freqMaster[k] - sb < 3) k = nMaster; + + } while(sb != (kStart + numQMFBands) && numPatches <= MAX_NUM_PATCHES); + + return numPatches; +} +/*********************************************************************************************************************** + * Function: FindFreq + * + * Description: search buffer of uint8_ts for a specific value + * + * Inputs: buffer of elements to search + * number of elements to search + * value to search for + * + * Outputs: none + * + * Return: non-zero if the value is found anywhere in the buffer, zero otherwise + **********************************************************************************************************************/ +int FindFreq(uint8_t *freq, int nFreq, uint8_t val) { + + int k; + + for(k = 0; k < nFreq; k++) { + if(freq[k] == val) return 1; + } + + return 0; +} +/*********************************************************************************************************************** + * Function: RemoveFreq + * + * Description: remove one element from a buffer of uint8_ts + * + * Inputs: buffer of elements + * number of elements + * index of element to remove + * + * Outputs: new buffer of length nFreq-1 + * + * Return: none + **********************************************************************************************************************/ +void RemoveFreq(uint8_t *freq, int nFreq, int removeIdx) { + + int k; + + if(removeIdx >= nFreq) return; + + for(k = removeIdx; k < nFreq - 1; k++) + freq[k] = freq[k + 1]; +} +/*********************************************************************************************************************** + * Function: CalcFreqLimiter + * + * Description: calculate limiter frequency table (4.6.18.3.2.3) + * + * Inputs: number of subbands in each patch + * low resolution frequency table + * number of bands in low resolution frequency table + * index of starting QMF subband for SBR (kStart) + * number of limiter bands + * number of patches + * + * Outputs: limiter frequency table + * + * Return: number of bands in limiter frequency table + **********************************************************************************************************************/ +int CalcFreqLimiter(uint8_t *freqLimiter, uint8_t *patchNumSubbands, uint8_t *freqLow, int nLow, int kStart, + int limiterBands, int numPatches) { + + int k, bands, nLimiter, nOctaves; + int limBandsPerOctave[3] = { 120, 200, 300 }; /* [1.2, 2.0, 3.0] * 100 */ + uint8_t patchBorders[MAX_NUM_PATCHES + 1]; + + /* simple case */ + if(limiterBands == 0) { + freqLimiter[0] = freqLow[0] - kStart; + freqLimiter[1] = freqLow[nLow] - kStart; + return 1; + } + + bands = limBandsPerOctave[limiterBands - 1]; + patchBorders[0] = kStart; + + /* from MPEG reference code - slightly different from spec (top border) */ + for(k = 1; k < numPatches; k++) + patchBorders[k] = patchBorders[k - 1] + patchNumSubbands[k - 1]; + patchBorders[k] = freqLow[nLow]; + + for(k = 0; k <= nLow; k++) + freqLimiter[k] = freqLow[k]; + + for(k = 1; k < numPatches; k++) + freqLimiter[k + nLow] = patchBorders[k]; + + k = 1; + nLimiter = nLow + numPatches - 1; + BubbleSort(freqLimiter, nLimiter + 1); + + while(k <= nLimiter) { + nOctaves = log2Tab[freqLimiter[k]] - log2Tab[freqLimiter[k - 1]]; /* Q28 */ + nOctaves = (nOctaves >> 9) * bands; /* Q19, max bands = 300 < 2^9 */ + if(nOctaves < (49 << 19)) { /* compare with 0.49*100, in Q19 */ + if(freqLimiter[k] == freqLimiter[k - 1] || FindFreq(patchBorders, numPatches + 1, freqLimiter[k]) == 0) { + RemoveFreq(freqLimiter, nLimiter + 1, k); + nLimiter--; + } + else if(FindFreq(patchBorders, numPatches + 1, freqLimiter[k - 1]) == 0) { + RemoveFreq(freqLimiter, nLimiter + 1, k - 1); + nLimiter--; + } + else { + k++; + } + } + else { + k++; + } + } + + /* store limiter boundaries as offsets from kStart */ + for(k = 0; k <= nLimiter; k++) + freqLimiter[k] -= kStart; + + return nLimiter; +} +/*********************************************************************************************************************** + * Function: CalcFreqTables + * + * Description: calulate master and derived frequency tables, and patches + * + * Inputs: initialized SBRHeader struct for this SCE/CPE block + * initialized SBRFreq struct for this SCE/CPE block + * sample rate index of output sample rate (after SBR) + * + * Outputs: master and derived frequency tables, and patches + * + * Return: non-zero if error, zero otherwise + **********************************************************************************************************************/ +int CalcFreqTables(SBRHeader *sbrHdr, SBRFreq *sbrFreq, int sampRateIdx) { + int k0, k2; + + k0 = k0Tab[sampRateIdx][sbrHdr->startFreq]; + + if(sbrHdr->stopFreq == 14) + k2 = 2 * k0; + else if(sbrHdr->stopFreq == 15) + k2 = 3 * k0; + else + k2 = k2Tab[sampRateIdx][sbrHdr->stopFreq]; + if(k2 > 64) k2 = 64; + + /* calculate master frequency table */ + if(sbrHdr->freqScale == 0) + sbrFreq->nMaster = CalcFreqMasterScaleZero(sbrFreq->freqMaster, sbrHdr->alterScale, k0, k2); + else + sbrFreq->nMaster = CalcFreqMaster(sbrFreq->freqMaster, sbrHdr->freqScale, sbrHdr->alterScale, k0, k2); + + /* calculate high frequency table and related parameters */ + sbrFreq->nHigh = CalcFreqHigh(sbrFreq->freqHigh, sbrFreq->freqMaster, sbrFreq->nMaster, sbrHdr->crossOverBand); + sbrFreq->numQMFBands = sbrFreq->freqHigh[sbrFreq->nHigh] - sbrFreq->freqHigh[0]; + sbrFreq->kStart = sbrFreq->freqHigh[0]; + + /* calculate low frequency table */ + sbrFreq->nLow = CalcFreqLow(sbrFreq->freqLow, sbrFreq->freqHigh, sbrFreq->nHigh); + + /* calculate noise floor frequency table */ + sbrFreq->numNoiseFloorBands = CalcFreqNoise(sbrFreq->freqNoise, sbrFreq->freqLow, sbrFreq->nLow, sbrFreq->kStart, + k2, sbrHdr->noiseBands); + + /* calculate limiter table */ + sbrFreq->numPatches = BuildPatches(sbrFreq->patchNumSubbands, sbrFreq->patchStartSubband, sbrFreq->freqMaster, + sbrFreq->nMaster, k0, sbrFreq->kStart, sbrFreq->numQMFBands, sampRateIdx); + sbrFreq->nLimiter = CalcFreqLimiter(sbrFreq->freqLimiter, sbrFreq->patchNumSubbands, sbrFreq->freqLow, + sbrFreq->nLow, sbrFreq->kStart, sbrHdr->limiterBands, sbrFreq->numPatches); + + return 0; +} +/*********************************************************************************************************************** + * Function: EstimateEnvelope + * + * Description: estimate power of generated HF QMF bands in one time-domain envelope + * (4.6.18.7.3) + * + * Inputs: initialized PSInfoSBR struct + * initialized SBRHeader struct for this SCE/CPE block + * initialized SBRGrid struct for this channel + * initialized SBRFreq struct for this SCE/CPE block + * index of current envelope + * + * Outputs: power of each QMF subband, stored as integer (Q0) * 2^N, N >= 0 + * + * Return: none + **********************************************************************************************************************/ +void EstimateEnvelope(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, int env) { + + int i, m, iStart, iEnd, xre, xim, nScale, expMax; + int p, n, mStart, mEnd, invFact, t; + int *XBuf; + U64 eCurr; + uint8_t *freqBandTab; + + /* estimate current envelope */ + iStart = sbrGrid->envTimeBorder[env] + HF_ADJ; + iEnd = sbrGrid->envTimeBorder[env + 1] + HF_ADJ; + if(sbrGrid->freqRes[env]) { + n = sbrFreq->nHigh; + freqBandTab = sbrFreq->freqHigh; + } + else { + n = sbrFreq->nLow; + freqBandTab = sbrFreq->freqLow; + } + + /* ADS should inline MADD64 (smlal) properly, but check to make sure */ + expMax = 0; + if(sbrHdr->interpFreq) { + for(m = 0; m < sbrFreq->numQMFBands; m++) { + eCurr.w64 = 0; + XBuf = m_PSInfoSBR->XBuf[iStart][sbrFreq->kStart + m]; + for(i = iStart; i < iEnd; i++) { + /* scale to int before calculating power (precision not critical, and avoids overflow) */ + xre = (*XBuf) >> FBITS_OUT_QMFA; + XBuf += 1; + xim = (*XBuf) >> FBITS_OUT_QMFA; + XBuf += (2 * 64 - 1); + eCurr.w64 = MADD64(eCurr.w64, xre, xre); + eCurr.w64 = MADD64(eCurr.w64, xim, xim); + } + + /* eCurr.w64 is now Q(64 - 2*FBITS_OUT_QMFA) (64-bit word) + * if energy is too big to fit in 32-bit word (> 2^31) scale down by power of 2 + */ + nScale = 0; + if(eCurr.r.hi32) { + nScale = (32 - CLZ(eCurr.r.hi32)) + 1; + t = (int) (eCurr.r.lo32 >> nScale); /* logical (unsigned) >> */ + t |= eCurr.r.hi32 << (32 - nScale); + } + else if(eCurr.r.lo32 >> 31) { + nScale = 1; + t = (int) (eCurr.r.lo32 >> nScale); /* logical (unsigned) >> */ + } + else { + t = (int) eCurr.r.lo32; + } + + invFact = invBandTab[(iEnd - iStart) - 1]; + m_PSInfoSBR->eCurr[m] = MULSHIFT32(t, invFact); + m_PSInfoSBR->eCurrExp[m] = nScale + 1; /* +1 for invFact = Q31 */ + if(m_PSInfoSBR->eCurrExp[m] > expMax) expMax = m_PSInfoSBR->eCurrExp[m]; + } + } + else { + for(p = 0; p < n; p++) { + mStart = freqBandTab[p]; + mEnd = freqBandTab[p + 1]; + eCurr.w64 = 0; + for(i = iStart; i < iEnd; i++) { + XBuf = m_PSInfoSBR->XBuf[i][mStart]; + for(m = mStart; m < mEnd; m++) { + xre = (*XBuf++) >> FBITS_OUT_QMFA; + xim = (*XBuf++) >> FBITS_OUT_QMFA; + eCurr.w64 = MADD64(eCurr.w64, xre, xre); + eCurr.w64 = MADD64(eCurr.w64, xim, xim); + } + } + + nScale = 0; + if(eCurr.r.hi32) { + nScale = (32 - CLZ(eCurr.r.hi32)) + 1; + t = (int) (eCurr.r.lo32 >> nScale); /* logical (unsigned) >> */ + t |= eCurr.r.hi32 << (32 - nScale); + } + else if(eCurr.r.lo32 >> 31) { + nScale = 1; + t = (int) (eCurr.r.lo32 >> nScale); /* logical (unsigned) >> */ + } + else { + t = (int) eCurr.r.lo32; + } + + invFact = invBandTab[(iEnd - iStart) - 1]; + invFact = MULSHIFT32(invBandTab[(mEnd - mStart) - 1], invFact) << 1; + t = MULSHIFT32(t, invFact); + + for(m = mStart; m < mEnd; m++) { + m_PSInfoSBR->eCurr[m - sbrFreq->kStart] = t; + m_PSInfoSBR->eCurrExp[m - sbrFreq->kStart] = nScale + 1; /* +1 for invFact = Q31 */ + } + if(m_PSInfoSBR->eCurrExp[mStart - sbrFreq->kStart] > expMax) + expMax = m_PSInfoSBR->eCurrExp[mStart - sbrFreq->kStart]; + } + } + m_PSInfoSBR->eCurrExpMax = expMax; +} +/*********************************************************************************************************************** + * Function: GetSMapped + * + * Description: calculate SMapped (4.6.18.7.2) + * + * Inputs: initialized SBRGrid struct for this channel + * initialized SBRFreq struct for this SCE/CPE block + * initialized SBRChan struct for this channel + * index of current envelope + * index of current QMF band + * la flag for this envelope + * + * Outputs: none + * + * Return: 1 if a sinusoid is present in this band, 0 if not + **********************************************************************************************************************/ +int GetSMapped(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int env, int band, int la) { + + int bandStart, bandEnd, oddFlag, r; + + if (sbrGrid->freqRes[env]) { + /* high resolution */ + bandStart = band; + bandEnd = band+1; + } else { + /* low resolution (see CalcFreqLow() for mapping) */ + oddFlag = sbrFreq->nHigh & 0x01; + bandStart = (band > 0 ? 2*band - oddFlag : 0); /* starting index for freqLow[band] */ + bandEnd = 2*(band+1) - oddFlag; /* ending index for freqLow[band+1] */ + } + + /* sMapped = 1 if sIndexMapped == 1 for any frequency in this band */ + for (band = bandStart; band < bandEnd; band++) { + if (sbrChan->addHarmonic[1][band]) { + r = ((sbrFreq->freqHigh[band+1] + sbrFreq->freqHigh[band]) >> 1); + if (env >= la || sbrChan->addHarmonic[0][r] == 1) + return 1; + } + } + return 0; +} + +#define GBOOST_MAX 0x2830afd3 /* Q28, 1.584893192 squared */ +#define ACC_SCALE 6 + +/* squared version of table in 4.6.18.7.5 */ /* Q30 (0x80000000 = sentinel for GMAX) */ +static const uint32_t limGainTab[4] PROGMEM = {0x20138ca7, 0x40000000, 0x7fb27dce, 0x80000000}; + +/*********************************************************************************************************************** + * Function: CalcMaxGain + * + * Description: calculate max gain in one limiter band (4.6.18.7.5) + * + * Inputs: initialized SBRHeader struct for this SCE/CPE block + * initialized SBRGrid struct for this channel + * initialized SBRFreq struct for this SCE/CPE block + * index of current channel (0 for SCE, 0 or 1 for CPE) + * index of current envelope + * index of current limiter band + * number of fraction bits in dequantized envelope + * (max = Q(FBITS_OUT_DQ_ENV - 6) = Q23, can go negative) + * + * Outputs: updated gainMax, gainMaxFBits, and sumEOrigMapped in PSInfoSBR struct + * + * Return: none + **********************************************************************************************************************/ +void CalcMaxGain(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, int ch, int env, int lim, int fbitsDQ) { + + int m, mStart, mEnd, q, z, r; + int sumEOrigMapped, sumECurr, gainMax, eOMGainMax, envBand; + uint8_t eCurrExpMax; + uint8_t *freqBandTab; + + mStart = sbrFreq->freqLimiter[lim]; /* these are offsets from kStart */ + mEnd = sbrFreq->freqLimiter[lim + 1]; + freqBandTab = (sbrGrid->freqRes[env] ? sbrFreq->freqHigh : sbrFreq->freqLow); + + /* calculate max gain to apply to signal in this limiter band */ + sumECurr = 0; + sumEOrigMapped = 0; + eCurrExpMax = m_PSInfoSBR->eCurrExpMax; + eOMGainMax = m_PSInfoSBR->eOMGainMax; + envBand = m_PSInfoSBR->envBand; + for(m = mStart; m < mEnd; m++) { + /* map current QMF band to appropriate envelope band */ + if(m == freqBandTab[envBand + 1] - sbrFreq->kStart) { + envBand++; + eOMGainMax = m_PSInfoSBR->envDataDequant[ch][env][envBand] >> ACC_SCALE; /* summing max 48 bands */ + } + sumEOrigMapped += eOMGainMax; + + /* easy test for overflow on ARM */ + sumECurr += (m_PSInfoSBR->eCurr[m] >> (eCurrExpMax - m_PSInfoSBR->eCurrExp[m])); + if(sumECurr >> 30) { + sumECurr >>= 1; + eCurrExpMax++; + } + } + m_PSInfoSBR->eOMGainMax = eOMGainMax; + m_PSInfoSBR->envBand = envBand; + + m_PSInfoSBR->gainMaxFBits = 30; /* Q30 tables */ + if(sumECurr == 0) { + /* any non-zero numerator * 1/EPS_0 is > G_MAX */ + gainMax = (sumEOrigMapped == 0 ? (int) limGainTab[sbrHdr->limiterGains] : (int) 0x80000000); + } + else if(sumEOrigMapped == 0) { + /* 1/(any non-zero denominator) * EPS_0 * limGainTab[x] is appx. 0 */ + gainMax = 0; + } + else { + /* sumEOrigMapped = Q(fbitsDQ - ACC_SCALE), sumECurr = Q(-eCurrExpMax) */ + gainMax = limGainTab[sbrHdr->limiterGains]; + if(sbrHdr->limiterGains != 3) { + q = MULSHIFT32(sumEOrigMapped, gainMax); /* Q(fbitsDQ - ACC_SCALE - 2), gainMax = Q30 */ + z = CLZ(sumECurr) - 1; + r = InvRNormalized(sumECurr << z); /* in = Q(z - eCurrExpMax), out = Q(29 + 31 - z + eCurrExpMax) */ + gainMax = MULSHIFT32(q, r); /* Q(29 + 31 - z + eCurrExpMax + fbitsDQ - ACC_SCALE - 2 - 32) */ + m_PSInfoSBR->gainMaxFBits = 26 - z + eCurrExpMax + fbitsDQ - ACC_SCALE; + } + } + m_PSInfoSBR->sumEOrigMapped = sumEOrigMapped; + m_PSInfoSBR->gainMax = gainMax; +} +/*********************************************************************************************************************** + * Function: CalcNoiseDivFactors + * + * Description: calculate 1/(1+Q) and Q/(1+Q) (4.6.18.7.4; 4.6.18.7.5) + * + * Inputs: dequantized noise floor scalefactor + * + * Outputs: 1/(1+Q) and Q/(1+Q), format = Q31 + * + * Return: none + **********************************************************************************************************************/ +void CalcNoiseDivFactors(int q, int *qp1Inv, int *qqp1Inv) { + + int z, qp1, t, s; + + /* 1 + Q_orig */ + qp1 = (q >> 1); + qp1 += (1 << (FBITS_OUT_DQ_NOISE - 1)); /* >> 1 to avoid overflow when adding 1.0 */ + z = CLZ(qp1) - 1; /* z <= 31 - FBITS_OUT_DQ_NOISE */ + qp1 <<= z; /* Q(FBITS_OUT_DQ_NOISE + z) = Q31 * 2^-(31 - (FBITS_OUT_DQ_NOISE + z)) */ + t = InvRNormalized(qp1) << 1; /* Q30 * 2^(31 - (FBITS_OUT_DQ_NOISE + z)), guaranteed not to overflow */ + + /* normalize to Q31 */ + s = (31 - (FBITS_OUT_DQ_NOISE - 1) - z - 1); /* clearly z >= 0, z <= (30 - (FBITS_OUT_DQ_NOISE - 1)) */ + *qp1Inv = (t >> s); /* s = [0, 31 - FBITS_OUT_DQ_NOISE] */ + *qqp1Inv = MULSHIFT32(t, q) << (32 - FBITS_OUT_DQ_NOISE - s); +} +/*********************************************************************************************************************** + * Function: CalcComponentGains + * + * Description: calculate gain of envelope, sinusoids, and noise in one limiter band + * (4.6.18.7.5) + * + * Inputs: initialized SBRHeader struct for this SCE/CPE block + * initialized SBRGrid struct for this channel + * initialized SBRFreq struct for this SCE/CPE block + * initialized SBRChan struct for this channel + * index of current channel (0 for SCE, 0 or 1 for CPE) + * index of current envelope + * index of current limiter band + * number of fraction bits in dequantized envelope + * + * Outputs: gains for envelope, sinusoids and noise + * number of fraction bits for envelope gain + * sum of the total gain for each component in this band + * other updated state variables + * + * Return: none + **********************************************************************************************************************/ +void CalcComponentGains(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int ch, int env, int lim, int fbitsDQ) { + + int d, m, mStart, mEnd, q, qm, noiseFloor, sIndexMapped; + int shift, eCurr, maxFlag, gainMax, gainMaxFBits; + int gain, sm, z, r, fbitsGain, gainScale; + uint8_t *freqBandTab; + + mStart = sbrFreq->freqLimiter[lim]; /* these are offsets from kStart */ + mEnd = sbrFreq->freqLimiter[lim + 1]; + + gainMax = m_PSInfoSBR->gainMax; + gainMaxFBits = m_PSInfoSBR->gainMaxFBits; + + d = (env == m_PSInfoSBR->la || env == sbrChan->laPrev ? 0 : 1); + freqBandTab = (sbrGrid->freqRes[env] ? sbrFreq->freqHigh : sbrFreq->freqLow); + + /* figure out which noise floor this envelope is in (only 1 or 2 noise floors allowed) */ + noiseFloor = 0; + if(sbrGrid->numNoiseFloors == 2 && sbrGrid->noiseTimeBorder[1] <= sbrGrid->envTimeBorder[env]) noiseFloor++; + + m_PSInfoSBR->sumECurrGLim = 0; + m_PSInfoSBR->sumSM = 0; + m_PSInfoSBR->sumQM = 0; + /* calculate energy of noise to add in this limiter band */ + for(m = mStart; m < mEnd; m++) { + if(m == sbrFreq->freqNoise[m_PSInfoSBR->noiseFloorBand + 1] - sbrFreq->kStart) { + /* map current QMF band to appropriate noise floor band (NOTE: freqLimiter[0] == freqLow[0] = freqHigh[0]) */ + m_PSInfoSBR->noiseFloorBand++; + CalcNoiseDivFactors(m_PSInfoSBR->noiseDataDequant[ch][noiseFloor][m_PSInfoSBR->noiseFloorBand], + &(m_PSInfoSBR->qp1Inv), &(m_PSInfoSBR->qqp1Inv)); + } + if(m == sbrFreq->freqHigh[m_PSInfoSBR->highBand + 1] - sbrFreq->kStart) m_PSInfoSBR->highBand++; + if(m == freqBandTab[m_PSInfoSBR->sBand + 1] - sbrFreq->kStart) { + m_PSInfoSBR->sBand++; + m_PSInfoSBR->sMapped = GetSMapped(sbrGrid, sbrFreq, sbrChan, env, m_PSInfoSBR->sBand, m_PSInfoSBR->la); + } + + /* get sIndexMapped for this QMF subband */ + sIndexMapped = 0; + r = ((sbrFreq->freqHigh[m_PSInfoSBR->highBand + 1] + sbrFreq->freqHigh[m_PSInfoSBR->highBand]) >> 1); + if(m + sbrFreq->kStart == r) { + /* r = center frequency, deltaStep = (env >= la || sIndexMapped'(r, numEnv'-1) == 1) */ + if(env >= m_PSInfoSBR->la || sbrChan->addHarmonic[0][r] == 1) sIndexMapped = + sbrChan->addHarmonic[1][m_PSInfoSBR->highBand]; + } + + /* save sine flags from last envelope in this frame: + * addHarmonic[0][0...63] = saved sine present flag from previous frame, for each QMF subband + * addHarmonic[1][0...nHigh-1] = addHarmonic bit from current frame, for each high-res frequency band + * from MPEG reference code - slightly different from spec + * (sIndexMapped'(m,LE'-1) can still be 0 when numEnv == psi->la) + */ + if(env == sbrGrid->numEnv - 1) { + if(m + sbrFreq->kStart == r) + sbrChan->addHarmonic[0][m + sbrFreq->kStart] = sbrChan->addHarmonic[1][m_PSInfoSBR->highBand]; + else + sbrChan->addHarmonic[0][m + sbrFreq->kStart] = 0; + } + + gain = m_PSInfoSBR->envDataDequant[ch][env][m_PSInfoSBR->sBand]; + qm = MULSHIFT32(gain, m_PSInfoSBR->qqp1Inv) << 1; + sm = (sIndexMapped ? MULSHIFT32(gain, m_PSInfoSBR->qp1Inv) << 1 : 0); + + /* three cases: (sMapped == 0 && delta == 1), (sMapped == 0 && delta == 0), (sMapped == 1) */ + if(d == 1 && m_PSInfoSBR->sMapped == 0) + gain = MULSHIFT32(m_PSInfoSBR->qp1Inv, gain) << 1; + else if(m_PSInfoSBR->sMapped != 0) gain = MULSHIFT32(m_PSInfoSBR->qqp1Inv, gain) << 1; + + /* gain, qm, sm = Q(fbitsDQ), gainMax = Q(fbitsGainMax) */ + eCurr = m_PSInfoSBR->eCurr[m]; + if(eCurr) { + z = CLZ(eCurr) - 1; + r = InvRNormalized(eCurr << z); /* in = Q(z - eCurrExp), out = Q(29 + 31 - z + eCurrExp) */ + gainScale = MULSHIFT32(gain, r); /* out = Q(29 + 31 - z + eCurrExp + fbitsDQ - 32) */ + fbitsGain = 29 + 31 - z + m_PSInfoSBR->eCurrExp[m] + fbitsDQ - 32; + } + else { + /* if eCurr == 0, then gain is unchanged (divide by EPS = 1) */ + gainScale = gain; + fbitsGain = fbitsDQ; + } + + /* see if gain for this band exceeds max gain */ + maxFlag = 0; + if(gainMax != (int) 0x80000000) { + if(fbitsGain >= gainMaxFBits) { + shift = MIN(fbitsGain - gainMaxFBits, 31); + maxFlag = ((gainScale >> shift) > gainMax ? 1 : 0); + } + else { + shift = MIN(gainMaxFBits - fbitsGain, 31); + maxFlag = (gainScale > (gainMax >> shift) ? 1 : 0); + } + } + + if(maxFlag) { + /* gainScale > gainMax, calculate ratio with 32/16 division */ + q = 0; + r = gainScale; /* guaranteed > 0, else maxFlag could not have been set */ + z = CLZ(r); + if(z < 16) { + q = 16 - z; + r >>= q; /* out = Q(fbitsGain - q) */ + } + + z = CLZ(gainMax) - 1; + r = (gainMax << z) / r; /* out = Q((fbitsGainMax + z) - (fbitsGain - q)) */ + q = (gainMaxFBits + z) - (fbitsGain - q); /* r = Q(q) */ + if(q > 30) { + r >>= MIN(q - 30, 31); + } + else { + z = MIN(30 - q, 30); + r = CLIP_2N_SHIFT30(r, z); /* let r = Q30 since range = [0.0, 1.0) (clip to 0x3fffffff = 0.99999) */ + } + + qm = MULSHIFT32(qm, r) << 2; + gain = MULSHIFT32(gain, r) << 2; + m_PSInfoSBR->gLimBuf[m] = gainMax; + m_PSInfoSBR->gLimFbits[m] = gainMaxFBits; + } + else { + m_PSInfoSBR->gLimBuf[m] = gainScale; + m_PSInfoSBR->gLimFbits[m] = fbitsGain; + } + + /* sumSM, sumQM, sumECurrGLim = Q(fbitsDQ - ACC_SCALE) */ + m_PSInfoSBR->smBuf[m] = sm; + m_PSInfoSBR->sumSM += (sm >> ACC_SCALE); + + m_PSInfoSBR->qmLimBuf[m] = qm; + if(env != m_PSInfoSBR->la && env != sbrChan->laPrev && sm == 0) m_PSInfoSBR->sumQM += (qm >> ACC_SCALE); + + /* eCurr * gain^2 same as gain^2, before division by eCurr + * (but note that gain != 0 even if eCurr == 0, since it's divided by eps) + */ + if(eCurr) m_PSInfoSBR->sumECurrGLim += (gain >> ACC_SCALE); + } +} +/*********************************************************************************************************************** + * Function: ApplyBoost + * + * Description: calculate and apply boost factor for envelope, sinusoids, and noise + * in this limiter band (4.6.18.7.5) + * + * Inputs: initialized SBRFreq struct for this SCE/CPE block + * index of current limiter band + * number of fraction bits in dequantized envelope + * + * Outputs: envelope gain, sinusoids and noise after scaling by gBoost + * format = Q(FBITS_GLIM_BOOST) for envelope gain, + * = Q(FBITS_QLIM_BOOST) for noise + * = Q(FBITS_OUT_QMFA) for sinusoids + * + * Return: none + * + * Notes: after scaling, each component has at least 1 GB + **********************************************************************************************************************/ +void ApplyBoost(SBRFreq *sbrFreq, int lim, int fbitsDQ) { + + int m, mStart, mEnd, q, z, r; + int sumEOrigMapped, gBoost; + + mStart = sbrFreq->freqLimiter[lim]; /* these are offsets from kStart */ + mEnd = sbrFreq->freqLimiter[lim + 1]; + + sumEOrigMapped = m_PSInfoSBR->sumEOrigMapped >> 1; + r = (m_PSInfoSBR->sumECurrGLim >> 1) + (m_PSInfoSBR->sumSM >> 1) + (m_PSInfoSBR->sumQM >> 1); /* 1 GB fine (sm and qm are mutually exclusive in acc) */ + if(r < (1 << (31 - 28))) { + /* any non-zero numerator * 1/EPS_0 is > GBOOST_MAX + * round very small r to zero to avoid scaling problems + */ + gBoost = (sumEOrigMapped == 0 ? (1 << 28) : GBOOST_MAX); + z = 0; + } + else if(sumEOrigMapped == 0) { + /* 1/(any non-zero denominator) * EPS_0 is appx. 0 */ + gBoost = 0; + z = 0; + } + else { + /* numerator (sumEOrigMapped) and denominator (r) have same Q format (before << z) */ + z = CLZ(r) - 1; /* z = [0, 27] */ + r = InvRNormalized(r << z); + gBoost = MULSHIFT32(sumEOrigMapped, r); + } + + /* gBoost = Q(28 - z) */ + if(gBoost > (GBOOST_MAX >> z)) { + gBoost = GBOOST_MAX; + z = 0; + } + gBoost <<= z; /* gBoost = Q28, minimum 1 GB */ + + /* convert gain, noise, sinusoids to fixed Q format, clipping if necessary + * (rare, usually only happens at very low bitrates, introduces slight + * distortion into final HF mapping, but should be inaudible) + */ + for(m = mStart; m < mEnd; m++) { + /* let gLimBoost = Q24, since in practice the max values are usually 16 to 20 + * unless limiterGains == 3 (limiter off) and eCurr ~= 0 (i.e. huge gain, but only + * because the envelope has 0 power anyway) + */ + q = MULSHIFT32(m_PSInfoSBR->gLimBuf[m], gBoost) << 2; /* Q(gLimFbits) * Q(28) --> Q(gLimFbits[m]-2) */ + r = SqrtFix(q, m_PSInfoSBR->gLimFbits[m] - 2, &z); + z -= FBITS_GLIM_BOOST; + if(z >= 0) { + m_PSInfoSBR->gLimBoost[m] = r >> MIN(z, 31); + } + else { + z = MIN(30, -z); + r = CLIP_2N_SHIFT30(r, z); + m_PSInfoSBR->gLimBoost[m] = r; + } + + q = MULSHIFT32(m_PSInfoSBR->qmLimBuf[m], gBoost) << 2; /* Q(fbitsDQ) * Q(28) --> Q(fbitsDQ-2) */ + r = SqrtFix(q, fbitsDQ - 2, &z); + z -= FBITS_QLIM_BOOST; /* << by 14, since integer sqrt of x < 2^16, and we want to leave 1 GB */ + if(z >= 0) { + m_PSInfoSBR->qmLimBoost[m] = r >> MIN(31, z); + } + else { + z = MIN(30, -z); + r = CLIP_2N_SHIFT30(r, z); + m_PSInfoSBR->qmLimBoost[m] = r; + } + + q = MULSHIFT32(m_PSInfoSBR->smBuf[m], gBoost) << 2; /* Q(fbitsDQ) * Q(28) --> Q(fbitsDQ-2) */ + r = SqrtFix(q, fbitsDQ - 2, &z); + z -= FBITS_OUT_QMFA; /* justify for adding to signal (xBuf) later */ + if(z >= 0) { + m_PSInfoSBR->smBoost[m] = r >> MIN(31, z); + } + else { + z = MIN(30, -z); + r = CLIP_2N_SHIFT30(r, z); + m_PSInfoSBR->smBoost[m] = r; + } + } +} +/*********************************************************************************************************************** + * Function: CalcGain + * + * Description: calculate and apply proper gain to HF components in one envelope + * (4.6.18.7.5) + * + * Inputs: initialized SBRHeader struct for this SCE/CPE block + * initialized SBRGrid struct for this channel + * initialized SBRFreq struct for this SCE/CPE block + * initialized SBRChan struct for this channel + * index of current channel (0 for SCE, 0 or 1 for CPE) + * index of current envelope + * + * Outputs: envelope gain, sinusoids and noise after scaling + * + * Return: none + **********************************************************************************************************************/ +void CalcGain(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int ch, int env) { + + int lim, fbitsDQ; + + /* initialize to -1 so that mapping limiter bands to env/noise bands works right on first pass */ + m_PSInfoSBR->envBand = -1; + m_PSInfoSBR->noiseFloorBand = -1; + m_PSInfoSBR->sBand = -1; + m_PSInfoSBR->highBand = -1; + + fbitsDQ = (FBITS_OUT_DQ_ENV - m_PSInfoSBR->envDataDequantScale[ch][env]); /* Q(29 - optional scalefactor) */ + for(lim = 0; lim < sbrFreq->nLimiter; lim++) { + /* the QMF bands are divided into lim regions (consecutive, non-overlapping) */ + CalcMaxGain(sbrHdr, sbrGrid, sbrFreq, ch, env, lim, fbitsDQ); + CalcComponentGains(sbrGrid, sbrFreq, sbrChan, ch, env, lim, fbitsDQ); + ApplyBoost(sbrFreq, lim, fbitsDQ); + } +} + +/* hSmooth table from 4.7.18.7.6, format = Q31 */ +static const int hSmoothCoef[MAX_NUM_SMOOTH_COEFS] PROGMEM = { 0x2aaaaaab, 0x2697a512, 0x1becfa68, 0x0ebdb043, + 0x04130598, }; + +/*********************************************************************************************************************** + * Function: MapHF + * + * Description: map HF components to proper QMF bands, with optional gain smoothing + * filter (4.6.18.7.6) + * + * Inputs: initialized SBRHeader struct for this SCE/CPE block + * initialized SBRGrid struct for this channel + * initialized SBRFreq struct for this SCE/CPE block + * initialized SBRChan struct for this channel + * index of current envelope + * reset flag (can be non-zero for first envelope only) + * + * Outputs: complete reconstructed subband QMF samples for this envelope + * + * Return: none + * + * Notes: ensures that output has >= MIN_GBITS_IN_QMFS guard bits, + * so it's not necessary to check anything in the synth QMF + **********************************************************************************************************************/ +void MapHF(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int env, int hfReset) { + + int noiseTabIndex, sinIndex, gainNoiseIndex, hSL; + int i, iStart, iEnd, m, idx, j, s, n, smre, smim; + int gFilt, qFilt, xre, xim, gbMask, gbIdx; + int *XBuf; + + noiseTabIndex = sbrChan->noiseTabIndex; + sinIndex = sbrChan->sinIndex; + gainNoiseIndex = sbrChan->gainNoiseIndex; /* oldest entries in filter delay buffer */ + + if(hfReset) noiseTabIndex = 2; /* starts at 1, double since complex */ + hSL = (sbrHdr->smoothMode ? 0 : 4); + + if(hfReset) { + for(i = 0; i < hSL; i++) { + for(m = 0; m < sbrFreq->numQMFBands; m++) { + sbrChan->gTemp[gainNoiseIndex][m] = m_PSInfoSBR->gLimBoost[m]; + sbrChan->qTemp[gainNoiseIndex][m] = m_PSInfoSBR->qmLimBoost[m]; + } + gainNoiseIndex++; + if(gainNoiseIndex == MAX_NUM_SMOOTH_COEFS) gainNoiseIndex = 0; + } ASSERT(env == 0); /* should only be reset when env == 0 */ + } + + iStart = sbrGrid->envTimeBorder[env]; + iEnd = sbrGrid->envTimeBorder[env + 1]; + for(i = iStart; i < iEnd; i++) { + /* save new values in temp buffers (delay) + * we only store MAX_NUM_SMOOTH_COEFS most recent values, + * so don't keep storing the same value over and over + */ + if(i - iStart < MAX_NUM_SMOOTH_COEFS) { + for(m = 0; m < sbrFreq->numQMFBands; m++) { + sbrChan->gTemp[gainNoiseIndex][m] = m_PSInfoSBR->gLimBoost[m]; + sbrChan->qTemp[gainNoiseIndex][m] = m_PSInfoSBR->qmLimBoost[m]; + } + } + + /* see 4.6.18.7.6 */ + XBuf = m_PSInfoSBR->XBuf[i + HF_ADJ][sbrFreq->kStart]; + gbMask = 0; + for(m = 0; m < sbrFreq->numQMFBands; m++) { + if(env == m_PSInfoSBR->la || env == sbrChan->laPrev) { + /* no smoothing filter for gain, and qFilt = 0 (only need to do once) */ + if(i == iStart) { + m_PSInfoSBR->gFiltLast[m] = sbrChan->gTemp[gainNoiseIndex][m]; + m_PSInfoSBR->qFiltLast[m] = 0; + } + } + else if(hSL == 0) { + /* no smoothing filter for gain, (only need to do once) */ + if(i == iStart) { + m_PSInfoSBR->gFiltLast[m] = sbrChan->gTemp[gainNoiseIndex][m]; + m_PSInfoSBR->qFiltLast[m] = sbrChan->qTemp[gainNoiseIndex][m]; + } + } + else { + /* apply smoothing filter to gain and noise (after MAX_NUM_SMOOTH_COEFS, it's always the same) */ + if(i - iStart < MAX_NUM_SMOOTH_COEFS) { + gFilt = 0; + qFilt = 0; + idx = gainNoiseIndex; + for(j = 0; j < MAX_NUM_SMOOTH_COEFS; j++) { + /* sum(abs(hSmoothCoef[j])) for all j < 1.0 */ + gFilt += MULSHIFT32(sbrChan->gTemp[idx][m], hSmoothCoef[j]); + qFilt += MULSHIFT32(sbrChan->qTemp[idx][m], hSmoothCoef[j]); + idx--; + if(idx < 0) idx += MAX_NUM_SMOOTH_COEFS; + } + m_PSInfoSBR->gFiltLast[m] = gFilt << 1; /* restore to Q(FBITS_GLIM_BOOST) (gain of filter < 1.0, so no overflow) */ + m_PSInfoSBR->qFiltLast[m] = qFilt << 1; /* restore to Q(FBITS_QLIM_BOOST) */ + } + } + + if(m_PSInfoSBR->smBoost[m] != 0) { + /* add scaled signal and sinusoid, don't add noise (qFilt = 0) */ + smre = m_PSInfoSBR->smBoost[m]; + smim = smre; + + /* sinIndex: [0] xre += sm [1] xim += sm*s [2] xre -= sm [3] xim -= sm*s */ + s = (sinIndex >> 1); /* if 2 or 3, flip sign to subtract sm */ + s <<= 31; + smre ^= (s >> 31); + smre -= (s >> 31); + s ^= ((m + sbrFreq->kStart) << 31); + smim ^= (s >> 31); + smim -= (s >> 31); + + /* if sinIndex == 0 or 2, smim = 0; if sinIndex == 1 or 3, smre = 0 */ + s = sinIndex << 31; + smim &= (s >> 31); + s ^= 0x80000000; + smre &= (s >> 31); + + noiseTabIndex += 2; /* noise filtered by 0, but still need to bump index */ + } + else { + /* add scaled signal and scaled noise */ + qFilt = m_PSInfoSBR->qFiltLast[m]; + n = noiseTab[noiseTabIndex++]; + smre = MULSHIFT32(n, qFilt) >> (FBITS_QLIM_BOOST - 1 - FBITS_OUT_QMFA); + + n = noiseTab[noiseTabIndex++]; + smim = MULSHIFT32(n, qFilt) >> (FBITS_QLIM_BOOST - 1 - FBITS_OUT_QMFA); + } + noiseTabIndex &= 1023; /* 512 complex numbers */ + + gFilt = m_PSInfoSBR->gFiltLast[m]; + xre = MULSHIFT32(gFilt, XBuf[0]); + xim = MULSHIFT32(gFilt, XBuf[1]); + xre = CLIP_2N_SHIFT30(xre, 32 - FBITS_GLIM_BOOST); + xim = CLIP_2N_SHIFT30(xim, 32 - FBITS_GLIM_BOOST); + + xre += smre; + *XBuf++ = xre; + xim += smim; + *XBuf++ = xim; + + gbMask |= FASTABS(xre); + gbMask |= FASTABS(xim); + } + /* update circular buffer index */ + gainNoiseIndex++; + if(gainNoiseIndex == MAX_NUM_SMOOTH_COEFS) gainNoiseIndex = 0; + + sinIndex++; + sinIndex &= 3; + + /* ensure MIN_GBITS_IN_QMFS guard bits in output + * almost never occurs in practice, but checking here makes synth QMF logic very simple + */ + if(gbMask >> (31 - MIN_GBITS_IN_QMFS)) { + XBuf = m_PSInfoSBR->XBuf[i + HF_ADJ][sbrFreq->kStart]; + for(m = 0; m < sbrFreq->numQMFBands; m++) { + xre = XBuf[0]; + xim = XBuf[1]; + xre = CLIP_2N(xre, (31 - MIN_GBITS_IN_QMFS)); + xim = CLIP_2N(xim, (31 - MIN_GBITS_IN_QMFS)); + *XBuf++ = xre; + *XBuf++ = xim; + } + gbMask = CLIP_2N(gbMask, (31 - MIN_GBITS_IN_QMFS)); + } + gbIdx = ((i + HF_ADJ) >> 5) & 0x01; + sbrChan->gbMask[gbIdx] |= gbMask; + } + sbrChan->noiseTabIndex = noiseTabIndex; + sbrChan->sinIndex = sinIndex; + sbrChan->gainNoiseIndex = gainNoiseIndex; +} +/*********************************************************************************************************************** + * Function: AdjustHighFreq + * + * Description: adjust high frequencies and add noise and sinusoids (4.6.18.7) + * + * Inputs: initialized SBRHeader struct for this SCE/CPE block + * initialized SBRGrid struct for this channel + * initialized SBRFreq struct for this SCE/CPE block + * initialized SBRChan struct for this channel + * index of current channel (0 for SCE, 0 or 1 for CPE) + * + * Outputs: complete reconstructed subband QMF samples for this channel + * + * Return: none + **********************************************************************************************************************/ +void AdjustHighFreq(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int ch) { + + int i, env, hfReset; + uint8_t frameClass, pointer; + + frameClass = sbrGrid->frameClass; + pointer = sbrGrid->pointer; + + /* derive la from table 4.159 */ + if ((frameClass == SBR_GRID_FIXVAR || frameClass == SBR_GRID_VARVAR) && pointer > 0) + m_PSInfoSBR->la = sbrGrid->numEnv + 1 - pointer; + else if (frameClass == SBR_GRID_VARFIX && pointer > 1) + m_PSInfoSBR->la = pointer - 1; + else + m_PSInfoSBR->la = -1; + + /* for each envelope, estimate gain and adjust SBR QMF bands */ + hfReset = sbrChan->reset; + for (env = 0; env < sbrGrid->numEnv; env++) { + EstimateEnvelope(sbrHdr, sbrGrid, sbrFreq, env); + CalcGain(sbrHdr, sbrGrid, sbrFreq, sbrChan, ch, env); + MapHF(sbrHdr, sbrGrid, sbrFreq, sbrChan, env, hfReset); + hfReset = 0; /* only set for first envelope after header reset */ + } + + /* set saved sine flags to 0 for QMF bands outside of current frequency range */ + for (i = 0; i < sbrFreq->freqLimiter[0] + sbrFreq->kStart; i++) + sbrChan->addHarmonic[0][i] = 0; + for (i = sbrFreq->freqLimiter[sbrFreq->nLimiter] + sbrFreq->kStart; i < 64; i++) + sbrChan->addHarmonic[0][i] = 0; + sbrChan->addHarmonicFlag[0] = sbrChan->addHarmonicFlag[1]; + + /* save la for next frame */ + if (m_PSInfoSBR->la == sbrGrid->numEnv) + sbrChan->laPrev = 0; + else + sbrChan->laPrev = -1; +} +/*********************************************************************************************************************** + * Function: CalcCovariance1 + * + * Description: calculate covariance matrix for p01, p12, p11, p22 (4.6.18.6.2) + * + * Inputs: buffer of low-freq samples, starting at time index 0, + * freq index = patch subband + * + * Outputs: complex covariance elements p01re, p01im, p12re, p12im, p11re, p22re + * (p11im = p22im = 0) + * format = integer (Q0) * 2^N, with scalefactor N >= 0 + * + * Return: scalefactor N + * + * Notes: outputs are normalized to have 1 GB (sign in at least top 2 bits) + **********************************************************************************************************************/ +int CalcCovariance1(int *XBuf, int *p01reN, int *p01imN, int *p12reN, int *p12imN, int *p11reN, int *p22reN) { + + int accBuf[2*6]; + int n, z, s, loShift, hiShift, gbMask; + U64 p01re, p01im, p12re, p12im, p11re, p22re; + + CVKernel1(XBuf, accBuf); + p01re.r.lo32 = accBuf[0]; p01re.r.hi32 = accBuf[1]; + p01im.r.lo32 = accBuf[2]; p01im.r.hi32 = accBuf[3]; + p11re.r.lo32 = accBuf[4]; p11re.r.hi32 = accBuf[5]; + p12re.r.lo32 = accBuf[6]; p12re.r.hi32 = accBuf[7]; + p12im.r.lo32 = accBuf[8]; p12im.r.hi32 = accBuf[9]; + p22re.r.lo32 = accBuf[10]; p22re.r.hi32 = accBuf[11]; + + /* 64-bit accumulators now have 2*FBITS_OUT_QMFA fraction bits + * want to scale them down to integers (32-bit signed, Q0) + * with scale factor of 2^n, n >= 0 + * leave 2 GB's for calculating determinant, so take top 30 non-zero bits + */ + gbMask = ((p01re.r.hi32) ^ (p01re.r.hi32 >> 31)) | ((p01im.r.hi32) ^ (p01im.r.hi32 >> 31)); + gbMask |= ((p12re.r.hi32) ^ (p12re.r.hi32 >> 31)) | ((p12im.r.hi32) ^ (p12im.r.hi32 >> 31)); + gbMask |= ((p11re.r.hi32) ^ (p11re.r.hi32 >> 31)) | ((p22re.r.hi32) ^ (p22re.r.hi32 >> 31)); + if (gbMask == 0) { + s = p01re.r.hi32 >> 31; gbMask = (p01re.r.lo32 ^ s) - s; + s = p01im.r.hi32 >> 31; gbMask |= (p01im.r.lo32 ^ s) - s; + s = p12re.r.hi32 >> 31; gbMask |= (p12re.r.lo32 ^ s) - s; + s = p12im.r.hi32 >> 31; gbMask |= (p12im.r.lo32 ^ s) - s; + s = p11re.r.hi32 >> 31; gbMask |= (p11re.r.lo32 ^ s) - s; + s = p22re.r.hi32 >> 31; gbMask |= (p22re.r.lo32 ^ s) - s; + z = 32 + CLZ(gbMask); + } else { + gbMask = FASTABS(p01re.r.hi32) | FASTABS(p01im.r.hi32); + gbMask |= FASTABS(p12re.r.hi32) | FASTABS(p12im.r.hi32); + gbMask |= FASTABS(p11re.r.hi32) | FASTABS(p22re.r.hi32); + z = CLZ(gbMask); + } + + n = 64 - z; /* number of non-zero bits in bottom of 64-bit word */ + if (n <= 30) { + loShift = (30 - n); + *p01reN = p01re.r.lo32 << loShift; *p01imN = p01im.r.lo32 << loShift; + *p12reN = p12re.r.lo32 << loShift; *p12imN = p12im.r.lo32 << loShift; + *p11reN = p11re.r.lo32 << loShift; *p22reN = p22re.r.lo32 << loShift; + return -(loShift + 2*FBITS_OUT_QMFA); + } else if (n < 32 + 30) { + loShift = (n - 30); + hiShift = 32 - loShift; + *p01reN = (p01re.r.hi32 << hiShift) | (p01re.r.lo32 >> loShift); + *p01imN = (p01im.r.hi32 << hiShift) | (p01im.r.lo32 >> loShift); + *p12reN = (p12re.r.hi32 << hiShift) | (p12re.r.lo32 >> loShift); + *p12imN = (p12im.r.hi32 << hiShift) | (p12im.r.lo32 >> loShift); + *p11reN = (p11re.r.hi32 << hiShift) | (p11re.r.lo32 >> loShift); + *p22reN = (p22re.r.hi32 << hiShift) | (p22re.r.lo32 >> loShift); + return (loShift - 2*FBITS_OUT_QMFA); + } else { + hiShift = n - (32 + 30); + *p01reN = p01re.r.hi32 >> hiShift; *p01imN = p01im.r.hi32 >> hiShift; + *p12reN = p12re.r.hi32 >> hiShift; *p12imN = p12im.r.hi32 >> hiShift; + *p11reN = p11re.r.hi32 >> hiShift; *p22reN = p22re.r.hi32 >> hiShift; + return (32 - 2*FBITS_OUT_QMFA - hiShift); + } + + return 0; +} +/*********************************************************************************************************************** + * Function: CalcCovariance2 + * + * Description: calculate covariance matrix for p02 (4.6.18.6.2) + * + * Inputs: buffer of low-freq samples, starting at time index = 0, + * freq index = patch subband + * + * Outputs: complex covariance element p02re, p02im + * format = integer (Q0) * 2^N, with scalefactor N >= 0 + * + * Return: scalefactor N + * + * Notes: outputs are normalized to have 1 GB (sign in at least top 2 bits) + **********************************************************************************************************************/ +int CalcCovariance2(int *XBuf, int *p02reN, int *p02imN) { + + U64 p02re, p02im; + int n, z, s, loShift, hiShift, gbMask; + int accBuf[2*2]; + + CVKernel2(XBuf, accBuf); + p02re.r.lo32 = accBuf[0]; + p02re.r.hi32 = accBuf[1]; + p02im.r.lo32 = accBuf[2]; + p02im.r.hi32 = accBuf[3]; + + /* 64-bit accumulators now have 2*FBITS_OUT_QMFA fraction bits + * want to scale them down to integers (32-bit signed, Q0) + * with scale factor of 2^n, n >= 0 + * leave 1 GB for calculating determinant, so take top 30 non-zero bits + */ + gbMask = ((p02re.r.hi32) ^ (p02re.r.hi32 >> 31)) | ((p02im.r.hi32) ^ (p02im.r.hi32 >> 31)); + if (gbMask == 0) { + s = p02re.r.hi32 >> 31; gbMask = (p02re.r.lo32 ^ s) - s; + s = p02im.r.hi32 >> 31; gbMask |= (p02im.r.lo32 ^ s) - s; + z = 32 + CLZ(gbMask); + } else { + gbMask = FASTABS(p02re.r.hi32) | FASTABS(p02im.r.hi32); + z = CLZ(gbMask); + } + n = 64 - z; /* number of non-zero bits in bottom of 64-bit word */ + + if (n <= 30) { + loShift = (30 - n); + *p02reN = p02re.r.lo32 << loShift; + *p02imN = p02im.r.lo32 << loShift; + return -(loShift + 2*FBITS_OUT_QMFA); + } else if (n < 32 + 30) { + loShift = (n - 30); + hiShift = 32 - loShift; + *p02reN = (p02re.r.hi32 << hiShift) | (p02re.r.lo32 >> loShift); + *p02imN = (p02im.r.hi32 << hiShift) | (p02im.r.lo32 >> loShift); + return (loShift - 2*FBITS_OUT_QMFA); + } else { + hiShift = n - (32 + 30); + *p02reN = p02re.r.hi32 >> hiShift; + *p02imN = p02im.r.hi32 >> hiShift; + return (32 - 2*FBITS_OUT_QMFA - hiShift); + } + + return 0; +} +/*********************************************************************************************************************** + * Function: CalcLPCoefs + * + * Description: calculate linear prediction coefficients for one subband (4.6.18.6.2) + * + * Inputs: buffer of low-freq samples, starting at time index = 0, + * freq index = patch subband + * number of guard bits in input sample buffer + * + * Outputs: complex LP coefficients a0re, a0im, a1re, a1im, format = Q29 + * + * Return: none + * + * Notes: output coefficients (a0re, a0im, a1re, a1im) clipped to range (-4, 4) + * if the comples coefficients have magnitude >= 4.0, they are all + * set to 0 (see spec) + **********************************************************************************************************************/ +void CalcLPCoefs(int *XBuf, int *a0re, int *a0im, int *a1re, int *a1im, int gb) { + + int zFlag, n1, n2, nd, d, dInv, tre, tim; + int p01re, p01im, p02re, p02im, p12re, p12im, p11re, p22re; + + /* pre-scale to avoid overflow - probably never happens in practice (see QMFA) + * max bit growth per accumulator = 38*2 = 76 mul-adds (X * X) + * using 64-bit MADD, so if X has n guard bits, X*X has 2n+1 guard bits + * gain 1 extra sign bit per multiply, so ensure ceil(log2(76/2) / 2) = 3 guard bits on inputs + */ + if (gb < 3) { + nd = 3 - gb; + for (n1 = (NUM_TIME_SLOTS*SAMPLES_PER_SLOT + 6 + 2); n1 != 0; n1--) { + XBuf[0] >>= nd; XBuf[1] >>= nd; + XBuf += (2*64); + } + XBuf -= (2*64*(NUM_TIME_SLOTS*SAMPLES_PER_SLOT + 6 + 2)); + } + + /* calculate covariance elements */ + n1 = CalcCovariance1(XBuf, &p01re, &p01im, &p12re, &p12im, &p11re, &p22re); + n2 = CalcCovariance2(XBuf, &p02re, &p02im); + + /* normalize everything to larger power of 2 scalefactor, call it n1 */ + if (n1 < n2) { + nd = MIN(n2 - n1, 31); + p01re >>= nd; p01im >>= nd; + p12re >>= nd; p12im >>= nd; + p11re >>= nd; p22re >>= nd; + n1 = n2; + } else if (n1 > n2) { + nd = MIN(n1 - n2, 31); + p02re >>= nd; p02im >>= nd; + } + + /* calculate determinant of covariance matrix (at least 1 GB in pXX) */ + d = MULSHIFT32(p12re, p12re) + MULSHIFT32(p12im, p12im); + d = MULSHIFT32(d, RELAX_COEF) << 1; + d = MULSHIFT32(p11re, p22re) - d; + ASSERT(d >= 0); /* should never be < 0 */ + + zFlag = 0; + *a0re = *a0im = 0; + *a1re = *a1im = 0; + if (d > 0) { + /* input = Q31 d = Q(-2*n1 - 32 + nd) = Q31 * 2^(31 + 2*n1 + 32 - nd) + * inverse = Q29 dInv = Q29 * 2^(-31 - 2*n1 - 32 + nd) = Q(29 + 31 + 2*n1 + 32 - nd) + * + * numerator has same Q format as d, since it's sum of normalized squares + * so num * inverse = Q(-2*n1 - 32) * Q(29 + 31 + 2*n1 + 32 - nd) + * = Q(29 + 31 - nd), drop low 32 in MULSHIFT32 + * = Q(29 + 31 - 32 - nd) = Q(28 - nd) + */ + nd = CLZ(d) - 1; + d <<= nd; + dInv = InvRNormalized(d); + + /* 1 GB in pXX */ + tre = MULSHIFT32(p01re, p12re) - MULSHIFT32(p01im, p12im) - MULSHIFT32(p02re, p11re); + tre = MULSHIFT32(tre, dInv); + tim = MULSHIFT32(p01re, p12im) + MULSHIFT32(p01im, p12re) - MULSHIFT32(p02im, p11re); + tim = MULSHIFT32(tim, dInv); + + /* if d is extremely small, just set coefs to 0 (would have poor precision anyway) */ + if (nd > 28 || (FASTABS(tre) >> (28 - nd)) >= 4 || (FASTABS(tim) >> (28 - nd)) >= 4) { + zFlag = 1; + } else { + *a1re = tre << (FBITS_LPCOEFS - 28 + nd); /* i.e. convert Q(28 - nd) to Q(29) */ + *a1im = tim << (FBITS_LPCOEFS - 28 + nd); + } + } + + if (p11re) { + /* input = Q31 p11re = Q(-n1 + nd) = Q31 * 2^(31 + n1 - nd) + * inverse = Q29 dInv = Q29 * 2^(-31 - n1 + nd) = Q(29 + 31 + n1 - nd) + * + * numerator is Q(-n1 - 3) + * so num * inverse = Q(-n1 - 3) * Q(29 + 31 + n1 - nd) + * = Q(29 + 31 - 3 - nd), drop low 32 in MULSHIFT32 + * = Q(29 + 31 - 3 - 32 - nd) = Q(25 - nd) + */ + nd = CLZ(p11re) - 1; /* assume positive */ + p11re <<= nd; + dInv = InvRNormalized(p11re); + + /* a1re, a1im = Q29, so scaled by (n1 + 3) */ + tre = (p01re >> 3) + MULSHIFT32(p12re, *a1re) + MULSHIFT32(p12im, *a1im); + tre = -MULSHIFT32(tre, dInv); + tim = (p01im >> 3) - MULSHIFT32(p12im, *a1re) + MULSHIFT32(p12re, *a1im); + tim = -MULSHIFT32(tim, dInv); + + if (nd > 25 || (FASTABS(tre) >> (25 - nd)) >= 4 || (FASTABS(tim) >> (25 - nd)) >= 4) { + zFlag = 1; + } else { + *a0re = tre << (FBITS_LPCOEFS - 25 + nd); /* i.e. convert Q(25 - nd) to Q(29) */ + *a0im = tim << (FBITS_LPCOEFS - 25 + nd); + } + } + + /* see 4.6.18.6.2 - if magnitude of a0 or a1 >= 4 then a0 = a1 = 0 + * i.e. a0re < 4, a0im < 4, a1re < 4, a1im < 4 + * Q29*Q29 = Q26 + */ + if (zFlag || MULSHIFT32(*a0re, *a0re) + MULSHIFT32(*a0im, *a0im) >= MAG_16 || MULSHIFT32(*a1re, *a1re) + MULSHIFT32(*a1im, *a1im) >= MAG_16) { + *a0re = *a0im = 0; + *a1re = *a1im = 0; + } + + /* no need to clip - we never changed the XBuf data, just used it to calculate a0 and a1 */ + if (gb < 3) { + nd = 3 - gb; + for (n1 = (NUM_TIME_SLOTS*SAMPLES_PER_SLOT + 6 + 2); n1 != 0; n1--) { + XBuf[0] <<= nd; XBuf[1] <<= nd; + XBuf += (2*64); + } + } +} +/*********************************************************************************************************************** + * Function: GenerateHighFreq + * + * Description: generate high frequencies with SBR (4.6.18.6) + * + * Inputs: initialized SBRGrid struct for this channel + * initialized SBRFreq struct for this SCE/CPE block + * initialized SBRChan struct for this channel + * index of current channel (0 for SCE, 0 or 1 for CPE) + * + * Outputs: new high frequency samples starting at frequency kStart + * + * Return: none + **********************************************************************************************************************/ +void GenerateHighFreq(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int ch) { + + int band, newBW, c, t, gb, gbMask, gbIdx; + int currPatch, p, x, k, g, i, iStart, iEnd, bw, bwsq; + int a0re, a0im, a1re, a1im; + int x1re, x1im, x2re, x2im; + int ACCre, ACCim; + int *XBufLo, *XBufHi; + (void) ch; + + /* calculate array of chirp factors */ + for (band = 0; band < sbrFreq->numNoiseFloorBands; band++) { + c = sbrChan->chirpFact[band]; /* previous (bwArray') */ + newBW = newBWTab[sbrChan->invfMode[0][band]][sbrChan->invfMode[1][band]]; + + /* weighted average of new and old (can't overflow - total gain = 1.0) */ + if (newBW < c) + t = MULSHIFT32(newBW, 0x60000000) + MULSHIFT32(0x20000000, c); /* new is smaller: 0.75*new + 0.25*old */ + else + t = MULSHIFT32(newBW, 0x74000000) + MULSHIFT32(0x0c000000, c); /* new is larger: 0.90625*new + 0.09375*old */ + t <<= 1; + + if (t < 0x02000000) /* below 0.015625, clip to 0 */ + t = 0; + if (t > 0x7f800000) /* clip to 0.99609375 */ + t = 0x7f800000; + + /* save curr as prev for next time */ + sbrChan->chirpFact[band] = t; + sbrChan->invfMode[0][band] = sbrChan->invfMode[1][band]; + } + + iStart = sbrGrid->envTimeBorder[0] + HF_ADJ; + iEnd = sbrGrid->envTimeBorder[sbrGrid->numEnv] + HF_ADJ; + + /* generate new high freqs from low freqs, patches, and chirp factors */ + k = sbrFreq->kStart; + g = 0; + bw = sbrChan->chirpFact[g]; + bwsq = MULSHIFT32(bw, bw) << 1; + + gbMask = (sbrChan->gbMask[0] | sbrChan->gbMask[1]); /* older 32 | newer 8 */ + gb = CLZ(gbMask) - 1; + + for (currPatch = 0; currPatch < sbrFreq->numPatches; currPatch++) { + for (x = 0; x < sbrFreq->patchNumSubbands[currPatch]; x++) { + /* map k to corresponding noise floor band */ + if (k >= sbrFreq->freqNoise[g+1]) { + g++; + bw = sbrChan->chirpFact[g]; /* Q31 */ + bwsq = MULSHIFT32(bw, bw) << 1; /* Q31 */ + } + + p = sbrFreq->patchStartSubband[currPatch] + x; /* low QMF band */ + XBufHi = m_PSInfoSBR->XBuf[iStart][k]; + if (bw) { + CalcLPCoefs(m_PSInfoSBR->XBuf[0][p], &a0re, &a0im, &a1re, &a1im, gb); + + a0re = MULSHIFT32(bw, a0re); /* Q31 * Q29 = Q28 */ + a0im = MULSHIFT32(bw, a0im); + a1re = MULSHIFT32(bwsq, a1re); + a1im = MULSHIFT32(bwsq, a1im); + + XBufLo = m_PSInfoSBR->XBuf[iStart-2][p]; + + x2re = XBufLo[0]; /* RE{XBuf[n-2]} */ + x2im = XBufLo[1]; /* IM{XBuf[n-2]} */ + XBufLo += (64*2); + + x1re = XBufLo[0]; /* RE{XBuf[n-1]} */ + x1im = XBufLo[1]; /* IM{XBuf[n-1]} */ + XBufLo += (64*2); + + for (i = iStart; i < iEnd; i++) { + /* a0re/im, a1re/im are Q28 with at least 1 GB, + * so the summing for AACre/im is fine (1 GB in, plus 1 from MULSHIFT32) + */ + ACCre = MULSHIFT32(x2re, a1re) - MULSHIFT32(x2im, a1im); + ACCim = MULSHIFT32(x2re, a1im) + MULSHIFT32(x2im, a1re); + x2re = x1re; + x2im = x1im; + + ACCre += MULSHIFT32(x1re, a0re) - MULSHIFT32(x1im, a0im); + ACCim += MULSHIFT32(x1re, a0im) + MULSHIFT32(x1im, a0re); + x1re = XBufLo[0]; /* RE{XBuf[n]} */ + x1im = XBufLo[1]; /* IM{XBuf[n]} */ + XBufLo += (64*2); + + /* lost 4 fbits when scaling by a0re/im, a1re/im (Q28) */ + ACCre = CLIP_2N_SHIFT30(ACCre, 4); + ACCre += x1re; + ACCim = CLIP_2N_SHIFT30(ACCim, 4); + ACCim += x1im; + + XBufHi[0] = ACCre; + XBufHi[1] = ACCim; + XBufHi += (64*2); + + /* update guard bit masks */ + gbMask = FASTABS(ACCre); + gbMask |= FASTABS(ACCim); + gbIdx = (i >> 5) & 0x01; /* 0 if i < 32, 1 if i >= 32 */ + sbrChan->gbMask[gbIdx] |= gbMask; + } + } else { + XBufLo = (int *)m_PSInfoSBR->XBuf[iStart][p]; + for (i = iStart; i < iEnd; i++) { + XBufHi[0] = XBufLo[0]; + XBufHi[1] = XBufLo[1]; + XBufLo += (64*2); + XBufHi += (64*2); + } + } + k++; /* high QMF band */ + } + } +} +/*********************************************************************************************************************** + * Function: DecodeHuffmanScalar + * + * Description: decode one Huffman symbol from bitstream + * + * Inputs: pointers to Huffman table and info struct + * left-aligned bit buffer with >= huffTabInfo->maxBits bits + * + * Outputs: decoded symbol in *val + * + * Return: number of bits in symbol + * + * Notes: assumes canonical Huffman codes: + * first CW always 0, we have "count" CW's of length "nBits" bits + * starting CW for codes of length nBits+1 = + * (startCW[nBits] + count[nBits]) << 1 + * if there are no codes at nBits, then we just keep << 1 each time + * (since count[nBits] = 0) + **********************************************************************************************************************/ +int DecodeHuffmanScalar(const signed int *huffTab, const HuffInfo_t *huffTabInfo, unsigned int bitBuf, + signed int *val) { + + unsigned int count, start, shift, t; + const uint8_t *countPtr; + const signed int /*short*/*map; + + map = huffTab + huffTabInfo->offset; + countPtr = huffTabInfo->count; + + start = 0; + count = 0; + shift = 32; + do { + start += count; + start <<= 1; + map += count; + count = *countPtr++; + shift--; + t = (bitBuf >> shift) - start; + } while(t >= count); + + *val = (signed int) map[t]; + return (countPtr - huffTabInfo->count); +} +/*********************************************************************************************************************** + * Function: DecodeOneSymbol + * + * Description: dequantize one Huffman symbol from bitstream, + * using table huffTabSBR[huffTabIndex] + * + * Inputs: index of Huffman table + * + * Outputs: bitstream advanced by number of bits in codeword + * + * Return: one decoded symbol + **********************************************************************************************************************/ +int DecodeOneSymbol(int huffTabIndex) { + + int nBits, val; + unsigned int bitBuf; + const HuffInfo_t *hi; + + hi = &(huffTabSBRInfo[huffTabIndex]); + + bitBuf = GetBitsNoAdvance(hi->maxBits) << (32 - hi->maxBits); + nBits = DecodeHuffmanScalar(huffTabSBR, hi, bitBuf, &val); + AdvanceBitstream(nBits); + + return val; +} + +/* [1.0, sqrt(2)], format = Q29 (one guard bit for decoupling) */ +static const int envDQTab[2] PROGMEM = {0x20000000, 0x2d413ccc}; + +/*********************************************************************************************************************** + * Function: DequantizeEnvelope + * + * Description: dequantize envelope scalefactors + * + * Inputs: number of scalefactors to process + * amplitude resolution flag for this frame (0 or 1) + * quantized envelope scalefactors + * + * Outputs: dequantized envelope scalefactors + * + * Return: extra int bits in output (6 + expMax) + * in other words, output format = Q(FBITS_OUT_DQ_ENV - (6 + expMax)) + * + * Notes: dequantized scalefactors have at least 2 GB + **********************************************************************************************************************/ +int DequantizeEnvelope(int nBands, int ampRes, int8_t *envQuant, int *envDequant) { + + int exp, expMax, i, scalei; + + if(nBands <= 0) return 0; + + /* scan for largest dequant value (do separately from envelope decoding to keep code cleaner) */ + expMax = 0; + for(i = 0; i < nBands; i++) { + if(envQuant[i] > expMax) expMax = envQuant[i]; + } + + /* dequantized envelope gains + * envDequant = 64*2^(envQuant / alpha) = 2^(6 + envQuant / alpha) + * if ampRes == 0, alpha = 2 and range of envQuant = [0, 127] + * if ampRes == 1, alpha = 1 and range of envQuant = [0, 63] + * also if coupling is on, envDequant is scaled by something in range [0, 2] + * so range of envDequant = [2^6, 2^69] (no coupling), [2^6, 2^70] (with coupling) + * + * typical range (from observation) of envQuant/alpha = [0, 27] --> largest envQuant ~= 2^33 + * output: Q(29 - (6 + expMax)) + * + * reference: 14496-3:2001(E)/4.6.18.3.5 and 14496-4:200X/FPDAM8/5.6.5.1.2.1.5 + */ + if(ampRes) { + do { + exp = *envQuant++; + scalei = MIN(expMax - exp, 31); + *envDequant++ = envDQTab[0] >> scalei; + } while(--nBands); + + return (6 + expMax); + } + else { + expMax >>= 1; + do { + exp = *envQuant++; + scalei = MIN(expMax - (exp >> 1), 31); + *envDequant++ = envDQTab[exp & 0x01] >> scalei; + } while(--nBands); + + return (6 + expMax); + } + +} +/*********************************************************************************************************************** + * Function: DequantizeNoise + * + * Description: dequantize noise scalefactors + * + * Inputs: number of scalefactors to process + * quantized noise scalefactors + * + * Outputs: dequantized noise scalefactors, format = Q(FBITS_OUT_DQ_NOISE) + * + * Return: none + * + * Notes: dequantized scalefactors have at least 2 GB + **********************************************************************************************************************/ +void DequantizeNoise(int nBands, int8_t *noiseQuant, int *noiseDequant) { + + int exp, scalei; + + if(nBands <= 0) return; + + /* dequantize noise floor gains (4.6.18.3.5): + * noiseDequant = 2^(NOISE_FLOOR_OFFSET - noiseQuant) + * + * range of noiseQuant = [0, 30] (see 4.6.18.3.6), NOISE_FLOOR_OFFSET = 6 + * so range of noiseDequant = [2^-24, 2^6] + */ + do { + exp = *noiseQuant++; + scalei = NOISE_FLOOR_OFFSET - exp + FBITS_OUT_DQ_NOISE; /* 6 + 24 - exp, exp = [0,30] */ + + if(scalei < 0) + *noiseDequant++ = 0; + else if(scalei < 30) + *noiseDequant++ = 1 << scalei; + else + *noiseDequant++ = 0x3fffffff; /* leave 2 GB */ + + } while(--nBands); +} +/*********************************************************************************************************************** + * Function: DecodeSBREnvelope + * + * Description: decode delta Huffman coded envelope scalefactors from bitstream + * + * Inputs: initialized SBRGrid struct for this channel + * initialized SBRFreq struct for this SCE/CPE block + * initialized SBRChan struct for this channel + * index of current channel (0 for SCE, 0 or 1 for CPE) + * + * Outputs: dequantized env scalefactors for left channel (before decoupling) + * dequantized env scalefactors for right channel (if coupling off) + * or raw decoded env scalefactors for right channel (if coupling on) + * + * Return: none + **********************************************************************************************************************/ +void DecodeSBREnvelope(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int ch) { + + int huffIndexTime, huffIndexFreq, env, envStartBits, band, nBands, sf, lastEnv; + int freqRes, freqResPrev, dShift, i; + + if(m_PSInfoSBR->couplingFlag && ch) { + dShift = 1; + if(sbrGrid->ampResFrame) { + huffIndexTime = HuffTabSBR_tEnv30b; + huffIndexFreq = HuffTabSBR_fEnv30b; + envStartBits = 5; + } + else { + huffIndexTime = HuffTabSBR_tEnv15b; + huffIndexFreq = HuffTabSBR_fEnv15b; + envStartBits = 6; + } + } + else { + dShift = 0; + if(sbrGrid->ampResFrame) { + huffIndexTime = HuffTabSBR_tEnv30; + huffIndexFreq = HuffTabSBR_fEnv30; + envStartBits = 6; + } + else { + huffIndexTime = HuffTabSBR_tEnv15; + huffIndexFreq = HuffTabSBR_fEnv15; + envStartBits = 7; + } + } + + /* range of envDataQuant[] = [0, 127] (see comments in DequantizeEnvelope() for reference) */ + for(env = 0; env < sbrGrid->numEnv; env++) { + nBands = (sbrGrid->freqRes[env] ? sbrFreq->nHigh : sbrFreq->nLow); + freqRes = (sbrGrid->freqRes[env]); + freqResPrev = (env == 0 ? sbrGrid->freqResPrev : sbrGrid->freqRes[env - 1]); + lastEnv = (env == 0 ? sbrGrid->numEnvPrev - 1 : env - 1); + if(lastEnv < 0) lastEnv = 0; /* first frame */ + + ASSERT(nBands <= MAX_QMF_BANDS); + + if(sbrChan->deltaFlagEnv[env] == 0) { + /* delta coding in freq */ + sf = GetBits(envStartBits) << dShift; + sbrChan->envDataQuant[env][0] = sf; + for(band = 1; band < nBands; band++) { + sf = DecodeOneSymbol(huffIndexFreq) << dShift; + sbrChan->envDataQuant[env][band] = sf + sbrChan->envDataQuant[env][band - 1]; + } + } + else if(freqRes == freqResPrev) { + /* delta coding in time - same freq resolution for both frames */ + for(band = 0; band < nBands; band++) { + sf = DecodeOneSymbol(huffIndexTime) << dShift; + sbrChan->envDataQuant[env][band] = sf + sbrChan->envDataQuant[lastEnv][band]; + } + } + else if(freqRes == 0 && freqResPrev == 1) { + /* delta coding in time - low freq resolution for new frame, high freq resolution for old frame */ + for(band = 0; band < nBands; band++) { + sf = DecodeOneSymbol(huffIndexTime) << dShift; + sbrChan->envDataQuant[env][band] = sf; + for(i = 0; i < sbrFreq->nHigh; i++) { + if(sbrFreq->freqHigh[i] == sbrFreq->freqLow[band]) { + sbrChan->envDataQuant[env][band] += sbrChan->envDataQuant[lastEnv][i]; + break; + } + } + } + } + else if(freqRes == 1 && freqResPrev == 0) { + /* delta coding in time - high freq resolution for new frame, low freq resolution for old frame */ + for(band = 0; band < nBands; band++) { + sf = DecodeOneSymbol(huffIndexTime) << dShift; + sbrChan->envDataQuant[env][band] = sf; + for(i = 0; i < sbrFreq->nLow; i++) { + if(sbrFreq->freqLow[i] <= sbrFreq->freqHigh[band] + && sbrFreq->freqHigh[band] < sbrFreq->freqLow[i + 1]) { + sbrChan->envDataQuant[env][band] += sbrChan->envDataQuant[lastEnv][i]; + break; + } + } + } + } + + /* skip coupling channel */ + if(ch != 1 || m_PSInfoSBR->couplingFlag != 1) + m_PSInfoSBR->envDataDequantScale[ch][env] = DequantizeEnvelope(nBands, sbrGrid->ampResFrame, + sbrChan->envDataQuant[env], m_PSInfoSBR->envDataDequant[ch][env]); + } + sbrGrid->numEnvPrev = sbrGrid->numEnv; + sbrGrid->freqResPrev = sbrGrid->freqRes[sbrGrid->numEnv - 1]; +} +/*********************************************************************************************************************** + * Function: DecodeSBRNoise + * + * Description: decode delta Huffman coded noise scalefactors from bitstream + * + * Inputs: initialized SBRGrid struct for this channel + * initialized SBRFreq struct for this SCE/CPE block + * initialized SBRChan struct for this channel + * index of current channel (0 for SCE, 0 or 1 for CPE) + * + * Outputs: dequantized noise scalefactors for left channel (before decoupling) + * dequantized noise scalefactors for right channel (if coupling off) + * or raw decoded noise scalefactors for right channel (if coupling on) + * + * Return: none + **********************************************************************************************************************/ +void DecodeSBRNoise(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int ch) { + + int huffIndexTime, huffIndexFreq, noiseFloor, band, dShift, sf, lastNoiseFloor; + + if(m_PSInfoSBR->couplingFlag && ch) { + dShift = 1; + huffIndexTime = HuffTabSBR_tNoise30b; + huffIndexFreq = HuffTabSBR_fNoise30b; + } + else { + dShift = 0; + huffIndexTime = HuffTabSBR_tNoise30; + huffIndexFreq = HuffTabSBR_fNoise30; + } + + for(noiseFloor = 0; noiseFloor < sbrGrid->numNoiseFloors; noiseFloor++) { + lastNoiseFloor = (noiseFloor == 0 ? sbrGrid->numNoiseFloorsPrev - 1 : noiseFloor - 1); + if(lastNoiseFloor < 0) lastNoiseFloor = 0; /* first frame */ + + ASSERT(sbrFreq->numNoiseFloorBands <= MAX_QMF_BANDS); + + if(sbrChan->deltaFlagNoise[noiseFloor] == 0) { + /* delta coding in freq */ + sbrChan->noiseDataQuant[noiseFloor][0] = GetBits(5) << dShift; + for(band = 1; band < sbrFreq->numNoiseFloorBands; band++) { + sf = DecodeOneSymbol(huffIndexFreq) << dShift; + sbrChan->noiseDataQuant[noiseFloor][band] = sf + sbrChan->noiseDataQuant[noiseFloor][band - 1]; + } + } + else { + /* delta coding in time */ + for(band = 0; band < sbrFreq->numNoiseFloorBands; band++) { + sf = DecodeOneSymbol(huffIndexTime) << dShift; + sbrChan->noiseDataQuant[noiseFloor][band] = sf + sbrChan->noiseDataQuant[lastNoiseFloor][band]; + } + } + + /* skip coupling channel */ + if(ch != 1 || m_PSInfoSBR->couplingFlag != 1) + DequantizeNoise(sbrFreq->numNoiseFloorBands, sbrChan->noiseDataQuant[noiseFloor], + m_PSInfoSBR->noiseDataDequant[ch][noiseFloor]); + } + sbrGrid->numNoiseFloorsPrev = sbrGrid->numNoiseFloors; +} + +/* dqTabCouple[i] = 2 / (1 + 2^(12 - i)), format = Q30 */ +static const int dqTabCouple[25] PROGMEM = { + 0x0007ff80, 0x000ffe00, 0x001ff802, 0x003fe010, 0x007f8080, 0x00fe03f8, 0x01f81f82, 0x03e0f83e, + 0x07878788, 0x0e38e38e, 0x1999999a, 0x2aaaaaab, 0x40000000, 0x55555555, 0x66666666, 0x71c71c72, + 0x78787878, 0x7c1f07c2, 0x7e07e07e, 0x7f01fc08, 0x7f807f80, 0x7fc01ff0, 0x7fe007fe, 0x7ff00200, + 0x7ff80080, +}; + +/*********************************************************************************************************************** + * Function: UncoupleSBREnvelope + * + * Description: scale dequantized envelope scalefactors according to channel + * coupling rules + * + * Inputs: initialized SBRGrid struct for this channel + * initialized SBRFreq struct for this SCE/CPE block + * initialized SBRChan struct for right channel including + * quantized envelope scalefactors + * + * Outputs: dequantized envelope data for left channel (after decoupling) + * dequantized envelope data for right channel (after decoupling) + * + * Return: none + **********************************************************************************************************************/ +void UncoupleSBREnvelope(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChanR) { + + int env, band, nBands, scalei, E_1; + + scalei = (sbrGrid->ampResFrame ? 0 : 1); + for(env = 0; env < sbrGrid->numEnv; env++) { + nBands = (sbrGrid->freqRes[env] ? sbrFreq->nHigh : sbrFreq->nLow); + m_PSInfoSBR->envDataDequantScale[1][env] = m_PSInfoSBR->envDataDequantScale[0][env]; + for(band = 0; band < nBands; band++) { + /* clip E_1 to [0, 24] (scalefactors approach 0 or 2) */ + E_1 = sbrChanR->envDataQuant[env][band] >> scalei; + if(E_1 < 0) E_1 = 0; + if(E_1 > 24) E_1 = 24; + + /* envDataDequant[0] has 1 GB, so << by 2 is okay */ + m_PSInfoSBR->envDataDequant[1][env][band] = MULSHIFT32(m_PSInfoSBR->envDataDequant[0][env][band], + dqTabCouple[24 - E_1]) << 2; + m_PSInfoSBR->envDataDequant[0][env][band] = MULSHIFT32(m_PSInfoSBR->envDataDequant[0][env][band], + dqTabCouple[E_1]) << 2; + } + } +} +/*********************************************************************************************************************** + * Function: UncoupleSBRNoise + * + * Description: scale dequantized noise floor scalefactors according to channel + * coupling rules + * + * Inputs: initialized SBRGrid struct for this channel + * initialized SBRFreq struct for this SCE/CPE block + * initialized SBRChan struct for this channel including + * quantized noise scalefactors + * + * Outputs: dequantized noise data for left channel (after decoupling) + * dequantized noise data for right channel (after decoupling) + * + * Return: none + **********************************************************************************************************************/ +void UncoupleSBRNoise(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChanR) { + + int noiseFloor, band, Q_1; + + for (noiseFloor = 0; noiseFloor < sbrGrid->numNoiseFloors; noiseFloor++) { + for (band = 0; band < sbrFreq->numNoiseFloorBands; band++) { + /* Q_1 should be in range [0, 24] according to 4.6.18.3.6, but check to make sure */ + Q_1 = sbrChanR->noiseDataQuant[noiseFloor][band]; + if (Q_1 < 0) Q_1 = 0; + if (Q_1 > 24) Q_1 = 24; + + /* noiseDataDequant[0] has 1 GB, so << by 2 is okay */ + m_PSInfoSBR->noiseDataDequant[1][noiseFloor][band] = + MULSHIFT32(m_PSInfoSBR->noiseDataDequant[0][noiseFloor][band], dqTabCouple[24 - Q_1]) << 2; + m_PSInfoSBR->noiseDataDequant[0][noiseFloor][band] = + MULSHIFT32(m_PSInfoSBR->noiseDataDequant[0][noiseFloor][band], dqTabCouple[Q_1]) << 2; + } + } +} +/*********************************************************************************************************************** + * Function: DecWindowOverlapNoClip + * + * Description: apply synthesis window, do overlap-add without clipping, + * for winSequence LONG-LONG + * + * Inputs: input buffer (output of type-IV DCT) + * overlap buffer (saved from last time) + * window type (sin or KBD) for input buffer + * window type (sin or KBD) for overlap buffer + * + * Outputs: one channel, one frame of 32-bit PCM, non-interleaved + * + * Return: none + * + * Notes: use this function when the decoded PCM is going to the SBR decoder + **********************************************************************************************************************/ +void DecWindowOverlapNoClip(int *buf0, int *over0, int *out0, int winTypeCurr, int winTypePrev) { + + int in, w0, w1, f0, f1; + int *buf1, *over1, *out1; + const int *wndPrev, *wndCurr; + + buf0 += (1024 >> 1); + buf1 = buf0 - 1; + out1 = out0 + 1024 - 1; + over1 = over0 + 1024 - 1; + + wndPrev = (winTypePrev == 1 ? kbdWindow + kbdWindowOffset[1] : sinWindow + sinWindowOffset[1]); + if (winTypeCurr == winTypePrev) { + /* cut window loads in half since current and overlap sections use same symmetric window */ + do { + w0 = *wndPrev++; + w1 = *wndPrev++; + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *over0; + *out0++ = in - f0; + + in = *over1; + *out1-- = in + f1; + + in = *buf1--; + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (over0 < over1); + } else { + /* different windows for current and overlap parts - should still fit in registers on ARM w/o stack spill */ + wndCurr = (winTypeCurr == 1 ? kbdWindow + kbdWindowOffset[1] : sinWindow + sinWindowOffset[1]); + do { + w0 = *wndPrev++; + w1 = *wndPrev++; + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *over0; + *out0++ = in - f0; + + in = *over1; + *out1-- = in + f1; + + w0 = *wndCurr++; + w1 = *wndCurr++; + in = *buf1--; + + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (over0 < over1); + } +} +/*********************************************************************************************************************** + * Function: DecWindowOverlapLongStart + * + * Description: apply synthesis window, do overlap-add, without clipping + * for winSequence LONG-START + * + * Inputs: input buffer (output of type-IV DCT) + * overlap buffer (saved from last time) + * window type (sin or KBD) for input buffer + * window type (sin or KBD) for overlap buffer + * + * Outputs: one channel, one frame of 32-bit PCM, non-interleaved + * + * Return: none + * + * Notes: use this function when the decoded PCM is going to the SBR decoder + **********************************************************************************************************************/ +void DecWindowOverlapLongStartNoClip(int *buf0, int *over0, int *out0, int winTypeCurr, int winTypePrev) { + + int i, in, w0, w1, f0, f1; + int *buf1, *over1, *out1; + const int *wndPrev, *wndCurr; + + buf0 += (1024 >> 1); + buf1 = buf0 - 1; + out1 = out0 + 1024 - 1; + over1 = over0 + 1024 - 1; + + wndPrev = (winTypePrev == 1 ? kbdWindow + kbdWindowOffset[1] : sinWindow + sinWindowOffset[1]); + i = 448; /* 2 outputs, 2 overlaps per loop */ + do { + w0 = *wndPrev++; + w1 = *wndPrev++; + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *over0; + *out0++ = in - f0; + + in = *over1; + *out1-- = in + f1; + + in = *buf1--; + + *over1-- = 0; /* Wn = 0 for n = (2047, 2046, ... 1600) */ + *over0++ = in >> 1; /* Wn = 1 for n = (1024, 1025, ... 1471) */ + } while (--i); + + wndCurr = (winTypeCurr == 1 ? kbdWindow + kbdWindowOffset[0] : sinWindow + sinWindowOffset[0]); + + /* do 64 more loops - 2 outputs, 2 overlaps per loop */ + do { + w0 = *wndPrev++; + w1 = *wndPrev++; + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *over0; + *out0++ = in - f0; + + in = *over1; + *out1-- = in + f1; + + w0 = *wndCurr++; /* W[0], W[1], ... --> W[255], W[254], ... */ + w1 = *wndCurr++; /* W[127], W[126], ... --> W[128], W[129], ... */ + in = *buf1--; + + *over1-- = MULSHIFT32(w0, in); /* Wn = short window for n = (1599, 1598, ... , 1536) */ + *over0++ = MULSHIFT32(w1, in); /* Wn = short window for n = (1472, 1473, ... , 1535) */ + } while (over0 < over1); +} +/*********************************************************************************************************************** + * Function: DecWindowOverlapLongStop + * + * Description: apply synthesis window, do overlap-add, without clipping + * for winSequence LONG-STOP + * + * Inputs: input buffer (output of type-IV DCT) + * overlap buffer (saved from last time) + * window type (sin or KBD) for input buffer + * window type (sin or KBD) for overlap buffer + * + * Outputs: one channel, one frame of 32-bit PCM, non-interleaved + * + * Return: none + * + * Notes: use this function when the decoded PCM is going to the SBR decoder + **********************************************************************************************************************/ +void DecWindowOverlapLongStopNoClip(int *buf0, int *over0, int *out0, int winTypeCurr, int winTypePrev) { + + int i, in, w0, w1, f0, f1; + int *buf1, *over1, *out1; + const int *wndPrev, *wndCurr; + + buf0 += (1024 >> 1); + buf1 = buf0 - 1; + out1 = out0 + 1024 - 1; + over1 = over0 + 1024 - 1; + + wndPrev = (winTypePrev == 1 ? kbdWindow + kbdWindowOffset[0] : sinWindow + sinWindowOffset[0]); + wndCurr = (winTypeCurr == 1 ? kbdWindow + kbdWindowOffset[1] : sinWindow + sinWindowOffset[1]); + + i = 448; /* 2 outputs, 2 overlaps per loop */ + do { + /* Wn = 0 for n = (0, 1, ... 447) */ + /* Wn = 1 for n = (576, 577, ... 1023) */ + in = *buf0++; + f1 = in >> 1; /* scale since skipping multiply by Q31 */ + + in = *over0; + *out0++ = in; + + in = *over1; + *out1-- = in + f1; + + w0 = *wndCurr++; + w1 = *wndCurr++; + in = *buf1--; + + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (--i); + + /* do 64 more loops - 2 outputs, 2 overlaps per loop */ + do { + w0 = *wndPrev++; /* W[0], W[1], ...W[63] */ + w1 = *wndPrev++; /* W[127], W[126], ... W[64] */ + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *over0; + *out0++ = in - f0; + + in = *over1; + *out1-- = in + f1; + + w0 = *wndCurr++; + w1 = *wndCurr++; + in = *buf1--; + + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (over0 < over1); +} +/*********************************************************************************************************************** + * Function: DecWindowOverlapShort + * + * Description: apply synthesis window, do overlap-add, without clipping + * for winSequence EIGHT-SHORT (does all 8 short blocks) + * + * Inputs: input buffer (output of type-IV DCT) + * overlap buffer (saved from last time) + * window type (sin or KBD) for input buffer + * window type (sin or KBD) for overlap buffer + * + * Outputs: one channel, one frame of 32-bit PCM, non-interleaved + * + * Return: none + * + * Notes: use this function when the decoded PCM is going to the SBR decoder + **********************************************************************************************************************/ +void DecWindowOverlapShortNoClip(int *buf0, int *over0, int *out0, int winTypeCurr, int winTypePrev) { + + int i, in, w0, w1, f0, f1; + int *buf1, *over1, *out1; + const int *wndPrev, *wndCurr; + + wndPrev = (winTypePrev == 1 ? kbdWindow + kbdWindowOffset[0] : sinWindow + sinWindowOffset[0]); + wndCurr = (winTypeCurr == 1 ? kbdWindow + kbdWindowOffset[0] : sinWindow + sinWindowOffset[0]); + + /* pcm[0-447] = 0 + overlap[0-447] */ + i = 448; + do { + f0 = *over0++; + f1 = *over0++; + *out0++ = f0; + *out0++ = f1; + i -= 2; + } while (i); + + /* pcm[448-575] = Wp[0-127] * block0[0-127] + overlap[448-575] */ + out1 = out0 + (128 - 1); + over1 = over0 + 128 - 1; + buf0 += 64; + buf1 = buf0 - 1; + do { + w0 = *wndPrev++; /* W[0], W[1], ...W[63] */ + w1 = *wndPrev++; /* W[127], W[126], ... W[64] */ + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *over0; + *out0++ = in - f0; + + in = *over1; + *out1-- = in + f1; + + w0 = *wndCurr++; + w1 = *wndCurr++; + in = *buf1--; + + /* save over0/over1 for next short block, in the slots just vacated */ + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (over0 < over1); + + /* pcm[576-703] = Wc[128-255] * block0[128-255] + Wc[0-127] * block1[0-127] + overlap[576-703] + * pcm[704-831] = Wc[128-255] * block1[128-255] + Wc[0-127] * block2[0-127] + overlap[704-831] + * pcm[832-959] = Wc[128-255] * block2[128-255] + Wc[0-127] * block3[0-127] + overlap[832-959] + */ + for (i = 0; i < 3; i++) { + out0 += 64; + out1 = out0 + 128 - 1; + over0 += 64; + over1 = over0 + 128 - 1; + buf0 += 64; + buf1 = buf0 - 1; + wndCurr -= 128; + + do { + w0 = *wndCurr++; /* W[0], W[1], ...W[63] */ + w1 = *wndCurr++; /* W[127], W[126], ... W[64] */ + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *(over0 - 128); /* from last short block */ + in += *(over0 + 0); /* from last full frame */ + *out0++ = in - f0; + + in = *(over1 - 128); /* from last short block */ + in += *(over1 + 0); /* from last full frame */ + *out1-- = in + f1; + + /* save over0/over1 for next short block, in the slots just vacated */ + in = *buf1--; + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (over0 < over1); + } + + /* pcm[960-1023] = Wc[128-191] * block3[128-191] + Wc[0-63] * block4[0-63] + overlap[960-1023] + * over[0-63] = Wc[192-255] * block3[192-255] + Wc[64-127] * block4[64-127] + */ + out0 += 64; + over0 -= 832; /* points at overlap[64] */ + over1 = over0 + 128 - 1; /* points at overlap[191] */ + buf0 += 64; + buf1 = buf0 - 1; + wndCurr -= 128; + do { + w0 = *wndCurr++; /* W[0], W[1], ...W[63] */ + w1 = *wndCurr++; /* W[127], W[126], ... W[64] */ + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *(over0 + 768); /* from last short block */ + in += *(over0 + 896); /* from last full frame */ + *out0++ = in - f0; + + in = *(over1 + 768); /* from last short block */ + *(over1 - 128) = in + f1; + + in = *buf1--; + *over1-- = MULSHIFT32(w0, in); /* save in overlap[128-191] */ + *over0++ = MULSHIFT32(w1, in); /* save in overlap[64-127] */ + } while (over0 < over1); + + /* over0 now points at overlap[128] */ + + /* over[64-191] = Wc[128-255] * block4[128-255] + Wc[0-127] * block5[0-127] + * over[192-319] = Wc[128-255] * block5[128-255] + Wc[0-127] * block6[0-127] + * over[320-447] = Wc[128-255] * block6[128-255] + Wc[0-127] * block7[0-127] + * over[448-576] = Wc[128-255] * block7[128-255] + */ + for (i = 0; i < 3; i++) { + over0 += 64; + over1 = over0 + 128 - 1; + buf0 += 64; + buf1 = buf0 - 1; + wndCurr -= 128; + do { + w0 = *wndCurr++; /* W[0], W[1], ...W[63] */ + w1 = *wndCurr++; /* W[127], W[126], ... W[64] */ + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + /* from last short block */ + *(over0 - 128) -= f0; + *(over1 - 128)+= f1; + + in = *buf1--; + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (over0 < over1); + } + + /* over[576-1024] = 0 */ + i = 448; + over0 += 64; + do { + *over0++ = 0; + *over0++ = 0; + *over0++ = 0; + *over0++ = 0; + i -= 4; + } while (i); +} +/*********************************************************************************************************************** + * Function: PreMultiply64 + * + * Description: pre-twiddle stage of 64-point DCT-IV + * + * Inputs: buffer of 64 samples + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: minimum 1 GB in, 2 GB out, gains 2 int bits + * gbOut = gbIn + 1 + * output is limited to sqrt(2)/2 plus GB in full GB + * uses 3-mul, 3-add butterflies instead of 4-mul, 2-add + **********************************************************************************************************************/ +void PreMultiply64(int *zbuf1) { + + int i, ar1, ai1, ar2, ai2, z1, z2; + int t, cms2, cps2a, sin2a, cps2b, sin2b; + int *zbuf2; + const int *csptr; + + zbuf2 = zbuf1 + 64 - 1; + csptr = cos4sin4tab64; + + /* whole thing should fit in registers - verify that compiler does this */ + for (i = 64 >> 2; i != 0; i--) { + /* cps2 = (cos+sin), sin2 = sin, cms2 = (cos-sin) */ + cps2a = *csptr++; + sin2a = *csptr++; + cps2b = *csptr++; + sin2b = *csptr++; + + ar1 = *(zbuf1 + 0); + ai2 = *(zbuf1 + 1); + ai1 = *(zbuf2 + 0); + ar2 = *(zbuf2 - 1); + + /* gain 2 ints bit from MULSHIFT32 by Q30 + * max per-sample gain (ignoring implicit scaling) = MAX(sin(angle)+cos(angle)) = 1.414 + * i.e. gain 1 GB since worst case is sin(angle) = cos(angle) = 0.707 (Q30), gain 2 from + * extra sign bits, and eat one in adding + */ + t = MULSHIFT32(sin2a, ar1 + ai1); + z2 = MULSHIFT32(cps2a, ai1) - t; + cms2 = cps2a - 2*sin2a; + z1 = MULSHIFT32(cms2, ar1) + t; + *zbuf1++ = z1; /* cos*ar1 + sin*ai1 */ + *zbuf1++ = z2; /* cos*ai1 - sin*ar1 */ + + t = MULSHIFT32(sin2b, ar2 + ai2); + z2 = MULSHIFT32(cps2b, ai2) - t; + cms2 = cps2b - 2*sin2b; + z1 = MULSHIFT32(cms2, ar2) + t; + *zbuf2-- = z2; /* cos*ai2 - sin*ar2 */ + *zbuf2-- = z1; /* cos*ar2 + sin*ai2 */ + } +} +/*********************************************************************************************************************** + * Function: PostMultiply64 + * + * Description: post-twiddle stage of 64-point type-IV DCT + * + * Inputs: buffer of 64 samples + * number of output samples to calculate + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: minimum 1 GB in, 2 GB out, gains 2 int bits + * gbOut = gbIn + 1 + * output is limited to sqrt(2)/2 plus GB in full GB + * nSampsOut is rounded up to next multiple of 4, since we calculate + * 4 samples per loop + **********************************************************************************************************************/ +void PostMultiply64(int *fft1, int nSampsOut) { + + int i, ar1, ai1, ar2, ai2; + int t, cms2, cps2, sin2; + int *fft2; + const int *csptr; + + csptr = cos1sin1tab64; + fft2 = fft1 + 64 - 1; + + /* load coeffs for first pass + * cps2 = (cos+sin)/2, sin2 = sin/2, cms2 = (cos-sin)/2 + */ + cps2 = *csptr++; + sin2 = *csptr++; + cms2 = cps2 - 2*sin2; + + for (i = (nSampsOut + 3) >> 2; i != 0; i--) { + ar1 = *(fft1 + 0); + ai1 = *(fft1 + 1); + ar2 = *(fft2 - 1); + ai2 = *(fft2 + 0); + + /* gain 2 int bits (multiplying by Q30), max gain = sqrt(2) */ + t = MULSHIFT32(sin2, ar1 + ai1); + *fft2-- = t - MULSHIFT32(cps2, ai1); + *fft1++ = t + MULSHIFT32(cms2, ar1); + + cps2 = *csptr++; + sin2 = *csptr++; + + ai2 = -ai2; + t = MULSHIFT32(sin2, ar2 + ai2); + *fft2-- = t - MULSHIFT32(cps2, ai2); + cms2 = cps2 - 2*sin2; + *fft1++ = t + MULSHIFT32(cms2, ar2); + } +} +/*********************************************************************************************************************** + * Function: QMFAnalysisConv + * + * Description: convolution kernel for analysis QMF + * + * Inputs: pointer to coefficient table, reordered for sequential access + * delay buffer of size 32*10 = 320 real-valued PCM samples + * index for delay ring buffer (range = [0, 9]) + * + * Outputs: 64 consecutive 32-bit samples + * + * Return: none + * + * Notes: this is carefully written to be efficient on ARM + * use the assembly code version in sbrqmfak.s when building for ARM! + **********************************************************************************************************************/ +void QMFAnalysisConv(int *cTab, int *delay, int dIdx, int *uBuf) { + + int k, dOff; + int *cPtr0, *cPtr1; + U64 u64lo, u64hi; + + dOff = dIdx*32 + 31; + cPtr0 = cTab; + cPtr1 = cTab + 33*5 - 1; + + /* special first pass since we need to flip sign to create cTab[384], cTab[512] */ + u64lo.w64 = 0; + u64hi.w64 = 0; + u64lo.w64 = MADD64(u64lo.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64hi.w64 = MADD64(u64hi.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64lo.w64 = MADD64(u64lo.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64hi.w64 = MADD64(u64hi.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64lo.w64 = MADD64(u64lo.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64hi.w64 = MADD64(u64hi.w64, *cPtr1--, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64lo.w64 = MADD64(u64lo.w64, -(*cPtr1--), delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64hi.w64 = MADD64(u64hi.w64, *cPtr1--, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64lo.w64 = MADD64(u64lo.w64, -(*cPtr1--), delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64hi.w64 = MADD64(u64hi.w64, *cPtr1--, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + + uBuf[0] = u64lo.r.hi32; + uBuf[32] = u64hi.r.hi32; + uBuf++; + dOff--; + + /* max gain for any sample in uBuf, after scaling by cTab, ~= 0.99 + * so we can just sum the uBuf values with no overflow problems + */ + for (k = 1; k <= 31; k++) { + u64lo.w64 = 0; + u64hi.w64 = 0; + u64lo.w64 = MADD64(u64lo.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64hi.w64 = MADD64(u64hi.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64lo.w64 = MADD64(u64lo.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64hi.w64 = MADD64(u64hi.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64lo.w64 = MADD64(u64lo.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64hi.w64 = MADD64(u64hi.w64, *cPtr1--, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64lo.w64 = MADD64(u64lo.w64, *cPtr1--, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64hi.w64 = MADD64(u64hi.w64, *cPtr1--, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64lo.w64 = MADD64(u64lo.w64, *cPtr1--, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64hi.w64 = MADD64(u64hi.w64, *cPtr1--, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + + uBuf[0] = u64lo.r.hi32; + uBuf[32] = u64hi.r.hi32; + uBuf++; + dOff--; + } +} +/*********************************************************************************************************************** + * Function: QMFAnalysis + * + * Description: 32-subband analysis QMF (4.6.18.4.1) + * + * Inputs: 32 consecutive samples of decoded 32-bit PCM, format = Q(fBitsIn) + * delay buffer of size 32*10 = 320 PCM samples + * number of fraction bits in input PCM + * index for delay ring buffer (range = [0, 9]) + * number of subbands to calculate (range = [0, 32]) + * + * Outputs: qmfaBands complex subband samples, format = Q(FBITS_OUT_QMFA) + * updated delay buffer + * updated delay index + * + * Return: guard bit mask + * + * Notes: output stored as RE{X0}, IM{X0}, RE{X1}, IM{X1}, ... RE{X31}, IM{X31} + * output stored in int buffer of size 64*2 = 128 + * (zero-filled from XBuf[2*qmfaBands] to XBuf[127]) + **********************************************************************************************************************/ +int QMFAnalysis(int *inbuf, int *delay, int *XBuf, int fBitsIn, int *delayIdx, int qmfaBands) { + + int n, y, shift, gbMask; + int *delayPtr, *uBuf, *tBuf; + + /* use XBuf[128] as temp buffer for reordering */ + uBuf = XBuf; /* first 64 samples */ + tBuf = XBuf + 64; /* second 64 samples */ + + /* overwrite oldest PCM with new PCM + * delay[n] has 1 GB after shifting (either << or >>) + */ + delayPtr = delay + (*delayIdx * 32); + if (fBitsIn > FBITS_IN_QMFA) { + shift = MIN(fBitsIn - FBITS_IN_QMFA, 31); + for (n = 32; n != 0; n--) { + y = (*inbuf) >> shift; + inbuf++; + *delayPtr++ = y; + } + } else { + shift = MIN(FBITS_IN_QMFA - fBitsIn, 30); + for (n = 32; n != 0; n--) { + y = *inbuf++; + y = CLIP_2N_SHIFT30(y, shift); + *delayPtr++ = y; + } + } + + QMFAnalysisConv((int *)cTabA, delay, *delayIdx, uBuf); + + /* uBuf has at least 2 GB right now (1 from clipping to Q(FBITS_IN_QMFA), one from + * the scaling by cTab (MULSHIFT32(*delayPtr--, *cPtr++), with net gain of < 1.0) + */ + tBuf[2*0 + 0] = uBuf[0]; + tBuf[2*0 + 1] = uBuf[1]; + for (n = 1; n < 31; n++) { + tBuf[2*n + 0] = -uBuf[64-n]; + tBuf[2*n + 1] = uBuf[n+1]; + } + tBuf[2*31 + 1] = uBuf[32]; + tBuf[2*31 + 0] = -uBuf[33]; + + /* fast in-place DCT-IV - only need 2*qmfaBands output samples */ + PreMultiply64(tBuf); /* 2 GB in, 3 GB out */ + FFT32C(tBuf); /* 3 GB in, 1 GB out */ + PostMultiply64(tBuf, qmfaBands*2); /* 1 GB in, 2 GB out */ + + gbMask = 0; + for (n = 0; n < qmfaBands; n++) { + XBuf[2*n+0] = tBuf[ n + 0]; /* implicit scaling of 2 in our output Q format */ + gbMask |= FASTABS(XBuf[2*n+0]); + XBuf[2*n+1] = -tBuf[63 - n]; + gbMask |= FASTABS(XBuf[2*n+1]); + } + + /* fill top section with zeros for HF generation */ + for ( ; n < 64; n++) { + XBuf[2*n+0] = 0; + XBuf[2*n+1] = 0; + } + + *delayIdx = (*delayIdx == NUM_QMF_DELAY_BUFS - 1 ? 0 : *delayIdx + 1); + + /* minimum of 2 GB in output */ + return gbMask; +} +/*********************************************************************************************************************** + * Function: QMFSynthesisConv + * + * Description: final convolution kernel for synthesis QMF + * + * Inputs: pointer to coefficient table, reordered for sequential access + * delay buffer of size 64*10 = 640 complex samples (1280 ints) + * index for delay ring buffer (range = [0, 9]) + * number of QMF subbands to process (range = [0, 64]) + * number of channels + * + * Outputs: 64 consecutive 16-bit PCM samples, interleaved by factor of nChans + * + * Return: none + * + * Notes: this is carefully written to be efficient on ARM + * use the assembly code version in sbrqmfsk.s when building for ARM! + **********************************************************************************************************************/ +void QMFSynthesisConv(int *cPtr, int *delay, int dIdx, short *outbuf, int nChans) { + + int k, dOff0, dOff1; + U64 sum64; + + dOff0 = (dIdx)*128; + dOff1 = dOff0 - 1; + if (dOff1 < 0) + dOff1 += 1280; + + /* scaling note: total gain of coefs (cPtr[0]-cPtr[9] for any k) is < 2.0, so 1 GB in delay values is adequate */ + for (k = 0; k <= 63; k++) { + sum64.w64 = 0; + sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff0]); dOff0 -= 256; if (dOff0 < 0) {dOff0 += 1280;} + sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff1]); dOff1 -= 256; if (dOff1 < 0) {dOff1 += 1280;} + sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff0]); dOff0 -= 256; if (dOff0 < 0) {dOff0 += 1280;} + sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff1]); dOff1 -= 256; if (dOff1 < 0) {dOff1 += 1280;} + sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff0]); dOff0 -= 256; if (dOff0 < 0) {dOff0 += 1280;} + sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff1]); dOff1 -= 256; if (dOff1 < 0) {dOff1 += 1280;} + sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff0]); dOff0 -= 256; if (dOff0 < 0) {dOff0 += 1280;} + sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff1]); dOff1 -= 256; if (dOff1 < 0) {dOff1 += 1280;} + sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff0]); dOff0 -= 256; if (dOff0 < 0) {dOff0 += 1280;} + sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff1]); dOff1 -= 256; if (dOff1 < 0) {dOff1 += 1280;} + + dOff0++; + dOff1--; + *outbuf = CLIPTOSHORT((sum64.r.hi32 + RND_VAL) >> FBITS_OUT_QMFS); + outbuf += nChans; + } +} +/*********************************************************************************************************************** + * Function: QMFSynthesis + * + * Description: 64-subband synthesis QMF (4.6.18.4.2) + * + * Inputs: 64 consecutive complex subband QMF samples, format = Q(FBITS_IN_QMFS) + * delay buffer of size 64*10 = 640 complex samples (1280 ints) + * index for delay ring buffer (range = [0, 9]) + * number of QMF subbands to process (range = [0, 64]) + * number of channels + * + * Outputs: 64 consecutive 16-bit PCM samples, interleaved by factor of nChans + * updated delay buffer + * updated delay index + * + * Return: none + * + * Notes: assumes MIN_GBITS_IN_QMFS guard bits in input, either from + * QMFAnalysis (if upsampling only) or from MapHF (if SBR on) + **********************************************************************************************************************/ +void QMFSynthesis(int *inbuf, int *delay, int *delayIdx, int qmfsBands, short *outbuf, int nChans) { + + int n, a0, a1, b0, b1, dOff0, dOff1, dIdx; + int *tBufLo, *tBufHi; + + dIdx = *delayIdx; + tBufLo = delay + dIdx*128 + 0; + tBufHi = delay + dIdx*128 + 127; + + /* reorder inputs to DCT-IV, only use first qmfsBands (complex) samples + */ + for (n = 0; n < qmfsBands >> 1; n++) { + a0 = *inbuf++; + b0 = *inbuf++; + a1 = *inbuf++; + b1 = *inbuf++; + *tBufLo++ = a0; + *tBufLo++ = a1; + *tBufHi-- = b0; + *tBufHi-- = b1; + } + if (qmfsBands & 0x01) { + a0 = *inbuf++; + b0 = *inbuf++; + *tBufLo++ = a0; + *tBufHi-- = b0; + *tBufLo++ = 0; + *tBufHi-- = 0; + n++; + } + for ( ; n < 32; n++) { + *tBufLo++ = 0; + *tBufHi-- = 0; + *tBufLo++ = 0; + *tBufHi-- = 0; + } + + tBufLo = delay + dIdx*128 + 0; + tBufHi = delay + dIdx*128 + 64; + + /* 2 GB in, 3 GB out */ + PreMultiply64(tBufLo); + PreMultiply64(tBufHi); + + /* 3 GB in, 1 GB out */ + FFT32C(tBufLo); + FFT32C(tBufHi); + + /* 1 GB in, 2 GB out */ + PostMultiply64(tBufLo, 64); + PostMultiply64(tBufHi, 64); + + /* could fuse with PostMultiply64 to avoid separate pass */ + dOff0 = dIdx*128; + dOff1 = dIdx*128 + 64; + for (n = 32; n != 0; n--) { + a0 = (*tBufLo++); + a1 = (*tBufLo++); + b0 = (*tBufHi++); + b1 = -(*tBufHi++); + + delay[dOff0++] = (b0 - a0); + delay[dOff0++] = (b1 - a1); + delay[dOff1++] = (b0 + a0); + delay[dOff1++] = (b1 + a1); + } + + QMFSynthesisConv((int *)cTabS, delay, dIdx, outbuf, nChans); + + *delayIdx = (*delayIdx == NUM_QMF_DELAY_BUFS - 1 ? 0 : *delayIdx + 1); +} +/*********************************************************************************************************************** + * Function: UnpackSBRHeader + * + * Description: unpack SBR header (table 4.56) + * + * Inputs: BitStreamInfo struct pointing to start of SBR header + * + * Outputs: initialized SBRHeader struct for this SCE/CPE block + * + * Return: non-zero if frame reset is triggered, zero otherwise + **********************************************************************************************************************/ +int UnpackSBRHeader(SBRHeader *sbrHdr) { + + SBRHeader sbrHdrPrev; + + /* save previous values so we know whether to reset decoder */ + sbrHdrPrev.startFreq = sbrHdr->startFreq; + sbrHdrPrev.stopFreq = sbrHdr->stopFreq; + sbrHdrPrev.freqScale = sbrHdr->freqScale; + sbrHdrPrev.alterScale = sbrHdr->alterScale; + sbrHdrPrev.crossOverBand = sbrHdr->crossOverBand; + sbrHdrPrev.noiseBands = sbrHdr->noiseBands; + + sbrHdr->ampRes = GetBits(1); + sbrHdr->startFreq = GetBits(4); + sbrHdr->stopFreq = GetBits(4); + sbrHdr->crossOverBand = GetBits(3); + sbrHdr->resBitsHdr = GetBits(2); + sbrHdr->hdrExtra1 = GetBits(1); + sbrHdr->hdrExtra2 = GetBits(1); + + if (sbrHdr->hdrExtra1) { + sbrHdr->freqScale = GetBits(2); + sbrHdr->alterScale = GetBits(1); + sbrHdr->noiseBands = GetBits(2); + } else { + /* defaults */ + sbrHdr->freqScale = 2; + sbrHdr->alterScale = 1; + sbrHdr->noiseBands = 2; + } + + if (sbrHdr->hdrExtra2) { + sbrHdr->limiterBands = GetBits(2); + sbrHdr->limiterGains = GetBits(2); + sbrHdr->interpFreq = GetBits(1); + sbrHdr->smoothMode = GetBits(1); + } else { + /* defaults */ + sbrHdr->limiterBands = 2; + sbrHdr->limiterGains = 2; + sbrHdr->interpFreq = 1; + sbrHdr->smoothMode = 1; + } + sbrHdr->count++; + + /* if any of these have changed from previous frame, reset the SBR module */ + if (sbrHdr->startFreq != sbrHdrPrev.startFreq || sbrHdr->stopFreq != sbrHdrPrev.stopFreq || + sbrHdr->freqScale != sbrHdrPrev.freqScale || sbrHdr->alterScale != sbrHdrPrev.alterScale || + sbrHdr->crossOverBand != sbrHdrPrev.crossOverBand || sbrHdr->noiseBands != sbrHdrPrev.noiseBands + ) + return -1; + else + return 0; +} + +/* cLog2[i] = ceil(log2(i)) (disregard i == 0) */ +static const uint8_t cLog2[9] = {0, 0, 1, 2, 2, 3, 3, 3, 3}; +/*********************************************************************************************************************** + * Function: UnpackSBRGrid + * + * Description: unpack SBR grid (table 4.62) + * + * Inputs: BitStreamInfo struct pointing to start of SBR grid + * initialized SBRHeader struct for this SCE/CPE block + * + * Outputs: initialized SBRGrid struct for this channel + * + * Return: none + **********************************************************************************************************************/ +void UnpackSBRGrid(SBRHeader *sbrHdr, SBRGrid *sbrGrid) { + + int numEnvRaw, env, rel, pBits, border, middleBorder = 0; + uint8_t relBordLead[MAX_NUM_ENV], relBordTrail[MAX_NUM_ENV]; + uint8_t relBorder0[3], relBorder1[3], relBorder[3]; + uint8_t numRelBorder0, numRelBorder1, numRelBorder, numRelLead = 0, numRelTrail; + uint8_t absBordLead = 0, absBordTrail = 0, absBorder; + + sbrGrid->ampResFrame = sbrHdr->ampRes; + sbrGrid->frameClass = GetBits(2); + switch(sbrGrid->frameClass){ + + case SBR_GRID_FIXFIX: + numEnvRaw = GetBits(2); + sbrGrid->numEnv = (1 << numEnvRaw); + if(sbrGrid->numEnv == 1) sbrGrid->ampResFrame = 0; + + ASSERT(sbrGrid->numEnv == 1 || sbrGrid->numEnv == 2 || sbrGrid->numEnv == 4); + + sbrGrid->freqRes[0] = GetBits(1); + for(env = 1; env < sbrGrid->numEnv; env++) + sbrGrid->freqRes[env] = sbrGrid->freqRes[0]; + + absBordLead = 0; + absBordTrail = NUM_TIME_SLOTS; + numRelLead = sbrGrid->numEnv - 1; + numRelTrail = 0; + + /* numEnv = 1, 2, or 4 */ + if(sbrGrid->numEnv == 1) + border = NUM_TIME_SLOTS / 1; + else if(sbrGrid->numEnv == 2) + border = NUM_TIME_SLOTS / 2; + else + border = NUM_TIME_SLOTS / 4; + + for(rel = 0; rel < numRelLead; rel++) + relBordLead[rel] = border; + + middleBorder = (sbrGrid->numEnv >> 1); + + break; + + case SBR_GRID_FIXVAR: + absBorder = GetBits(2) + NUM_TIME_SLOTS; + numRelBorder = GetBits(2); + sbrGrid->numEnv = numRelBorder + 1; + for(rel = 0; rel < numRelBorder; rel++) + relBorder[rel] = 2 * GetBits(2) + 2; + + pBits = cLog2[sbrGrid->numEnv + 1]; + sbrGrid->pointer = GetBits(pBits); + + for(env = sbrGrid->numEnv - 1; env >= 0; env--) + sbrGrid->freqRes[env] = GetBits(1); + + absBordLead = 0; + absBordTrail = absBorder; + numRelLead = 0; + numRelTrail = numRelBorder; + + for(rel = 0; rel < numRelTrail; rel++) + relBordTrail[rel] = relBorder[rel]; + + if(sbrGrid->pointer > 1) + middleBorder = sbrGrid->numEnv + 1 - sbrGrid->pointer; + else + middleBorder = sbrGrid->numEnv - 1; + + break; + + case SBR_GRID_VARFIX: + absBorder = GetBits(2); + numRelBorder = GetBits(2); + sbrGrid->numEnv = numRelBorder + 1; + for(rel = 0; rel < numRelBorder; rel++) + relBorder[rel] = 2 * GetBits(2) + 2; + + pBits = cLog2[sbrGrid->numEnv + 1]; + sbrGrid->pointer = GetBits(pBits); + + for(env = 0; env < sbrGrid->numEnv; env++) + sbrGrid->freqRes[env] = GetBits(1); + + absBordLead = absBorder; + absBordTrail = NUM_TIME_SLOTS; + numRelLead = numRelBorder; + numRelTrail = 0; + + for(rel = 0; rel < numRelLead; rel++) + relBordLead[rel] = relBorder[rel]; + + if(sbrGrid->pointer == 0) + middleBorder = 1; + else if(sbrGrid->pointer == 1) + middleBorder = sbrGrid->numEnv - 1; + else + middleBorder = sbrGrid->pointer - 1; + + break; + + case SBR_GRID_VARVAR: + absBordLead = GetBits(2); /* absBorder0 */ + absBordTrail = GetBits(2) + NUM_TIME_SLOTS; /* absBorder1 */ + numRelBorder0 = GetBits(2); + numRelBorder1 = GetBits(2); + + sbrGrid->numEnv = numRelBorder0 + numRelBorder1 + 1; + ASSERT(sbrGrid->numEnv <= 5); + + for(rel = 0; rel < numRelBorder0; rel++) + relBorder0[rel] = 2 * GetBits(2) + 2; + + for(rel = 0; rel < numRelBorder1; rel++) + relBorder1[rel] = 2 * GetBits(2) + 2; + + pBits = cLog2[numRelBorder0 + numRelBorder1 + 2]; + sbrGrid->pointer = GetBits(pBits); + + for(env = 0; env < sbrGrid->numEnv; env++) + sbrGrid->freqRes[env] = GetBits(1); + + numRelLead = numRelBorder0; + numRelTrail = numRelBorder1; + + for(rel = 0; rel < numRelLead; rel++) + relBordLead[rel] = relBorder0[rel]; + + for(rel = 0; rel < numRelTrail; rel++) + relBordTrail[rel] = relBorder1[rel]; + + if(sbrGrid->pointer > 1) + middleBorder = sbrGrid->numEnv + 1 - sbrGrid->pointer; + else + middleBorder = sbrGrid->numEnv - 1; + + break; + } + + /* build time border vector */ + sbrGrid->envTimeBorder[0] = absBordLead * SAMPLES_PER_SLOT; + + rel = 0; + border = absBordLead; + for(env = 1; env <= numRelLead; env++) { + border += relBordLead[rel++]; + sbrGrid->envTimeBorder[env] = border * SAMPLES_PER_SLOT; + } + + rel = 0; + border = absBordTrail; + for(env = sbrGrid->numEnv - 1; env > numRelLead; env--) { + border -= relBordTrail[rel++]; + sbrGrid->envTimeBorder[env] = border * SAMPLES_PER_SLOT; + } + + sbrGrid->envTimeBorder[sbrGrid->numEnv] = absBordTrail * SAMPLES_PER_SLOT; + + if(sbrGrid->numEnv > 1) { + sbrGrid->numNoiseFloors = 2; + sbrGrid->noiseTimeBorder[0] = sbrGrid->envTimeBorder[0]; + sbrGrid->noiseTimeBorder[1] = sbrGrid->envTimeBorder[middleBorder]; + sbrGrid->noiseTimeBorder[2] = sbrGrid->envTimeBorder[sbrGrid->numEnv]; + } + else { + sbrGrid->numNoiseFloors = 1; + sbrGrid->noiseTimeBorder[0] = sbrGrid->envTimeBorder[0]; + sbrGrid->noiseTimeBorder[1] = sbrGrid->envTimeBorder[1]; + } +} +/*********************************************************************************************************************** + * Function: UnpackDeltaTimeFreq + * + * Description: unpack time/freq flags for delta coding of SBR envelopes (table 4.63) + * + * Inputs: BitStreamInfo struct pointing to start of dt/df flags + * number of envelopes + * number of noise floors + * + * Outputs: delta flags for envelope and noise floors + * + * Return: none + **********************************************************************************************************************/ +void UnpackDeltaTimeFreq(int numEnv, uint8_t *deltaFlagEnv, int numNoiseFloors, uint8_t *deltaFlagNoise) { + + int env, noiseFloor; + + for (env = 0; env < numEnv; env++) + deltaFlagEnv[env] = GetBits(1); + + for (noiseFloor = 0; noiseFloor < numNoiseFloors; noiseFloor++) + deltaFlagNoise[noiseFloor] = GetBits(1); +} +/*********************************************************************************************************************** + * Function: UnpackInverseFilterMode + * + * Description: unpack invf flags for chirp factor calculation (table 4.64) + * + * Inputs: BitStreamInfo struct pointing to start of invf flags + * number of noise floor bands + * + * Outputs: invf flags for noise floor bands + * + * Return: none + **********************************************************************************************************************/ +void UnpackInverseFilterMode(int numNoiseFloorBands, uint8_t *mode) { + + int n; + + for (n = 0; n < numNoiseFloorBands; n++) + mode[n] = GetBits(2); +} +/*********************************************************************************************************************** + * Function: UnpackSinusoids + * + * Description: unpack sinusoid (harmonic) flags for each SBR subband (table 4.67) + * + * Inputs: BitStreamInfo struct pointing to start of sinusoid flags + * number of high resolution SBR subbands (nHigh) + * + * Outputs: sinusoid flags for each SBR subband, zero-filled above nHigh + * + * Return: none + **********************************************************************************************************************/ +void UnpackSinusoids(int nHigh, int addHarmonicFlag, uint8_t *addHarmonic) { + + int n; + + n = 0; + if(addHarmonicFlag) { + for(; n < nHigh; n++) + addHarmonic[n] = GetBits(1); + } + + /* zero out unused bands */ + for(; n < MAX_QMF_BANDS; n++) + addHarmonic[n] = 0; +} +/*********************************************************************************************************************** + * Function: CopyCouplingGrid + * + * Description: copy grid parameters from left to right for channel coupling + * + * Inputs: initialized SBRGrid struct for left channel + * + * Outputs: initialized SBRGrid struct for right channel + * + * Return: none + **********************************************************************************************************************/ +void CopyCouplingGrid(SBRGrid *sbrGridLeft, SBRGrid *sbrGridRight) { + + int env, noiseFloor; + + sbrGridRight->frameClass = sbrGridLeft->frameClass; + sbrGridRight->ampResFrame = sbrGridLeft->ampResFrame; + sbrGridRight->pointer = sbrGridLeft->pointer; + + sbrGridRight->numEnv = sbrGridLeft->numEnv; + for(env = 0; env < sbrGridLeft->numEnv; env++) { + sbrGridRight->envTimeBorder[env] = sbrGridLeft->envTimeBorder[env]; + sbrGridRight->freqRes[env] = sbrGridLeft->freqRes[env]; + } + sbrGridRight->envTimeBorder[env] = sbrGridLeft->envTimeBorder[env]; /* borders are [0, numEnv] inclusive */ + + sbrGridRight->numNoiseFloors = sbrGridLeft->numNoiseFloors; + for(noiseFloor = 0; noiseFloor <= sbrGridLeft->numNoiseFloors; noiseFloor++) + sbrGridRight->noiseTimeBorder[noiseFloor] = sbrGridLeft->noiseTimeBorder[noiseFloor]; + + /* numEnvPrev, numNoiseFloorsPrev, freqResPrev are updated in DecodeSBREnvelope() and DecodeSBRNoise() */ +} +/*********************************************************************************************************************** + * Function: CopyCouplingInverseFilterMode + * + * Description: copy invf flags from left to right for channel coupling + * + * Inputs: invf flags for left channel + * number of noise floor bands + * + * Outputs: invf flags for right channel + * + * Return: none + **********************************************************************************************************************/ +void CopyCouplingInverseFilterMode(int numNoiseFloorBands, uint8_t *modeLeft, uint8_t *modeRight) { + + int band; + + for(band = 0; band < numNoiseFloorBands; band++) + modeRight[band] = modeLeft[band]; +} +/*********************************************************************************************************************** + * Function: UnpackSBRSingleChannel + * + * Description: unpack sideband info (grid, delta flags, invf flags, envelope and + * noise floor configuration, sinusoids) for a single channel + * + * Inputs: BitStreamInfo struct pointing to start of sideband info + * initialized PSInfoSBR struct (after parsing SBR header and building + * frequency tables) + * base output channel (range = [0, nChans-1]) + * + * Outputs: updated PSInfoSBR struct (SBRGrid and SBRChan) + * + * Return: none + **********************************************************************************************************************/ +void UnpackSBRSingleChannel(int chBase) { + + int bitsLeft; + SBRHeader *sbrHdr = &(m_PSInfoSBR->sbrHdr[chBase]); + SBRGrid *sbrGridL = &(m_PSInfoSBR->sbrGrid[chBase + 0]); + SBRFreq *sbrFreq = &(m_PSInfoSBR->sbrFreq[chBase]); + SBRChan *sbrChanL = &(m_PSInfoSBR->sbrChan[chBase + 0]); + + m_PSInfoSBR->dataExtra = GetBits(1); + if(m_PSInfoSBR->dataExtra) m_PSInfoSBR->resBitsData = GetBits(4); + + UnpackSBRGrid(sbrHdr, sbrGridL); + UnpackDeltaTimeFreq(sbrGridL->numEnv, sbrChanL->deltaFlagEnv, sbrGridL->numNoiseFloors, sbrChanL->deltaFlagNoise); + UnpackInverseFilterMode(sbrFreq->numNoiseFloorBands, sbrChanL->invfMode[1]); + + DecodeSBREnvelope(sbrGridL, sbrFreq, sbrChanL, 0); + DecodeSBRNoise(sbrGridL, sbrFreq, sbrChanL, 0); + + sbrChanL->addHarmonicFlag[1] = GetBits(1); + UnpackSinusoids(sbrFreq->nHigh, sbrChanL->addHarmonicFlag[1], sbrChanL->addHarmonic[1]); + + m_PSInfoSBR->extendedDataPresent = GetBits(1); + if(m_PSInfoSBR->extendedDataPresent) { + m_PSInfoSBR->extendedDataSize = GetBits(4); + if(m_PSInfoSBR->extendedDataSize == 15) m_PSInfoSBR->extendedDataSize += GetBits(8); + + bitsLeft = 8 * m_PSInfoSBR->extendedDataSize; + + /* get ID, unpack extension info, do whatever is necessary with it... */ + while(bitsLeft > 0) { + GetBits(8); + bitsLeft -= 8; + } + } +} +/*********************************************************************************************************************** + * Function: UnpackSBRChannelPair + * + * Description: unpack sideband info (grid, delta flags, invf flags, envelope and + * noise floor configuration, sinusoids) for a channel pair + * + * Inputs: base output channel (range = [0, nChans-1]) + * + * Outputs: updated PSInfoSBR struct (SBRGrid and SBRChan for both channels) + * + * Return: none + **********************************************************************************************************************/ +void UnpackSBRChannelPair(int chBase) { + + int bitsLeft; + SBRHeader *sbrHdr = &(m_PSInfoSBR->sbrHdr[chBase]); + SBRGrid *sbrGridL = &(m_PSInfoSBR->sbrGrid[chBase + 0]), *sbrGridR = &(m_PSInfoSBR->sbrGrid[chBase + 1]); + SBRFreq *sbrFreq = &(m_PSInfoSBR->sbrFreq[chBase]); + SBRChan *sbrChanL = &(m_PSInfoSBR->sbrChan[chBase + 0]), *sbrChanR = &(m_PSInfoSBR->sbrChan[chBase + 1]); + + m_PSInfoSBR->dataExtra = GetBits(1); + if(m_PSInfoSBR->dataExtra) { + m_PSInfoSBR->resBitsData = GetBits(4); + m_PSInfoSBR->resBitsData = GetBits(4); + } + + m_PSInfoSBR->couplingFlag = GetBits(1); + if(m_PSInfoSBR->couplingFlag) { + UnpackSBRGrid(sbrHdr, sbrGridL); + CopyCouplingGrid(sbrGridL, sbrGridR); + + UnpackDeltaTimeFreq(sbrGridL->numEnv, sbrChanL->deltaFlagEnv, sbrGridL->numNoiseFloors, + sbrChanL->deltaFlagNoise); + UnpackDeltaTimeFreq(sbrGridR->numEnv, sbrChanR->deltaFlagEnv, sbrGridR->numNoiseFloors, + sbrChanR->deltaFlagNoise); + + UnpackInverseFilterMode(sbrFreq->numNoiseFloorBands, sbrChanL->invfMode[1]); + CopyCouplingInverseFilterMode(sbrFreq->numNoiseFloorBands, sbrChanL->invfMode[1], sbrChanR->invfMode[1]); + + DecodeSBREnvelope(sbrGridL, sbrFreq, sbrChanL, 0); + DecodeSBRNoise(sbrGridL, sbrFreq, sbrChanL, 0); + DecodeSBREnvelope(sbrGridR, sbrFreq, sbrChanR, 1); + DecodeSBRNoise(sbrGridR, sbrFreq, sbrChanR, 1); + + /* pass RIGHT sbrChan struct */ + UncoupleSBREnvelope(sbrGridL, sbrFreq, sbrChanR); + UncoupleSBRNoise(sbrGridL, sbrFreq, sbrChanR); + + } + else { + UnpackSBRGrid(sbrHdr, sbrGridL); + UnpackSBRGrid(sbrHdr, sbrGridR); + UnpackDeltaTimeFreq(sbrGridL->numEnv, sbrChanL->deltaFlagEnv, sbrGridL->numNoiseFloors, + sbrChanL->deltaFlagNoise); + UnpackDeltaTimeFreq(sbrGridR->numEnv, sbrChanR->deltaFlagEnv, sbrGridR->numNoiseFloors, + sbrChanR->deltaFlagNoise); + UnpackInverseFilterMode(sbrFreq->numNoiseFloorBands, sbrChanL->invfMode[1]); + UnpackInverseFilterMode(sbrFreq->numNoiseFloorBands, sbrChanR->invfMode[1]); + + DecodeSBREnvelope(sbrGridL, sbrFreq, sbrChanL, 0); + DecodeSBREnvelope(sbrGridR, sbrFreq, sbrChanR, 1); + DecodeSBRNoise(sbrGridL, sbrFreq, sbrChanL, 0); + DecodeSBRNoise(sbrGridR, sbrFreq, sbrChanR, 1); + } + + sbrChanL->addHarmonicFlag[1] = GetBits(1); + UnpackSinusoids(sbrFreq->nHigh, sbrChanL->addHarmonicFlag[1], sbrChanL->addHarmonic[1]); + + sbrChanR->addHarmonicFlag[1] = GetBits(1); + UnpackSinusoids(sbrFreq->nHigh, sbrChanR->addHarmonicFlag[1], sbrChanR->addHarmonic[1]); + + m_PSInfoSBR->extendedDataPresent = GetBits(1); + if(m_PSInfoSBR->extendedDataPresent) { + m_PSInfoSBR->extendedDataSize = GetBits(4); + if(m_PSInfoSBR->extendedDataSize == 15) m_PSInfoSBR->extendedDataSize += GetBits(8); + + bitsLeft = 8 * m_PSInfoSBR->extendedDataSize; + + /* get ID, unpack extension info, do whatever is necessary with it... */ + while(bitsLeft > 0) { + GetBits(8); + bitsLeft -= 8; + } + } +} diff --git a/yoRadio/src/audioI2S/aac_decoder/aac_decoder.h b/yoRadio/src/audioI2S/aac_decoder/aac_decoder.h new file mode 100644 index 0000000..74b570e --- /dev/null +++ b/yoRadio/src/audioI2S/aac_decoder/aac_decoder.h @@ -0,0 +1,583 @@ +// based on helix aac decoder +#pragma once +//#pragma GCC optimize ("O3") +//#pragma GCC diagnostic ignored "-Wnarrowing" + +#include "Arduino.h" + +#define AAC_ENABLE_MPEG4 +//#define AAC_ENABLE_SBR // needs additional 60KB Heap, + +#define ASSERT(x) /* do nothing */ + +#ifndef MAX +#define MAX(a,b) std::max(a,b) +#endif + +#ifndef MIN +#define MIN(a,b) std::min(a,b) +#endif + + +/* AAC file format */ +enum { + AAC_FF_Unknown = 0, /* should be 0 on init */ + AAC_FF_ADTS = 1, + AAC_FF_ADIF = 2, + AAC_FF_RAW = 3 +}; + +/* syntactic element type */ +enum { + AAC_ID_INVALID = -1, + AAC_ID_SCE = 0, + AAC_ID_CPE = 1, + AAC_ID_CCE = 2, + AAC_ID_LFE = 3, + AAC_ID_DSE = 4, + AAC_ID_PCE = 5, + AAC_ID_FIL = 6, + AAC_ID_END = 7 +}; + +enum { + ERR_AAC_NONE = 0, + ERR_AAC_INDATA_UNDERFLOW = -1, + ERR_AAC_NULL_POINTER = -2, + ERR_AAC_INVALID_ADTS_HEADER = -3, + ERR_AAC_INVALID_ADIF_HEADER = -4, + ERR_AAC_INVALID_FRAME = -5, + ERR_AAC_MPEG4_UNSUPPORTED = -6, + ERR_AAC_CHANNEL_MAP = -7, + ERR_AAC_SYNTAX_ELEMENT = -8, + ERR_AAC_DEQUANT = -9, + ERR_AAC_STEREO_PROCESS = -10, + ERR_AAC_PNS = -11, + ERR_AAC_SHORT_BLOCK_DEINT = -12, + ERR_AAC_TNS = -13, + ERR_AAC_IMDCT = -14, + ERR_AAC_NCHANS_TOO_HIGH = -15, + ERR_AAC_SBR_INIT = -16, + ERR_AAC_SBR_BITSTREAM = -17, + ERR_AAC_SBR_DATA = -18, + ERR_AAC_SBR_PCM_FORMAT = -19, + ERR_AAC_SBR_NCHANS_TOO_HIGH = -20, + ERR_AAC_SBR_SINGLERATE_UNSUPPORTED = -21, + ERR_AAC_RAWBLOCK_PARAMS = -22, + ERR_AAC_UNKNOWN = -9999 +}; + +enum { + SBR_GRID_FIXFIX = 0, + SBR_GRID_FIXVAR = 1, + SBR_GRID_VARFIX = 2, + SBR_GRID_VARVAR = 3 +}; + +enum { + HuffTabSBR_tEnv15 = 0, + HuffTabSBR_fEnv15 = 1, + HuffTabSBR_tEnv15b = 2, + HuffTabSBR_fEnv15b = 3, + HuffTabSBR_tEnv30 = 4, + HuffTabSBR_fEnv30 = 5, + HuffTabSBR_tEnv30b = 6, + HuffTabSBR_fEnv30b = 7, + HuffTabSBR_tNoise30 = 8, + HuffTabSBR_fNoise30 = 5, + HuffTabSBR_tNoise30b = 9, + HuffTabSBR_fNoise30b = 7 +}; + +typedef struct _AACDecInfo_t { + /* raw decoded data, before rounding to 16-bit PCM (for postprocessing such as SBR) */ + void *rawSampleBuf[2]; + int rawSampleBytes; + int rawSampleFBits; + /* fill data (can be used for processing SBR or other extensions) */ + uint8_t *fillBuf; + int fillCount; + int fillExtType; + int prevBlockID; /* block information */ + int currBlockID; + int currInstTag; + int sbDeinterleaveReqd[2]; // [MAX_NCHANS_ELEM] + int adtsBlocksLeft; + int bitRate; /* user-accessible info */ + int nChans; + int sampRate; + float compressionRatio; + int id; /* 0: MPEG-4, 1: MPEG2 */ + int profile; /* 0: Main profile, 1: LowComplexity (LC), 2: ScalableSamplingRate (SSR), 3: reserved */ + int format; + int sbrEnabled; + int tnsUsed; + int pnsUsed; + int frameCount; +} AACDecInfo_t; + + +typedef struct _aac_BitStreamInfo_t { + uint8_t *bytePtr; + unsigned int iCache; + int cachedBits; + int nBytes; +} aac_BitStreamInfo_t; + +typedef union _U64 { + int64_t w64; + struct { + unsigned int lo32; + signed int hi32; + } r; +} U64; + +typedef struct _AACFrameInfo_t { + int bitRate; + int nChans; + int sampRateCore; + int sampRateOut; + int bitsPerSample; + int outputSamps; + int profile; + int tnsUsed; + int pnsUsed; +} AACFrameInfo_t; + +typedef struct _HuffInfo_t { + int maxBits; /* number of bits in longest codeword */ + uint8_t count[20]; /* count[MAX_HUFF_BITS] = number of codes with length i+1 bits */ + int offset; /* offset into symbol table */ +} HuffInfo_t; + +typedef struct _PulseInfo_t { + uint8_t pulseDataPresent; + uint8_t numPulse; + uint8_t startSFB; + uint8_t offset[4]; // [MAX_PULSES] + uint8_t amp[4]; // [MAX_PULSES] +} PulseInfo_t; + +typedef struct _TNSInfo_t { + uint8_t tnsDataPresent; + uint8_t numFilt[8]; // [MAX_TNS_FILTERS] max 1 filter each for 8 short windows, or 3 filters for 1 long window + uint8_t coefRes[8]; // [MAX_TNS_FILTERS] + uint8_t length[8]; // [MAX_TNS_FILTERS] + uint8_t order[8]; // [MAX_TNS_FILTERS] + uint8_t dir[8]; // [MAX_TNS_FILTERS] + 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 +} TNSInfo_t; + +typedef struct _GainControlInfo_t { + uint8_t gainControlDataPresent; + uint8_t maxBand; + uint8_t adjNum[3][8]; // [MAX_GAIN_BANDS][MAX_GAIN_WIN] + uint8_t alevCode[3][8][7]; // [MAX_GAIN_BANDS][MAX_GAIN_WIN][MAX_GAIN_ADJUST] + uint8_t alocCode[3][8][7]; // [MAX_GAIN_BANDS][MAX_GAIN_WIN][MAX_GAIN_ADJUST] +} GainControlInfo_t; + +typedef struct _ICSInfo_t { + uint8_t icsResBit; + uint8_t winSequence; + uint8_t winShape; + uint8_t maxSFB; + uint8_t sfGroup; + uint8_t predictorDataPresent; + uint8_t predictorReset; + uint8_t predictorResetGroupNum; + uint8_t predictionUsed[41]; // [MAX_PRED_SFB] + uint8_t numWinGroup; + uint8_t winGroupLen[8]; // [MAX_WIN_GROUPS] +} ICSInfo_t; + +typedef struct _ADTSHeader_t { + /* fixed */ + uint8_t id; /* MPEG bit - should be 1 */ + uint8_t layer; /* MPEG layer - should be 0 */ + uint8_t protectBit; /* 0 = CRC word follows, 1 = no CRC word */ + uint8_t profile; /* 0 = main, 1 = LC, 2 = SSR, 3 = reserved */ + uint8_t sampRateIdx; /* sample rate index range = [0, 11] */ + uint8_t privateBit; /* ignore */ + uint8_t channelConfig; /* 0 = implicit, >0 = use default table */ + uint8_t origCopy; /* 0 = copy, 1 = original */ + uint8_t home; /* ignore */ + /* variable */ + uint8_t copyBit; /* 1 bit of the 72-bit copyright ID (transmitted as 1 bit per frame) */ + uint8_t copyStart; /* 1 = this bit starts the 72-bit ID, 0 = it does not */ + int frameLength; /* length of frame */ + int bufferFull; /* number of 32-bit words left in enc buffer, 0x7FF = VBR */ + uint8_t numRawDataBlocks; /* number of raw data blocks in frame */ + /* CRC */ + int crcCheckWord; /* 16-bit CRC check word (present if protectBit == 0) */ +} ADTSHeader_t; + +typedef struct _ADIFHeader_t { + uint8_t copyBit; /* 0 = no copyright ID, 1 = 72-bit copyright ID follows immediately */ + uint8_t origCopy; /* 0 = copy, 1 = original */ + uint8_t home; /* ignore */ + uint8_t bsType; /* bitstream type: 0 = CBR, 1 = VBR */ + int bitRate; /* bitRate: CBR = bits/sec, VBR = peak bits/frame, 0 = unknown */ + uint8_t numPCE; /* number of program config elements (max = 16) */ + int bufferFull; /* bits left in bit reservoir */ + uint8_t copyID[9]; /* [ADIF_COPYID_SIZE] optional 72-bit copyright ID */ +} ADIFHeader_t; + +/* sizeof(ProgConfigElement_t) = 82 bytes (if KEEP_PCE_COMMENTS not defined) */ +typedef struct _ProgConfigElement_t { + uint8_t elemInstTag; /* element instance tag */ + uint8_t profile; /* 0 = main, 1 = LC, 2 = SSR, 3 = reserved */ + uint8_t sampRateIdx; /* sample rate index range = [0, 11] */ + uint8_t numFCE; /* number of front channel elements (max = 15) */ + uint8_t numSCE; /* number of side channel elements (max = 15) */ + uint8_t numBCE; /* number of back channel elements (max = 15) */ + uint8_t numLCE; /* number of LFE channel elements (max = 3) */ + uint8_t numADE; /* number of associated data elements (max = 7) */ + uint8_t numCCE; /* number of valid channel coupling elements (max = 15) */ + uint8_t monoMixdown; /* mono mixdown: bit 4 = present flag, bits 3-0 = element number */ + uint8_t stereoMixdown; /* stereo mixdown: bit 4 = present flag, bits 3-0 = element number */ + uint8_t matrixMixdown; /* bit 4 = present flag, bit 3 = unused,bits 2-1 = index, bit 0 = pseudo-surround enable */ + uint8_t fce[15]; /* [MAX_NUM_FCE] front element channel pair: bit 4 = SCE/CPE flag, bits 3-0 = inst tag */ + uint8_t sce[15]; /* [MAX_NUM_SCE] side element channel pair: bit 4 = SCE/CPE flag, bits 3-0 = inst tag */ + uint8_t bce[15]; /* [MAX_NUM_BCE] back element channel pair: bit 4 = SCE/CPE flag, bits 3-0 = inst tag */ + uint8_t lce[3]; /* [MAX_NUM_LCE] instance tag for LFE elements */ + uint8_t ade[7]; /* [MAX_NUM_ADE] instance tag for ADE elements */ + uint8_t cce[15]; /* [MAX_NUM_BCE] channel coupling elements: bit 4 = switching flag, bits 3-0 = inst tag */ +} ProgConfigElement_t; + +typedef struct _SBRHeader { + int count; + + uint8_t ampRes; + uint8_t startFreq; + uint8_t stopFreq; + uint8_t crossOverBand; + uint8_t resBitsHdr; + uint8_t hdrExtra1; + uint8_t hdrExtra2; + + uint8_t freqScale; + uint8_t alterScale; + uint8_t noiseBands; + + uint8_t limiterBands; + uint8_t limiterGains; + uint8_t interpFreq; + uint8_t smoothMode; +} SBRHeader; + +/* need one SBRGrid per channel, updated every frame */ +typedef struct _SBRGrid { + uint8_t frameClass; + uint8_t ampResFrame; + uint8_t pointer; + + uint8_t numEnv; /* L_E */ + uint8_t envTimeBorder[5 + 1]; // [MAX_NUM_ENV+1] /* t_E */ + uint8_t freqRes[5]; // [MAX_NUM_ENV]/* r */ + uint8_t numNoiseFloors; /* L_Q */ + uint8_t noiseTimeBorder[2 + 1]; // [MAX_NUM_NOISE_FLOORS+1] /* t_Q */ + + uint8_t numEnvPrev; + uint8_t numNoiseFloorsPrev; + uint8_t freqResPrev; +} SBRGrid; + +/* need one SBRFreq per element (SCE/CPE/LFE), updated only on header reset */ +typedef struct _SBRFreq { + int kStart; /* k_x */ + int nMaster; + int nHigh; + int nLow; + int nLimiter; /* N_l */ + int numQMFBands; /* M */ + int numNoiseFloorBands; /* Nq */ + int kStartPrev; + int numQMFBandsPrev; + uint8_t freqMaster[48 + 1]; // [MAX_QMF_BANDS + 1] /* not necessary to save this after derived tables are generated */ + uint8_t freqHigh[48 + 1]; // [MAX_QMF_BANDS + 1] + uint8_t freqLow[48 / 2 + 1]; // [MAX_QMF_BANDS / 2 + 1] /* nLow = nHigh - (nHigh >> 1) */ + uint8_t freqNoise[5 + 1]; // [MAX_NUM_NOISE_FLOOR_BANDS+1] + uint8_t freqLimiter[48 / 2 + 5];// [MAX_QMF_BANDS / 2 + MAX_NUM_PATCHES] /* max (intermediate) size = nLow + numPatches - 1 */ + + uint8_t numPatches; + uint8_t patchNumSubbands[5 + 1]; // [MAX_NUM_PATCHES + 1] + uint8_t patchStartSubband[5 + 1]; // [MAX_NUM_PATCHES + 1] +} SBRFreq; + +typedef struct _SBRChan { + int reset; + uint8_t deltaFlagEnv[5]; // [MAX_NUM_ENV] + uint8_t deltaFlagNoise[2]; // [MAX_NUM_NOISE_FLOORS] + int8_t envDataQuant[5][48]; // [MAX_NUM_ENV][MAX_QMF_BANDS] /* range = [0, 127] */ + int8_t noiseDataQuant[2][5]; // [MAX_NUM_NOISE_FLOORS][MAX_NUM_NOISE_FLOOR_BANDS] + + uint8_t invfMode[2][5]; // [2][MAX_NUM_NOISE_FLOOR_BANDS] /* invfMode[0/1][band] = prev/curr */ + int chirpFact[5]; // [MAX_NUM_NOISE_FLOOR_BANDS] /* bwArray */ + uint8_t addHarmonicFlag[2]; /* addHarmonicFlag[0/1] = prev/curr */ + uint8_t addHarmonic[2][64]; /* addHarmonic[0/1][band] = prev/curr */ + + int gbMask[2]; /* gbMask[0/1] = XBuf[0-31]/XBuf[32-39] */ + int8_t laPrev; + + int noiseTabIndex; + int sinIndex; + int gainNoiseIndex; + int gTemp[5][48]; // [MAX_NUM_SMOOTH_COEFS][MAX_QMF_BANDS] + int qTemp[5][48]; // [MAX_NUM_SMOOTH_COEFS][MAX_QMF_BANDS] + +} SBRChan; + + +/* state info struct for baseline (MPEG-4 LC) decoding */ +typedef struct _PSInfoBase_t { + int dataCount; + uint8_t dataBuf[510]; // [DATA_BUF_SIZE] + int fillCount; + uint8_t fillBuf[269]; //[FILL_BUF_SIZE] + /* state information which is the same throughout whole frame */ + int nChans; + int useImpChanMap; + int sampRateIdx; + /* state information which can be overwritten by subsequent elements within frame */ + ICSInfo_t icsInfo[2]; // [MAX_NCHANS_ELEM] + int commonWin; + short scaleFactors[2][15*8]; // [MAX_NCHANS_ELEM][MAX_SF_BANDS] + uint8_t sfbCodeBook[2][15*8]; // [MAX_NCHANS_ELEM][MAX_SF_BANDS] + int msMaskPresent; + uint8_t msMaskBits[(15 * 8 + 7) >> 3]; // [MAX_MS_MASK_BYTES] + int pnsUsed[2]; // [MAX_NCHANS_ELEM] + int pnsLastVal; + int intensityUsed[2]; // [MAX_NCHANS_ELEM] +// PulseInfo_t pulseInfo[2]; // [MAX_NCHANS_ELEM] + TNSInfo_t tnsInfo[2]; // [MAX_NCHANS_ELEM] + int tnsLPCBuf[20]; // [MAX_TNS_ORDER] + int tnsWorkBuf[20]; //[MAX_TNS_ORDER] + GainControlInfo_t gainControlInfo[2]; // [MAX_NCHANS_ELEM] + int gbCurrent[2]; // [MAX_NCHANS_ELEM] + int coef[2][1024]; // [MAX_NCHANS_ELEM][AAC_MAX_NSAMPS] +#ifdef AAC_ENABLE_SBR + int sbrWorkBuf[2][1024]; // [MAX_NCHANS_ELEM][AAC_MAX_NSAMPS]; +#endif + /* state information which must be saved for each element and used in next frame */ + int overlap[2][1024]; // [AAC_MAX_NCHANS][AAC_MAX_NSAMPS] + int prevWinShape[2]; // [AAC_MAX_NCHANS] +} PSInfoBase_t; + +typedef struct _PSInfoSBR { + /* save for entire file */ + int frameCount; + int sampRateIdx; + + /* state info that must be saved for each channel */ + SBRHeader sbrHdr[2]; + SBRGrid sbrGrid[2]; + SBRFreq sbrFreq[2]; + SBRChan sbrChan[2]; + + /* temp variables, no need to save between blocks */ + uint8_t dataExtra; + uint8_t resBitsData; + uint8_t extendedDataPresent; + int extendedDataSize; + + int8_t envDataDequantScale[2][5]; // [MAX_NCHANS_ELEM][MAX_NUM_ENV + int envDataDequant[2][5][48]; // [MAX_NCHANS_ELEM][MAX_NUM_ENV][MAX_QMF_BANDS + int noiseDataDequant[2][2][5]; // [MAX_NCHANS_ELEM][MAX_NUM_NOISE_FLOORS][MAX_NUM_NOISE_FLOOR_BANDS] + + int eCurr[48]; // [MAX_QMF_BANDS] + uint8_t eCurrExp[48]; // [MAX_QMF_BANDS] + uint8_t eCurrExpMax; + int8_t la; + + int crcCheckWord; + int couplingFlag; + int envBand; + int eOMGainMax; + int gainMax; + int gainMaxFBits; + int noiseFloorBand; + int qp1Inv; + int qqp1Inv; + int sMapped; + int sBand; + int highBand; + + int sumEOrigMapped; + int sumECurrGLim; + int sumSM; + int sumQM; + int gLimBoost[48]; + int qmLimBoost[48]; + int smBoost[48]; + + int smBuf[48]; + int qmLimBuf[48]; + int gLimBuf[48]; + int gLimFbits[48]; + + int gFiltLast[48]; + int qFiltLast[48]; + + /* large buffers */ + int delayIdxQMFA[2]; // [AAC_MAX_NCHANS] + int delayQMFA[2][10 * 32]; // [AAC_MAX_NCHANS][DELAY_SAMPS_QMFA] + int delayIdxQMFS[2]; // [AAC_MAX_NCHANS] + int delayQMFS[2][10 * 128]; // [AAC_MAX_NCHANS][DELAY_SAMPS_QMFS] + int XBufDelay[2][8][64][2]; // [AAC_MAX_NCHANS][HF_GEN][64][2] + int XBuf[32+8][64][2]; +} PSInfoSBR_t; + +bool AACDecoder_AllocateBuffers(void); +int AACFlushCodec(); +void AACDecoder_FreeBuffers(void); +bool AACDecoder_IsInit(void); +int AACFindSyncWord(uint8_t *buf, int nBytes); +int AACSetRawBlockParams(int copyLast, int nChans, int sampRateCore, int profile); +int AACDecode(uint8_t *inbuf, int *bytesLeft, short *outbuf); +int AACGetSampRate(); +int AACGetChannels(); +int AACGetID(); // 0-MPEG4, 1-MPEG2 +uint8_t AACGetProfile(); // 0-Main, 1-LC, 2-SSR, 3-reserved +uint8_t AACGetFormat(); // 0-unknown 1-ADTS 2-ADIF, 3-RAW +int AACGetBitsPerSample(); +int AACGetBitrate(); +int AACGetOutputSamps(); +int AACGetBitrate(); +void DecodeLPCCoefs(int order, int res, int8_t *filtCoef, int *a, int *b); +int FilterRegion(int size, int dir, int order, int *audioCoef, int *a, int *hist); +int TNSFilter(int ch); +int DecodeSingleChannelElement(); +int DecodeChannelPairElement(); +int DecodeLFEChannelElement(); +int DecodeDataStreamElement(); +int DecodeProgramConfigElement(uint8_t idx); +int DecodeFillElement(); +int DecodeNextElement(uint8_t **buf, int *bitOffset, int *bitsAvail); +void PreMultiply(int tabidx, int *zbuf1); +void PostMultiply(int tabidx, int *fft1); +void PreMultiplyRescale(int tabidx, int *zbuf1, int es); +void PostMultiplyRescale(int tabidx, int *fft1, int es); +void DCT4(int tabidx, int *coef, int gb); +void BitReverse(int *inout, int tabidx); +void R4FirstPass(int *x, int bg); +void R8FirstPass(int *x, int bg); +void R4Core(int *x, int bg, int gp, int *wtab); +void R4FFT(int tabidx, int *x); +void UnpackZeros(int nVals, int *coef); +void UnpackQuads(int cb, int nVals, int *coef); +void UnpackPairsNoEsc(int cb, int nVals, int *coef); +void UnpackPairsEsc(int cb, int nVals, int *coef); +void DecodeSpectrumLong(int ch); +void DecodeSpectrumShort(int ch); +void DecWindowOverlap(int *buf0, int *over0, short *pcm0, int nChans, int winTypeCurr, int winTypePrev); +void DecWindowOverlapLongStart(int *buf0, int *over0, short *pcm0, int nChans, int winTypeCurr, int winTypePrev); +void DecWindowOverlapLongStop(int *buf0, int *over0, short *pcm0, int nChans, int winTypeCurr, int winTypePrev); +void DecWindowOverlapShort(int *buf0, int *over0, short *pcm0, int nChans, int winTypeCurr, int winTypePrev); +int IMDCT(int ch, int chOut, short *outbuf); +void DecodeICSInfo(ICSInfo_t *icsInfo, int sampRateIdx); +void DecodeSectionData(int winSequence, int numWinGrp, int maxSFB, uint8_t *sfbCodeBook); +int DecodeOneScaleFactor(); +void DecodeScaleFactors(int numWinGrp, int maxSFB, int globalGain, uint8_t *sfbCodeBook, short *scaleFactors); +void DecodePulseInfo(uint8_t ch); +void DecodeTNSInfo(int winSequence, TNSInfo_t *ti, int8_t *tnsCoef); +void DecodeGainControlInfo(int winSequence, GainControlInfo_t *gi); +void DecodeICS(int ch); +int DecodeNoiselessData(uint8_t **buf, int *bitOffset, int *bitsAvail, int ch); +int DecodeHuffmanScalar(const signed short *huffTab, const HuffInfo_t *huffTabInfo, unsigned int bitBuf, int32_t *val); +int UnpackADTSHeader(uint8_t **buf, int *bitOffset, int *bitsAvail); +int GetADTSChannelMapping(uint8_t *buf, int bitOffset, int bitsAvail); +int GetNumChannelsADIF(int nPCE); +int GetSampleRateIdxADIF(int nPCE); +int UnpackADIFHeader(uint8_t **buf, int *bitOffset, int *bitsAvail); +int SetRawBlockParams(int copyLast, int nChans, int sampRate, int profile); +int PrepareRawBlock(); +int DequantBlock(int *inbuf, int nSamps, int scale); +int AACDequantize(int ch); +int DeinterleaveShortBlocks(int ch); +unsigned int Get32BitVal(unsigned int *last); +int InvRootR(int r); +int ScaleNoiseVector(int *coef, int nVals, int sf); +void GenerateNoiseVector(int *coef, int *last, int nVals); +void CopyNoiseVector(int *coefL, int *coefR, int nVals); +int PNS(int ch); +int GetSampRateIdx(int sampRate); +void StereoProcessGroup(int *coefL, int *coefR, const uint16_t *sfbTab, int msMaskPres, uint8_t *msMaskPtr, + int msMaskOffset, int maxSFB, uint8_t *cbRight, short *sfRight, int *gbCurrent); +int StereoProcess(); +int RatioPowInv(int a, int b, int c); +int SqrtFix(int q, int fBitsIn, int *fBitsOut); +int InvRNormalized(int r); +void BitReverse32(int *inout); +void R8FirstPass32(int *r0); +void R4Core32(int *r0); +void FFT32C(int *x); +void CVKernel1(int *XBuf, int *accBuf); +void CVKernel2(int *XBuf, int *accBuf); +void SetBitstreamPointer(int nBytes, uint8_t *buf); +inline void RefillBitstreamCache(); +unsigned int GetBits(int nBits); +unsigned int GetBitsNoAdvance(int nBits); +void AdvanceBitstream(int nBits); +int CalcBitsUsed(uint8_t *startBuf, int startOffset); +void ByteAlignBitstream(); +// SBR +void InitSBRState(); +int DecodeSBRBitstream(int chBase); +int DecodeSBRData(int chBase, short *outbuf); +int FlushCodecSBR(); +void BubbleSort(uint8_t *v, int nItems); +uint8_t VMin(uint8_t *v, int nItems); +uint8_t VMax(uint8_t *v, int nItems); +int CalcFreqMasterScaleZero(uint8_t *freqMaster, int alterScale, int k0, int k2); +int CalcFreqMaster(uint8_t *freqMaster, int freqScale, int alterScale, int k0, int k2); +int CalcFreqHigh(uint8_t *freqHigh, uint8_t *freqMaster, int nMaster, int crossOverBand); +int CalcFreqLow(uint8_t *freqLow, uint8_t *freqHigh, int nHigh); +int CalcFreqNoise(uint8_t *freqNoise, uint8_t *freqLow, int nLow, int kStart, int k2, int noiseBands); +int BuildPatches(uint8_t *patchNumSubbands, uint8_t *patchStartSubband, uint8_t *freqMaster, int nMaster, int k0, + int kStart, int numQMFBands, int sampRateIdx); +int FindFreq(uint8_t *freq, int nFreq, uint8_t val); +void RemoveFreq(uint8_t *freq, int nFreq, int removeIdx); +int CalcFreqLimiter(uint8_t *freqLimiter, uint8_t *patchNumSubbands, uint8_t *freqLow, int nLow, int kStart, + int limiterBands, int numPatches); +int CalcFreqTables(SBRHeader *sbrHdr, SBRFreq *sbrFreq, int sampRateIdx); +void EstimateEnvelope(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, int env); +int GetSMapped(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int env, int band, int la); +void CalcMaxGain(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, int ch, int env, int lim, int fbitsDQ); +void CalcNoiseDivFactors(int q, int *qp1Inv, int *qqp1Inv); +void CalcComponentGains(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int ch, int env, int lim, int fbitsDQ); +void ApplyBoost(SBRFreq *sbrFreq, int lim, int fbitsDQ); +void CalcGain(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int ch, int env); +void MapHF(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int env, int hfReset); +void AdjustHighFreq(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int ch); +int CalcCovariance1(int *XBuf, int *p01reN, int *p01imN, int *p12reN, int *p12imN, int *p11reN, int *p22reN); +int CalcCovariance2(int *XBuf, int *p02reN, int *p02imN); +void CalcLPCoefs(int *XBuf, int *a0re, int *a0im, int *a1re, int *a1im, int gb); +void GenerateHighFreq(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int ch); +int DecodeHuffmanScalar(const signed int *huffTab, const HuffInfo_t *huffTabInfo, unsigned int bitBuf, signed int *val); +int DecodeOneSymbol(int huffTabIndex); +int DequantizeEnvelope(int nBands, int ampRes, int8_t *envQuant, int *envDequant); +void DequantizeNoise(int nBands, int8_t *noiseQuant, int *noiseDequant); +void DecodeSBREnvelope(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int ch); +void DecodeSBRNoise(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int ch); +void UncoupleSBREnvelope(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChanR); +void UncoupleSBRNoise(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChanR); +void DecWindowOverlapNoClip(int *buf0, int *over0, int *out0, int winTypeCurr, int winTypePrev); +void DecWindowOverlapLongStartNoClip(int *buf0, int *over0, int *out0, int winTypeCurr, int winTypePrev); +void DecWindowOverlapLongStopNoClip(int *buf0, int *over0, int *out0, int winTypeCurr, int winTypePrev); +void DecWindowOverlapShortNoClip(int *buf0, int *over0, int *out0, int winTypeCurr, int winTypePrev); +void PreMultiply64(int *zbuf1); +void PostMultiply64(int *fft1, int nSampsOut); +void QMFAnalysisConv(int *cTab, int *delay, int dIdx, int *uBuf); +int QMFAnalysis(int *inbuf, int *delay, int *XBuf, int fBitsIn, int *delayIdx, int qmfaBands); +void QMFSynthesisConv(int *cPtr, int *delay, int dIdx, short *outbuf, int nChans); +void QMFSynthesis(int *inbuf, int *delay, int *delayIdx, int qmfsBands, short *outbuf, int nChans); +int UnpackSBRHeader(SBRHeader *sbrHdr); +void UnpackSBRGrid(SBRHeader *sbrHdr, SBRGrid *sbrGrid); +void UnpackDeltaTimeFreq(int numEnv, uint8_t *deltaFlagEnv, int numNoiseFloors, uint8_t *deltaFlagNoise); +void UnpackInverseFilterMode(int numNoiseFloorBands, uint8_t *mode); +void UnpackSinusoids(int nHigh, int addHarmonicFlag, uint8_t *addHarmonic); +void CopyCouplingGrid(SBRGrid *sbrGridLeft, SBRGrid *sbrGridRight); +void CopyCouplingInverseFilterMode(int numNoiseFloorBands, uint8_t *modeLeft, uint8_t *modeRight); +void UnpackSBRSingleChannel(int chBase); +void UnpackSBRChannelPair(int chBase); diff --git a/yoRadio/src/audioI2S/flac_decoder/flac_decoder.cpp b/yoRadio/src/audioI2S/flac_decoder/flac_decoder.cpp new file mode 100644 index 0000000..4c2f9c0 --- /dev/null +++ b/yoRadio/src/audioI2S/flac_decoder/flac_decoder.cpp @@ -0,0 +1,556 @@ +/* + * flac_decoder.cpp + * Java source code from https://www.nayuki.io/page/simple-flac-implementation + * adapted to ESP32 + * + * Created on: Jul 03,2020 + * Updated on: Jul 03,2021 + * + * Author: Wolle + * + * + */ +#include "flac_decoder.h" +#include "vector" +using namespace std; + + +FLACFrameHeader_t *FLACFrameHeader; +FLACMetadataBlock_t *FLACMetadataBlock; +FLACsubFramesBuff_t *FLACsubFramesBuff; + +vectorcoefs; +const uint16_t outBuffSize = 2048; +uint16_t m_blockSize=0; +uint16_t m_blockSizeLeft = 0; +uint16_t m_validSamples = 0; +uint8_t m_status = 0; +uint8_t* m_inptr; +int16_t m_bytesAvail; +int16_t m_bytesDecoded = 0; +float m_compressionRatio = 0; +uint16_t m_rIndex=0; +uint64_t m_bitBuffer = 0; +uint8_t m_bitBufferLen = 0; +bool m_f_OggS_found = false; + +//---------------------------------------------------------------------------------------------------------------------- +// FLAC INI SECTION +//---------------------------------------------------------------------------------------------------------------------- +bool FLACDecoder_AllocateBuffers(void){ + if(psramFound()) { + // PSRAM found, Buffer will be allocated in PSRAM + if(!FLACFrameHeader) {FLACFrameHeader = (FLACFrameHeader_t*) ps_malloc(sizeof(FLACFrameHeader_t));} + if(!FLACMetadataBlock) {FLACMetadataBlock = (FLACMetadataBlock_t*) ps_malloc(sizeof(FLACMetadataBlock_t));} + if(!FLACsubFramesBuff) {FLACsubFramesBuff = (FLACsubFramesBuff_t*) ps_malloc(sizeof(FLACsubFramesBuff_t));} + } + else { + if(!FLACFrameHeader) {FLACFrameHeader = (FLACFrameHeader_t*) malloc(sizeof(FLACFrameHeader_t));} + if(!FLACMetadataBlock) {FLACMetadataBlock = (FLACMetadataBlock_t*) malloc(sizeof(FLACMetadataBlock_t));} + if(!FLACsubFramesBuff) {FLACsubFramesBuff = (FLACsubFramesBuff_t*) malloc(sizeof(FLACsubFramesBuff_t));} + } + if(!FLACFrameHeader || !FLACMetadataBlock || !FLACsubFramesBuff ){ + log_e("not enough memory to allocate flacdecoder buffers"); + return false; + } + FLACDecoder_ClearBuffer(); + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +void FLACDecoder_ClearBuffer(){ + memset(FLACFrameHeader, 0, sizeof(FLACFrameHeader_t)); + memset(FLACMetadataBlock, 0, sizeof(FLACMetadataBlock_t)); + memset(FLACsubFramesBuff, 0, sizeof(FLACsubFramesBuff_t)); + m_status = DECODE_FRAME; + return; +} +//---------------------------------------------------------------------------------------------------------------------- +void FLACDecoder_FreeBuffers(){ + if(FLACFrameHeader) {free(FLACFrameHeader); FLACFrameHeader = NULL;} + if(FLACMetadataBlock) {free(FLACMetadataBlock); FLACMetadataBlock = NULL;} + if(FLACsubFramesBuff) {free(FLACsubFramesBuff); FLACsubFramesBuff = NULL;} +} +//---------------------------------------------------------------------------------------------------------------------- +// B I T R E A D E R +//---------------------------------------------------------------------------------------------------------------------- +uint32_t readUint(uint8_t nBits){ + while (m_bitBufferLen < nBits){ + uint8_t temp = *(m_inptr + m_rIndex); + m_rIndex++; + m_bytesAvail--; + if(m_bytesAvail < 0) { log_i("error in bitreader"); } + m_bitBuffer = (m_bitBuffer << 8) | temp; + m_bitBufferLen += 8; + } + m_bitBufferLen -= nBits; + uint32_t result = m_bitBuffer >> m_bitBufferLen; + if (nBits < 32) + result &= (1 << nBits) - 1; + return result; +} + +int32_t readSignedInt(int nBits){ + int32_t temp = readUint(nBits) << (32 - nBits); + temp = temp >> (32 - nBits); // The C++ compiler uses the sign bit to fill vacated bit positions + return temp; +} + +int64_t readRiceSignedInt(uint8_t param){ + long val = 0; + while (readUint(1) == 0) + val++; + val = (val << param) | readUint(param); + return (val >> 1) ^ -(val & 1); +} + +void alignToByte() { + m_bitBufferLen -= m_bitBufferLen % 8; +} +//---------------------------------------------------------------------------------------------------------------------- +// F L A C - D E C O D E R +//---------------------------------------------------------------------------------------------------------------------- +void FLACSetRawBlockParams(uint8_t Chans, uint32_t SampRate, uint8_t BPS, uint32_t tsis, uint32_t AuDaLength){ + FLACMetadataBlock->numChannels = Chans; + FLACMetadataBlock->sampleRate = SampRate; + FLACMetadataBlock->bitsPerSample = BPS; + FLACMetadataBlock->totalSamples = tsis; // total samples in stream + FLACMetadataBlock->audioDataLength = AuDaLength; +} +//---------------------------------------------------------------------------------------------------------------------- +void FLACDecoderReset(){ // set var to default + m_status = DECODE_FRAME; + m_bitBuffer = 0; + m_bitBufferLen = 0; +} +//---------------------------------------------------------------------------------------------------------------------- +int FLACFindSyncWord(unsigned char *buf, int nBytes) { + int i; + + /* find byte-aligned syncword - need 13 matching bits */ + for (i = 0; i < nBytes - 1; i++) { + if ((buf[i + 0] & 0xFF) == 0xFF && (buf[i + 1] & 0xF8) == 0xF8) { + FLACDecoderReset(); + return i; + } + } + return -1; +} +//---------------------------------------------------------------------------------------------------------------------- +int FLACFindOggSyncWord(unsigned char *buf, int nBytes){ + int i; + + /* find byte-aligned syncword - need 13 matching bits */ + for (i = 0; i < nBytes - 1; i++) { + if ((buf[i + 0] & 0xFF) == 0xFF && (buf[i + 1] & 0xF8) == 0xF8) { + FLACDecoderReset(); + log_i("FLAC sync found"); + return i; + } + } + /* find byte-aligned OGG Magic - OggS */ + for (i = 0; i < nBytes - 1; i++) { + if ((buf[i + 0] == 'O') && (buf[i + 1] == 'g') && (buf[i + 2] == 'g') && (buf[i + 3] == 'S')) { + FLACDecoderReset(); + log_i("OggS found"); + m_f_OggS_found = true; + return i; + } + } + return -1; +} +//---------------------------------------------------------------------------------------------------------------------- +int FLACparseOggHeader(unsigned char *buf){ + uint8_t i = 0; + uint8_t ssv = *(buf + i); // stream_structure_version + (void)ssv; + i++; + uint8_t htf = *(buf + i); // header_type_flag + (void)htf; + i++; + uint32_t tmp = 0; // absolute granule position + for (int j = 0; j < 4; j++) { + tmp += *(buf + j + i) << (4 -j - 1) * 8; + } + i += 4; + uint64_t agp = (uint64_t) tmp << 32; + for (int j = 0; j < 4; j++) { + agp += *(buf + j + i) << (4 -j - 1) * 8; + } + i += 4; + uint32_t ssnr = 0; // stream serial number + for (int j = 0; j < 4; j++) { + ssnr += *(buf + j + i) << (4 -j - 1) * 8; + } + i += 4; + uint32_t psnr = 0; // page sequence no + for (int j = 0; j < 4; j++) { + psnr += *(buf + j + i) << (4 -j - 1) * 8; + } + i += 4; + uint32_t pchk = 0; // page checksum + for (int j = 0; j < 4; j++) { + pchk += *(buf + j + i) << (4 -j - 1) * 8; + } + i += 4; + uint8_t psegm = *(buf + i); + i++; + uint8_t psegmBuff[256]; + uint32_t pageLen = 0; + for(uint8_t j = 0; j < psegm; j++){ + psegmBuff[j] = *(buf + i); + pageLen += psegmBuff[j]; + i++; + } + return i; +} +//---------------------------------------------------------------------------------------------------------------------- +int8_t FLACDecode(uint8_t *inbuf, int *bytesLeft, short *outbuf){ + + if(m_f_OggS_found == true){ + m_f_OggS_found = false; + *bytesLeft -= FLACparseOggHeader(inbuf); + return ERR_FLAC_NONE; + } + + if(m_status != OUT_SAMPLES){ + m_rIndex = 0; + m_bytesAvail = (*bytesLeft); + m_inptr = inbuf; + } + + if(m_status == DECODE_FRAME){ // Read a ton of header fields, and ignore most of them + + if ((inbuf[0] == 'O') && (inbuf[1] == 'g') && (inbuf[2] == 'g') && (inbuf[3] == 'S')){ + *bytesLeft -= 4; + m_f_OggS_found = true; + return ERR_FLAC_NONE; + } + + uint32_t temp = readUint(8); + uint16_t sync = temp << 6 |readUint(6); + if (sync != 0x3FFE){ + log_i("Sync code expected 0x3FFE but received %X", sync); + return ERR_FLAC_SYNC_CODE_NOT_FOUND; + } + + readUint(1); + FLACFrameHeader->blockingStrategy = readUint(1); + FLACFrameHeader->blockSizeCode = readUint(4); + FLACFrameHeader->sampleRateCode = readUint(4); + FLACFrameHeader->chanAsgn = readUint(4); + FLACFrameHeader->sampleSizeCode = readUint(3); + + if(!FLACMetadataBlock->numChannels){ + if(FLACFrameHeader->chanAsgn == 0) FLACMetadataBlock->numChannels = 1; + if(FLACFrameHeader->chanAsgn == 1) FLACMetadataBlock->numChannels = 2; + if(FLACFrameHeader->chanAsgn > 7) FLACMetadataBlock->numChannels = 2; + } + if(FLACMetadataBlock->numChannels < 1) return ERR_FLAC_UNKNOWN_CHANNEL_ASSIGNMENT; + + if(!FLACMetadataBlock->bitsPerSample){ + if(FLACFrameHeader->sampleSizeCode == 1) FLACMetadataBlock->bitsPerSample = 8; + if(FLACFrameHeader->sampleSizeCode == 2) FLACMetadataBlock->bitsPerSample = 12; + if(FLACFrameHeader->sampleSizeCode == 4) FLACMetadataBlock->bitsPerSample = 16; + if(FLACFrameHeader->sampleSizeCode == 5) FLACMetadataBlock->bitsPerSample = 20; + if(FLACFrameHeader->sampleSizeCode == 6) FLACMetadataBlock->bitsPerSample = 24; + } + if(FLACMetadataBlock->bitsPerSample > 16) return ERR_FLAC_BITS_PER_SAMPLE_TOO_BIG; + if(FLACMetadataBlock->bitsPerSample < 8 ) return ERR_FLAG_BITS_PER_SAMPLE_UNKNOWN; + + if(!FLACMetadataBlock->sampleRate){ + if(FLACFrameHeader->sampleRateCode == 1) FLACMetadataBlock->sampleRate = 88200; + if(FLACFrameHeader->sampleRateCode == 2) FLACMetadataBlock->sampleRate = 176400; + if(FLACFrameHeader->sampleRateCode == 3) FLACMetadataBlock->sampleRate = 192000; + if(FLACFrameHeader->sampleRateCode == 4) FLACMetadataBlock->sampleRate = 8000; + if(FLACFrameHeader->sampleRateCode == 5) FLACMetadataBlock->sampleRate = 16000; + if(FLACFrameHeader->sampleRateCode == 6) FLACMetadataBlock->sampleRate = 22050; + if(FLACFrameHeader->sampleRateCode == 7) FLACMetadataBlock->sampleRate = 24000; + if(FLACFrameHeader->sampleRateCode == 8) FLACMetadataBlock->sampleRate = 32000; + if(FLACFrameHeader->sampleRateCode == 9) FLACMetadataBlock->sampleRate = 44100; + if(FLACFrameHeader->sampleRateCode == 10) FLACMetadataBlock->sampleRate = 48000; + if(FLACFrameHeader->sampleRateCode == 11) FLACMetadataBlock->sampleRate = 96000; + } + + readUint(1); + temp = (readUint(8) << 24); + temp = ~temp; + + uint32_t shift = 0x80000000; // Number of leading zeros + int8_t count = 0; + for(int i=0; i<32; i++){ + if((temp & shift) == 0) {count++; shift >>= 1;} + else break; + } + count--; + for (int i = 0; i < count; i++) readUint(8); + m_blockSize = 0; + + if (FLACFrameHeader->blockSizeCode == 1) + m_blockSize = 192; + else if (2 <= FLACFrameHeader->blockSizeCode && FLACFrameHeader->blockSizeCode <= 5) + m_blockSize = 576 << (FLACFrameHeader->blockSizeCode - 2); + else if (FLACFrameHeader->blockSizeCode == 6) + m_blockSize = readUint(8) + 1; + else if (FLACFrameHeader->blockSizeCode == 7) + m_blockSize = readUint(16) + 1; + else if (8 <= FLACFrameHeader->blockSizeCode && FLACFrameHeader->blockSizeCode <= 15) + m_blockSize = 256 << (FLACFrameHeader->blockSizeCode - 8); + else{ + return ERR_FLAC_RESERVED_BLOCKSIZE_UNSUPPORTED; + } + + if(m_blockSize > 8192){ + log_e("Error: blockSize too big"); + return ERR_FLAC_BLOCKSIZE_TOO_BIG; + } + + if(FLACFrameHeader->sampleRateCode == 12) + readUint(8); + else if (FLACFrameHeader->sampleRateCode == 13 || FLACFrameHeader->sampleRateCode == 14){ + readUint(16); + } + readUint(8); + m_status = DECODE_SUBFRAMES; + *bytesLeft = m_bytesAvail; + m_blockSizeLeft = m_blockSize; + + return ERR_FLAC_NONE; + } + + if(m_status == DECODE_SUBFRAMES){ + + // Decode each channel's subframe, then skip footer + int ret = decodeSubframes(); + if(ret != 0) return ret; + m_status = OUT_SAMPLES; + } + + if(m_status == OUT_SAMPLES){ // Write the decoded samples + // blocksize can be much greater than outbuff, so we can't stuff all in once + // therefore we need often more than one loop (split outputblock into pieces) + uint16_t blockSize; + static uint16_t offset = 0; + if(m_blockSize < outBuffSize + offset) blockSize = m_blockSize - offset; + else blockSize = outBuffSize; + + + for (int i = 0; i < blockSize; i++) { + for (int j = 0; j < FLACMetadataBlock->numChannels; j++) { + int val = FLACsubFramesBuff->samplesBuffer[j][i + offset]; + if (FLACMetadataBlock->bitsPerSample == 8) val += 128; + outbuf[2*i+j] = val; + } + } + + m_validSamples = blockSize * FLACMetadataBlock->numChannels; + offset += blockSize; + + if(offset != m_blockSize) return GIVE_NEXT_LOOP; + offset = 0; + if(offset > m_blockSize) { log_e("offset has a wrong value"); } + } + + alignToByte(); + readUint(16); + m_bytesDecoded = *bytesLeft - m_bytesAvail; +// log_i("m_bytesDecoded %i", m_bytesDecoded); +// m_compressionRatio = (float)m_bytesDecoded / (float)m_blockSize * FLACMetadataBlock->numChannels * (16/8); +// log_i("m_compressionRatio % f", m_compressionRatio); + *bytesLeft = m_bytesAvail; + m_status = DECODE_FRAME; + return ERR_FLAC_NONE; +} +//---------------------------------------------------------------------------------------------------------------------- +uint16_t FLACGetOutputSamps(){ + int vs = m_validSamples; + m_validSamples=0; + return vs; +} +//---------------------------------------------------------------------------------------------------------------------- +uint64_t FLACGetTotoalSamplesInStream(){ + return FLACMetadataBlock->totalSamples; +} +//---------------------------------------------------------------------------------------------------------------------- +uint8_t FLACGetBitsPerSample(){ + return FLACMetadataBlock->bitsPerSample; +} +//---------------------------------------------------------------------------------------------------------------------- +uint8_t FLACGetChannels(){ + return FLACMetadataBlock->numChannels; +} +//---------------------------------------------------------------------------------------------------------------------- +uint32_t FLACGetSampRate(){ + return FLACMetadataBlock->sampleRate; +} +//---------------------------------------------------------------------------------------------------------------------- +uint32_t FLACGetBitRate(){ + if(FLACMetadataBlock->totalSamples){ + float BitsPerSamp = (float)FLACMetadataBlock->audioDataLength / (float)FLACMetadataBlock->totalSamples * 8; + return ((uint32_t)BitsPerSamp * FLACMetadataBlock->sampleRate); + } + return 0; +} +//---------------------------------------------------------------------------------------------------------------------- +uint32_t FLACGetAudioFileDuration() { + if(FLACGetSampRate()){ + uint32_t afd = FLACGetTotoalSamplesInStream()/ FLACGetSampRate(); // AudioFileDuration + return afd; + } + return 0; +} +//---------------------------------------------------------------------------------------------------------------------- +int8_t decodeSubframes(){ + if(FLACFrameHeader->chanAsgn <= 7) { + for (int ch = 0; ch < FLACMetadataBlock->numChannels; ch++) + decodeSubframe(FLACMetadataBlock->bitsPerSample, ch); + } + else if (8 <= FLACFrameHeader->chanAsgn && FLACFrameHeader->chanAsgn <= 10) { + decodeSubframe(FLACMetadataBlock->bitsPerSample + (FLACFrameHeader->chanAsgn == 9 ? 1 : 0), 0); + decodeSubframe(FLACMetadataBlock->bitsPerSample + (FLACFrameHeader->chanAsgn == 9 ? 0 : 1), 1); + if(FLACFrameHeader->chanAsgn == 8) { + for (int i = 0; i < m_blockSize; i++) + FLACsubFramesBuff->samplesBuffer[1][i] = ( + FLACsubFramesBuff->samplesBuffer[0][i] - + FLACsubFramesBuff->samplesBuffer[1][i]); + } + else if (FLACFrameHeader->chanAsgn == 9) { + for (int i = 0; i < m_blockSize; i++) + FLACsubFramesBuff->samplesBuffer[0][i] += FLACsubFramesBuff->samplesBuffer[1][i]; + } + else if (FLACFrameHeader->chanAsgn == 10) { + for (int i = 0; i < m_blockSize; i++) { + long side = FLACsubFramesBuff->samplesBuffer[1][i]; + long right = FLACsubFramesBuff->samplesBuffer[0][i] - (side >> 1); + FLACsubFramesBuff->samplesBuffer[1][i] = right; + FLACsubFramesBuff->samplesBuffer[0][i] = right + side; + } + } + else { + log_e("unknown channel assignment"); + return ERR_FLAC_UNKNOWN_CHANNEL_ASSIGNMENT; + } + } + else{ + log_e("Reserved channel assignment"); + return ERR_FLAC_RESERVED_CHANNEL_ASSIGNMENT; + } + return ERR_FLAC_NONE; +} +//---------------------------------------------------------------------------------------------------------------------- +int8_t decodeSubframe(uint8_t sampleDepth, uint8_t ch) { + int8_t ret = 0; + readUint(1); + uint8_t type = readUint(6); + int shift = readUint(1); + if (shift == 1) { + while (readUint(1) == 0) + shift++; + } + sampleDepth -= shift; + + if(type == 0){ // Constant coding + int16_t s= readSignedInt(sampleDepth); + for(int i=0; i < m_blockSize; i++){ + FLACsubFramesBuff->samplesBuffer[ch][i] = s; + } + } + else if (type == 1) { // Verbatim coding + for (int i = 0; i < m_blockSize; i++) + FLACsubFramesBuff->samplesBuffer[ch][i] = readSignedInt(sampleDepth); + } + else if (8 <= type && type <= 12){ + ret = decodeFixedPredictionSubframe(type - 8, sampleDepth, ch); + if(ret) return ret; + } + else if (32 <= type && type <= 63){ + ret = decodeLinearPredictiveCodingSubframe(type - 31, sampleDepth, ch); + if(ret) return ret; + } + else{ + return ERR_FLAC_RESERVED_SUB_TYPE; + } + if(shift>0){ + for (int i = 0; i < m_blockSize; i++){ + FLACsubFramesBuff->samplesBuffer[ch][i] <<= shift; + } + } + return ERR_FLAC_NONE; +} +//---------------------------------------------------------------------------------------------------------------------- +int8_t decodeFixedPredictionSubframe(uint8_t predOrder, uint8_t sampleDepth, uint8_t ch) { + uint8_t ret = 0; + for(uint8_t i = 0; i < predOrder; i++) + FLACsubFramesBuff->samplesBuffer[ch][i] = readSignedInt(sampleDepth); + ret = decodeResiduals(predOrder, ch); + if(ret) return ret; + coefs.clear(); + if(predOrder == 0) coefs.resize(0); + if(predOrder == 1) coefs.push_back(1); // FIXED_PREDICTION_COEFFICIENTS + if(predOrder == 2){coefs.push_back(2); coefs.push_back(-1);} + if(predOrder == 3){coefs.push_back(3); coefs.push_back(-3); coefs.push_back(1);} + if(predOrder == 4){coefs.push_back(4); coefs.push_back(-6); coefs.push_back(4); coefs.push_back(-1);} + if(predOrder > 4) return ERR_FLAC_PREORDER_TOO_BIG; // Error: preorder > 4" + restoreLinearPrediction(ch, 0); + return ERR_FLAC_NONE; +} +//---------------------------------------------------------------------------------------------------------------------- +int8_t decodeLinearPredictiveCodingSubframe(int lpcOrder, int sampleDepth, uint8_t ch){ + int8_t ret = 0; + for (int i = 0; i < lpcOrder; i++) + FLACsubFramesBuff->samplesBuffer[ch][i] = readSignedInt(sampleDepth); + int precision = readUint(4) + 1; + int shift = readSignedInt(5); + coefs.resize(0); + for (uint8_t i = 0; i < lpcOrder; i++) + coefs.push_back(readSignedInt(precision)); + ret = decodeResiduals(lpcOrder, ch); + if(ret) return ret; + restoreLinearPrediction(ch, shift); + return ERR_FLAC_NONE; +} +//---------------------------------------------------------------------------------------------------------------------- +int8_t decodeResiduals(uint8_t warmup, uint8_t ch) { + + int method = readUint(2); + if (method >= 2) + return ERR_FLAC_RESERVED_RESIDUAL_CODING; // Reserved residual coding method + uint8_t paramBits = method == 0 ? 4 : 5; + int escapeParam = (method == 0 ? 0xF : 0x1F); + int partitionOrder = readUint(4); + + int numPartitions = 1 << partitionOrder; + if (m_blockSize % numPartitions != 0) + return ERR_FLAC_WRONG_RICE_PARTITION_NR; //Error: Block size not divisible by number of Rice partitions + int partitionSize = m_blockSize/ numPartitions; + + for (int i = 0; i < numPartitions; i++) { + int start = i * partitionSize + (i == 0 ? warmup : 0); + int end = (i + 1) * partitionSize; + + int param = readUint(paramBits); + if (param < escapeParam) { + for (int j = start; j < end; j++){ + FLACsubFramesBuff->samplesBuffer[ch][j] = readRiceSignedInt(param); + } + } else { + int numBits = readUint(5); + for (int j = start; j < end; j++){ + FLACsubFramesBuff->samplesBuffer[ch][j] = readSignedInt(numBits); + } + } + } + return ERR_FLAC_NONE; +} +//---------------------------------------------------------------------------------------------------------------------- +void restoreLinearPrediction(uint8_t ch, uint8_t shift) { + + for (int i = coefs.size(); i < m_blockSize; i++) { + int32_t sum = 0; + for (int j = 0; j < coefs.size(); j++){ + sum += FLACsubFramesBuff->samplesBuffer[ch][i - 1 - j] * coefs[j]; + } + FLACsubFramesBuff->samplesBuffer[ch][i] += (sum >> shift); + } +} +//---------------------------------------------------------------------------------------------------------------------- + diff --git a/yoRadio/src/audioI2S/flac_decoder/flac_decoder.h b/yoRadio/src/audioI2S/flac_decoder/flac_decoder.h new file mode 100644 index 0000000..44bf41f --- /dev/null +++ b/yoRadio/src/audioI2S/flac_decoder/flac_decoder.h @@ -0,0 +1,175 @@ +/* + * flac_decoder.h + * + * Created on: Jul 03,2020 + * Updated on: Apr 27,2021 + * + * Author: wolle + * + * Restrictions: + * blocksize must not exceed 8192 + * bits per sample must be 8 or 16 + * num Channels must be 1 or 2 + * + * + */ +#pragma once +#pragma GCC optimize ("Ofast") + +#include "Arduino.h" + +#define MAX_CHANNELS 2 +#define MAX_BLOCKSIZE 8192 +#define APLL_DISABLE 0 +#define EXTERNAL_I2S 0 + + +typedef struct FLACsubFramesBuff_t{ + int32_t samplesBuffer[MAX_CHANNELS][MAX_BLOCKSIZE]; +}FLACsubframesBuffer_t; + +enum : uint8_t {FLACDECODER_INIT, FLACDECODER_READ_IN, FLACDECODER_WRITE_OUT}; +enum : uint8_t {DECODE_FRAME, DECODE_SUBFRAMES, OUT_SAMPLES}; +enum : int8_t {GIVE_NEXT_LOOP = +1, + ERR_FLAC_NONE = 0, + ERR_FLAC_BLOCKSIZE_TOO_BIG = -1, + ERR_FLAC_RESERVED_BLOCKSIZE_UNSUPPORTED = -2, + ERR_FLAC_SYNC_CODE_NOT_FOUND = -3, + ERR_FLAC_UNKNOWN_CHANNEL_ASSIGNMENT = -4, + ERR_FLAC_RESERVED_CHANNEL_ASSIGNMENT = -5, + ERR_FLAC_RESERVED_SUB_TYPE = -6, + ERR_FLAC_PREORDER_TOO_BIG = -7, + ERR_FLAC_RESERVED_RESIDUAL_CODING = -8, + ERR_FLAC_WRONG_RICE_PARTITION_NR = -9, + ERR_FLAC_BITS_PER_SAMPLE_TOO_BIG = -10, + ERR_FLAG_BITS_PER_SAMPLE_UNKNOWN = 11}; + +typedef struct FLACMetadataBlock_t{ + // METADATA_BLOCK_STREAMINFO + uint16_t minblocksize; // The minimum block size (in samples) used in the stream. + //---------------------------------------------------------------------------------------- + // The maximum block size (in samples) used in the stream. + uint16_t maxblocksize; // (Minimum blocksize == maximum blocksize) implies a fixed-blocksize stream. + //---------------------------------------------------------------------------------------- + // The minimum frame size (in bytes) used in the stream. + uint32_t minframesize; // May be 0 to imply the value is not known. + //---------------------------------------------------------------------------------------- + // The maximum frame size (in bytes) used in the stream. + uint32_t maxframesize; // May be 0 to imply the value is not known. + //---------------------------------------------------------------------------------------- + // Sample rate in Hz. Though 20 bits are available, + // the maximum sample rate is limited by the structure of frame headers to 655350Hz. + uint32_t sampleRate; // Also, a value of 0 is invalid. + //---------------------------------------------------------------------------------------- + // Number of channels FLAC supports from 1 to 8 channels + uint8_t numChannels; // 000 : 1 channel .... 111 : 8 channels + //---------------------------------------------------------------------------------------- + // Sample size in bits: + // 000 : get from STREAMINFO metadata block + // 001 : 8 bits per sample + // 010 : 12 bits per sample + // 011 : reserved + // 100 : 16 bits per sample + // 101 : 20 bits per sample + // 110 : 24 bits per sample + uint8_t bitsPerSample; // 111 : reserved + //---------------------------------------------------------------------------------------- + // Total samples in stream. 'Samples' means inter-channel sample, + // i.e. one second of 44.1Khz audio will have 44100 samples regardless of the number + uint64_t totalSamples; // of channels. A value of zero here means the number of total samples is unknown. + //---------------------------------------------------------------------------------------- + uint32_t audioDataLength;// is not the filelength, is only the length of the audio datablock in bytes + + + +}FLACMetadataBlock_t; + + +typedef struct FLACFrameHeader_t { + // 0 : fixed-blocksize stream; frame header encodes the frame number + uint8_t blockingStrategy; // 1 : variable-blocksize stream; frame header encodes the sample number + //---------------------------------------------------------------------------------------- + // Block size in inter-channel samples: + // 0000 : reserved + // 0001 : 192 samples + // 0010-0101 : 576 * (2^(n-2)) samples, i.e. 576/1152/2304/4608 + // 0110 : get 8 bit (blocksize-1) from end of header + // 0111 : get 16 bit (blocksize-1) from end of header + uint8_t blockSizeCode; // 1000-1111 : 256 * (2^(n-8)) samples, i.e. 256/512/1024/2048/4096/8192/16384/32768 + //---------------------------------------------------------------------------------------- + // 0000 : get from STREAMINFO metadata block + // 0001 : 88.2kHz + // 0010 : 176.4kHz + // 0011 : 192kHz + // 0100 : 8kHz + // 0101 : 16kHz + // 0110 : 22.05kHz + // 0111 : 24kHz + // 1000 : 32kHz + // 1001 : 44.1kHz + // 1010 : 48kHz + // 1011 : 96kHz + // 1100 : get 8 bit sample rate (in kHz) from end of header + // 1101 : get 16 bit sample rate (in Hz) from end of header + // 1110 : get 16 bit sample rate (in tens of Hz) from end of header + uint8_t sampleRateCode; // 1111 : invalid, to prevent sync-fooling string of 1s + //---------------------------------------------------------------------------------------- + // Channel assignment + // 0000 1 channel: mono + // 0001 2 channels: left, right + // 0010 3 channels + // 0011 4 channels + // 0100 5 channels + // 0101 6 channels + // 0110 7 channels + // 0111 8 channels + // 1000 : left/side stereo: channel 0 is the left channel, channel 1 is the side(difference) channel + // 1001 : right/side stereo: channel 0 is the side(difference) channel, channel 1 is the right channel + // 1010 : mid/side stereo: channel 0 is the mid(average) channel, channel 1 is the side(difference) channel + uint8_t chanAsgn; // 1011-1111 : reserved + //---------------------------------------------------------------------------------------- + // Sample size in bits: + // 000 : get from STREAMINFO metadata block + // 001 : 8 bits per sample + // 010 : 12 bits per sample + // 011 : reserved + // 100 : 16 bits per sample + // 101 : 20 bits per sample + // 110 : 24 bits per sample + uint8_t sampleSizeCode; // 111 : reserved + //---------------------------------------------------------------------------------------- + uint32_t totalSamples; // totalSamplesInStream + //---------------------------------------------------------------------------------------- + uint32_t bitrate; // bitrate + + +}FLACFrameHeader_t; + +int FLACFindSyncWord(unsigned char *buf, int nBytes); +int FLACFindOggSyncWord(unsigned char *buf, int nBytes); +int FLACparseOggHeader(unsigned char *buf); +bool FLACDecoder_AllocateBuffers(void); +void FLACDecoder_ClearBuffer(); +void FLACDecoder_FreeBuffers(); +void FLACSetRawBlockParams(uint8_t Chans, uint32_t SampRate, uint8_t BPS, uint32_t tsis, uint32_t AuDaLength); +void FLACDecoderReset(); +int8_t FLACDecode(uint8_t *inbuf, int *bytesLeft, short *outbuf); +uint16_t FLACGetOutputSamps(); +uint64_t FLACGetTotoalSamplesInStream(); +uint8_t FLACGetBitsPerSample(); +uint8_t FLACGetChannels(); +uint32_t FLACGetSampRate(); +uint32_t FLACGetBitRate(); +uint32_t FLACGetAudioFileDuration(); +uint32_t readUint(uint8_t nBits); +int32_t readSignedInt(int nBits); +int64_t readRiceSignedInt(uint8_t param); +void alignToByte(); +int8_t decodeSubframes(); +int8_t decodeSubframe(uint8_t sampleDepth, uint8_t ch); +int8_t decodeFixedPredictionSubframe(uint8_t predOrder, uint8_t sampleDepth, uint8_t ch); +int8_t decodeLinearPredictiveCodingSubframe(int lpcOrder, int sampleDepth, uint8_t ch); +int8_t decodeResiduals(uint8_t warmup, uint8_t ch); +void restoreLinearPrediction(uint8_t ch, uint8_t shift); + + diff --git a/yoRadio/src/audioI2S/mp3_decoder/mp3_decoder.cpp b/yoRadio/src/audioI2S/mp3_decoder/mp3_decoder.cpp new file mode 100644 index 0000000..e2c6ae0 --- /dev/null +++ b/yoRadio/src/audioI2S/mp3_decoder/mp3_decoder.cpp @@ -0,0 +1,3822 @@ +/* + * mp3_decoder.cpp + * libhelix_HMP3DECODER + * + * Created on: 26.10.2018 + * Updated on: 03.01.2022 + */ +#include "mp3_decoder.h" +/* clip to range [-2^n, 2^n - 1] */ +#if 0 //Fast on ARM: +#define CLIP_2N(y, n) { \ + int sign = (y) >> 31; \ + if (sign != (y) >> (n)) { \ + (y) = sign ^ ((1 << (n)) - 1); \ + } \ +} +#else //on xtensa this is faster, due to asm min/max instructions: +#define CLIP_2N(y, n) { \ + int x = 1 << n; \ + if (y < -x) y = -x; \ + x--; \ + if (y > x) y = x; \ +} +#endif + +const uint8_t m_SYNCWORDH =0xff; +const uint8_t m_SYNCWORDL =0xf0; +const uint8_t m_DQ_FRACBITS_OUT =25; // number of fraction bits in output of dequant +const uint8_t m_CSHIFT =12; // coefficients have 12 leading sign bits for early-terminating mulitplies +const uint8_t m_SIBYTES_MPEG1_MONO =17; +const uint8_t m_SIBYTES_MPEG1_STEREO =32; +const uint8_t m_SIBYTES_MPEG2_MONO =9; +const uint8_t m_SIBYTES_MPEG2_STEREO =17; +const uint8_t m_IMDCT_SCALE =2; // additional scaling (by sqrt(2)) for fast IMDCT36 +const uint8_t m_NGRANS_MPEG1 =2; +const uint8_t m_NGRANS_MPEG2 =1; +const uint32_t m_SQRTHALF =0x5a82799a; // sqrt(0.5) in Q31 format + + +MP3FrameInfo_t *m_MP3FrameInfo; +SFBandTable_t m_SFBandTable; +StereoMode_t m_sMode; /* mono/stereo mode */ +MPEGVersion_t m_MPEGVersion; /* version ID */ +FrameHeader_t *m_FrameHeader; +SideInfoSub_t m_SideInfoSub[m_MAX_NGRAN][m_MAX_NCHAN]; +SideInfo_t *m_SideInfo; +CriticalBandInfo_t m_CriticalBandInfo[m_MAX_NCHAN]; /* filled in dequantizer, used in joint stereo reconstruction */ +DequantInfo_t *m_DequantInfo; +HuffmanInfo_t *m_HuffmanInfo; +IMDCTInfo_t *m_IMDCTInfo; +ScaleFactorInfoSub_t m_ScaleFactorInfoSub[m_MAX_NGRAN][m_MAX_NCHAN]; +ScaleFactorJS_t *m_ScaleFactorJS; +SubbandInfo_t *m_SubbandInfo; +MP3DecInfo_t *m_MP3DecInfo; + +const unsigned short huffTable[4242] PROGMEM = { + /* huffTable01[9] */ + 0xf003, 0x3112, 0x3101, 0x2011, 0x2011, 0x1000, 0x1000, 0x1000, 0x1000, + /* huffTable02[65] */ + 0xf006, 0x6222, 0x6201, 0x5212, 0x5212, 0x5122, 0x5122, 0x5021, 0x5021, 0x3112, 0x3112, 0x3112, + 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + /* huffTable03[65] */ + 0xf006, 0x6222, 0x6201, 0x5212, 0x5212, 0x5122, 0x5122, 0x5021, 0x5021, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, + 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2101, 0x2101, 0x2101, + 0x2101, 0x2101, 0x2101, 0x2101, 0x2101, 0x2101, 0x2101, 0x2101, 0x2101, 0x2101, 0x2101, 0x2101, + 0x2101, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, + 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, + /* huffTable05[257] */ + 0xf008, 0x8332, 0x8322, 0x7232, 0x7232, 0x6132, 0x6132, 0x6132, 0x6132, 0x7312, 0x7312, 0x7301, + 0x7301, 0x7031, 0x7031, 0x7222, 0x7222, 0x6212, 0x6212, 0x6212, 0x6212, 0x6122, 0x6122, 0x6122, + 0x6122, 0x6201, 0x6201, 0x6201, 0x6201, 0x6021, 0x6021, 0x6021, 0x6021, 0x3112, 0x3112, 0x3112, + 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, + 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, + 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + /* huffTable06[129] */ + 0xf007, 0x7332, 0x7301, 0x6322, 0x6322, 0x6232, 0x6232, 0x6031, 0x6031, 0x5312, 0x5312, 0x5312, + 0x5312, 0x5132, 0x5132, 0x5132, 0x5132, 0x5222, 0x5222, 0x5222, 0x5222, 0x5201, 0x5201, 0x5201, + 0x5201, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4122, 0x4122, 0x4122, + 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x4021, 0x4021, 0x4021, 0x4021, 0x4021, 0x4021, 0x4021, + 0x4021, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, + 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, + 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, + 0x2112, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, + 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, + /* huffTable07[110] */ + 0xf006, 0x0041, 0x0052, 0x005b, 0x0060, 0x0063, 0x0068, 0x006b, 0x6212, 0x5122, 0x5122, 0x6201, + 0x6021, 0x4112, 0x4112, 0x4112, 0x4112, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0xf004, 0x4552, 0x4542, 0x4452, 0x4352, 0x3532, 0x3532, + 0x3442, 0x3442, 0x3522, 0x3522, 0x3252, 0x3252, 0x2512, 0x2512, 0x2512, 0x2512, 0xf003, 0x2152, + 0x2152, 0x3501, 0x3432, 0x2051, 0x2051, 0x3342, 0x3332, 0xf002, 0x2422, 0x2242, 0x1412, 0x1412, + 0xf001, 0x1142, 0x1041, 0xf002, 0x2401, 0x2322, 0x2232, 0x2301, 0xf001, 0x1312, 0x1132, 0xf001, + 0x1031, 0x1222, + /* huffTable08[280] */ + 0xf008, 0x0101, 0x010a, 0x010f, 0x8512, 0x8152, 0x0112, 0x0115, 0x8422, 0x8242, 0x8412, 0x7142, + 0x7142, 0x8401, 0x8041, 0x8322, 0x8232, 0x8312, 0x8132, 0x8301, 0x8031, 0x6222, 0x6222, 0x6222, + 0x6222, 0x6201, 0x6201, 0x6201, 0x6201, 0x6021, 0x6021, 0x6021, 0x6021, 0x4212, 0x4212, 0x4212, + 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, + 0x4212, 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, + 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, + 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, + 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, + 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, + 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, + 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, + 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, + 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, + 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, + 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, + 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0xf003, 0x3552, 0x3452, 0x2542, 0x2542, 0x1352, 0x1352, + 0x1352, 0x1352, 0xf002, 0x2532, 0x2442, 0x1522, 0x1522, 0xf001, 0x1252, 0x1501, 0xf001, 0x1432, + 0x1342, 0xf001, 0x1051, 0x1332, + /* huffTable09[93] */ + 0xf006, 0x0041, 0x004a, 0x004f, 0x0052, 0x0057, 0x005a, 0x6412, 0x6142, 0x6322, 0x6232, 0x5312, + 0x5312, 0x5132, 0x5132, 0x6301, 0x6031, 0x5222, 0x5222, 0x5201, 0x5201, 0x4212, 0x4212, 0x4212, + 0x4212, 0x4122, 0x4122, 0x4122, 0x4122, 0x4021, 0x4021, 0x4021, 0x4021, 0x3112, 0x3112, 0x3112, + 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3000, 0x3000, 0x3000, + 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0xf003, 0x3552, 0x3542, 0x2532, 0x2532, 0x2352, 0x2352, + 0x3452, 0x3501, 0xf002, 0x2442, 0x2522, 0x2252, 0x2512, 0xf001, 0x1152, 0x1432, 0xf002, 0x1342, + 0x1342, 0x2051, 0x2401, 0xf001, 0x1422, 0x1242, 0xf001, 0x1332, 0x1041, + /* huffTable10[320] */ + 0xf008, 0x0101, 0x010a, 0x010f, 0x0118, 0x011b, 0x0120, 0x0125, 0x8712, 0x8172, 0x012a, 0x012d, + 0x0132, 0x8612, 0x8162, 0x8061, 0x0137, 0x013a, 0x013d, 0x8412, 0x8142, 0x8041, 0x8322, 0x8232, + 0x8301, 0x7312, 0x7312, 0x7132, 0x7132, 0x7031, 0x7031, 0x7222, 0x7222, 0x6212, 0x6212, 0x6212, + 0x6212, 0x6122, 0x6122, 0x6122, 0x6122, 0x6201, 0x6201, 0x6201, 0x6201, 0x6021, 0x6021, 0x6021, + 0x6021, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, + 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0xf003, 0x3772, 0x3762, 0x3672, 0x3752, 0x3572, 0x3662, + 0x2742, 0x2742, 0xf002, 0x2472, 0x2652, 0x2562, 0x2732, 0xf003, 0x2372, 0x2372, 0x2642, 0x2642, + 0x3552, 0x3452, 0x2362, 0x2362, 0xf001, 0x1722, 0x1272, 0xf002, 0x2462, 0x2701, 0x1071, 0x1071, + 0xf002, 0x1262, 0x1262, 0x2542, 0x2532, 0xf002, 0x1601, 0x1601, 0x2352, 0x2442, 0xf001, 0x1632, + 0x1622, 0xf002, 0x2522, 0x2252, 0x1512, 0x1512, 0xf002, 0x1152, 0x1152, 0x2432, 0x2342, 0xf001, + 0x1501, 0x1051, 0xf001, 0x1422, 0x1242, 0xf001, 0x1332, 0x1401, + /* huffTable11[296] */ + 0xf008, 0x0101, 0x0106, 0x010f, 0x0114, 0x0117, 0x8722, 0x8272, 0x011c, 0x7172, 0x7172, 0x8712, + 0x8071, 0x8632, 0x8362, 0x8061, 0x011f, 0x0122, 0x8512, 0x7262, 0x7262, 0x8622, 0x8601, 0x7612, + 0x7612, 0x7162, 0x7162, 0x8152, 0x8432, 0x8051, 0x0125, 0x8422, 0x8242, 0x8412, 0x8142, 0x8401, + 0x8041, 0x7322, 0x7322, 0x7232, 0x7232, 0x6312, 0x6312, 0x6312, 0x6312, 0x6132, 0x6132, 0x6132, + 0x6132, 0x7301, 0x7301, 0x7031, 0x7031, 0x6222, 0x6222, 0x6222, 0x6222, 0x5122, 0x5122, 0x5122, + 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, + 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x5201, 0x5201, 0x5201, + 0x5201, 0x5201, 0x5201, 0x5201, 0x5201, 0x5021, 0x5021, 0x5021, 0x5021, 0x5021, 0x5021, 0x5021, + 0x5021, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, + 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, + 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, + 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, + 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, + 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, + 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, + 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0xf002, 0x2772, 0x2762, 0x2672, 0x2572, 0xf003, 0x2662, + 0x2662, 0x2742, 0x2742, 0x2472, 0x2472, 0x3752, 0x3552, 0xf002, 0x2652, 0x2562, 0x1732, 0x1732, + 0xf001, 0x1372, 0x1642, 0xf002, 0x2542, 0x2452, 0x2532, 0x2352, 0xf001, 0x1462, 0x1701, 0xf001, + 0x1442, 0x1522, 0xf001, 0x1252, 0x1501, 0xf001, 0x1342, 0x1332, + /* huffTable12[185] */ + 0xf007, 0x0081, 0x008a, 0x008f, 0x0092, 0x0097, 0x009a, 0x009d, 0x00a2, 0x00a5, 0x00a8, 0x7622, + 0x7262, 0x7162, 0x00ad, 0x00b0, 0x00b3, 0x7512, 0x7152, 0x7432, 0x7342, 0x00b6, 0x7422, 0x7242, + 0x7412, 0x6332, 0x6332, 0x6142, 0x6142, 0x6322, 0x6322, 0x6232, 0x6232, 0x7041, 0x7301, 0x6031, + 0x6031, 0x5312, 0x5312, 0x5312, 0x5312, 0x5132, 0x5132, 0x5132, 0x5132, 0x5222, 0x5222, 0x5222, + 0x5222, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4122, 0x4122, 0x4122, + 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x5201, 0x5201, 0x5201, 0x5201, 0x5021, 0x5021, 0x5021, + 0x5021, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x3112, 0x3112, 0x3112, + 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, + 0x3112, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0xf003, 0x3772, 0x3762, + 0x2672, 0x2672, 0x2752, 0x2752, 0x2572, 0x2572, 0xf002, 0x2662, 0x2742, 0x2472, 0x2562, 0xf001, + 0x1652, 0x1732, 0xf002, 0x2372, 0x2552, 0x1722, 0x1722, 0xf001, 0x1272, 0x1642, 0xf001, 0x1462, + 0x1712, 0xf002, 0x1172, 0x1172, 0x2701, 0x2071, 0xf001, 0x1632, 0x1362, 0xf001, 0x1542, 0x1452, + 0xf002, 0x1442, 0x1442, 0x2601, 0x2501, 0xf001, 0x1612, 0x1061, 0xf001, 0x1532, 0x1352, 0xf001, + 0x1522, 0x1252, 0xf001, 0x1051, 0x1401, + /* huffTable13[497] */ + 0xf006, 0x0041, 0x0082, 0x00c3, 0x00e4, 0x0105, 0x0116, 0x011f, 0x0130, 0x0139, 0x013e, 0x0143, + 0x0146, 0x6212, 0x6122, 0x6201, 0x6021, 0x4112, 0x4112, 0x4112, 0x4112, 0x4101, 0x4101, 0x4101, + 0x4101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0xf006, 0x0108, 0x0111, 0x011a, 0x0123, 0x012c, 0x0131, + 0x0136, 0x013f, 0x0144, 0x0147, 0x014c, 0x0151, 0x0156, 0x015b, 0x6f12, 0x61f2, 0x60f1, 0x0160, + 0x0163, 0x0166, 0x62e2, 0x0169, 0x6e12, 0x61e2, 0x016c, 0x016f, 0x0172, 0x0175, 0x0178, 0x017b, + 0x66c2, 0x6d32, 0x017e, 0x6d22, 0x62d2, 0x6d12, 0x67b2, 0x0181, 0x0184, 0x63c2, 0x0187, 0x6b42, + 0x51d2, 0x51d2, 0x6d01, 0x60d1, 0x6a82, 0x68a2, 0x6c42, 0x64c2, 0x6b62, 0x66b2, 0x5c32, 0x5c32, + 0x5c22, 0x5c22, 0x52c2, 0x52c2, 0x5b52, 0x5b52, 0x65b2, 0x6982, 0x5c12, 0x5c12, 0xf006, 0x51c2, + 0x51c2, 0x6892, 0x6c01, 0x50c1, 0x50c1, 0x64b2, 0x6a62, 0x66a2, 0x6972, 0x5b32, 0x5b32, 0x53b2, + 0x53b2, 0x6882, 0x6a52, 0x5b22, 0x5b22, 0x65a2, 0x6962, 0x54a2, 0x54a2, 0x6872, 0x6782, 0x5492, + 0x5492, 0x6772, 0x6672, 0x42b2, 0x42b2, 0x42b2, 0x42b2, 0x4b12, 0x4b12, 0x4b12, 0x4b12, 0x41b2, + 0x41b2, 0x41b2, 0x41b2, 0x5b01, 0x5b01, 0x50b1, 0x50b1, 0x5692, 0x5692, 0x5a42, 0x5a42, 0x5a32, + 0x5a32, 0x53a2, 0x53a2, 0x5952, 0x5952, 0x5592, 0x5592, 0x4a22, 0x4a22, 0x4a22, 0x4a22, 0x42a2, + 0x42a2, 0x42a2, 0x42a2, 0xf005, 0x4a12, 0x4a12, 0x41a2, 0x41a2, 0x5a01, 0x5862, 0x40a1, 0x40a1, + 0x5682, 0x5942, 0x4392, 0x4392, 0x5932, 0x5852, 0x5582, 0x5762, 0x4922, 0x4922, 0x4292, 0x4292, + 0x5752, 0x5572, 0x4832, 0x4832, 0x4382, 0x4382, 0x5662, 0x5742, 0x5472, 0x5652, 0x5562, 0x5372, + 0xf005, 0x3912, 0x3912, 0x3912, 0x3912, 0x3192, 0x3192, 0x3192, 0x3192, 0x4901, 0x4901, 0x4091, + 0x4091, 0x4842, 0x4842, 0x4482, 0x4482, 0x4272, 0x4272, 0x5642, 0x5462, 0x3822, 0x3822, 0x3822, + 0x3822, 0x3282, 0x3282, 0x3282, 0x3282, 0x3812, 0x3812, 0x3812, 0x3812, 0xf004, 0x4732, 0x4722, + 0x3712, 0x3712, 0x3172, 0x3172, 0x4552, 0x4701, 0x4071, 0x4632, 0x4362, 0x4542, 0x4452, 0x4622, + 0x4262, 0x4532, 0xf003, 0x2182, 0x2182, 0x3801, 0x3081, 0x3612, 0x3162, 0x3601, 0x3061, 0xf004, + 0x4352, 0x4442, 0x3522, 0x3522, 0x3252, 0x3252, 0x3501, 0x3501, 0x2512, 0x2512, 0x2512, 0x2512, + 0x2152, 0x2152, 0x2152, 0x2152, 0xf003, 0x3432, 0x3342, 0x3051, 0x3422, 0x3242, 0x3332, 0x2412, + 0x2412, 0xf002, 0x1142, 0x1142, 0x2401, 0x2041, 0xf002, 0x2322, 0x2232, 0x1312, 0x1312, 0xf001, + 0x1132, 0x1301, 0xf001, 0x1031, 0x1222, 0xf003, 0x0082, 0x008b, 0x008e, 0x0091, 0x0094, 0x0097, + 0x3ce2, 0x3dd2, 0xf003, 0x0093, 0x3eb2, 0x3be2, 0x3f92, 0x39f2, 0x3ae2, 0x3db2, 0x3bd2, 0xf003, + 0x3f82, 0x38f2, 0x3cc2, 0x008d, 0x3e82, 0x0090, 0x27f2, 0x27f2, 0xf003, 0x2ad2, 0x2ad2, 0x3da2, + 0x3cb2, 0x3bc2, 0x36f2, 0x2f62, 0x2f62, 0xf002, 0x28e2, 0x2f52, 0x2d92, 0x29d2, 0xf002, 0x25f2, + 0x27e2, 0x2ca2, 0x2bb2, 0xf003, 0x2f42, 0x2f42, 0x24f2, 0x24f2, 0x3ac2, 0x36e2, 0x23f2, 0x23f2, + 0xf002, 0x1f32, 0x1f32, 0x2d82, 0x28d2, 0xf001, 0x1f22, 0x12f2, 0xf002, 0x2e62, 0x2c92, 0x1f01, + 0x1f01, 0xf002, 0x29c2, 0x2e52, 0x1ba2, 0x1ba2, 0xf002, 0x2d72, 0x27d2, 0x1e42, 0x1e42, 0xf002, + 0x28c2, 0x26d2, 0x1e32, 0x1e32, 0xf002, 0x19b2, 0x19b2, 0x2b92, 0x2aa2, 0xf001, 0x1ab2, 0x15e2, + 0xf001, 0x14e2, 0x1c82, 0xf001, 0x1d62, 0x13e2, 0xf001, 0x1e22, 0x1e01, 0xf001, 0x10e1, 0x1d52, + 0xf001, 0x15d2, 0x1c72, 0xf001, 0x17c2, 0x1d42, 0xf001, 0x1b82, 0x18b2, 0xf001, 0x14d2, 0x1a92, + 0xf001, 0x19a2, 0x1c62, 0xf001, 0x13d2, 0x1b72, 0xf001, 0x1c52, 0x15c2, 0xf001, 0x1992, 0x1a72, + 0xf001, 0x17a2, 0x1792, 0xf003, 0x0023, 0x3df2, 0x2de2, 0x2de2, 0x1ff2, 0x1ff2, 0x1ff2, 0x1ff2, + 0xf001, 0x1fe2, 0x1fd2, 0xf001, 0x1ee2, 0x1fc2, 0xf001, 0x1ed2, 0x1fb2, 0xf001, 0x1bf2, 0x1ec2, + 0xf002, 0x1cd2, 0x1cd2, 0x2fa2, 0x29e2, 0xf001, 0x1af2, 0x1dc2, 0xf001, 0x1ea2, 0x1e92, 0xf001, + 0x1f72, 0x1e72, 0xf001, 0x1ef2, 0x1cf2, + /* huffTable15[580] */ + 0xf008, 0x0101, 0x0122, 0x0143, 0x0154, 0x0165, 0x0176, 0x017f, 0x0188, 0x0199, 0x01a2, 0x01ab, + 0x01b4, 0x01bd, 0x01c2, 0x01cb, 0x01d4, 0x01d9, 0x01de, 0x01e3, 0x01e8, 0x01ed, 0x01f2, 0x01f7, + 0x01fc, 0x0201, 0x0204, 0x0207, 0x020a, 0x020f, 0x0212, 0x0215, 0x021a, 0x021d, 0x0220, 0x8192, + 0x0223, 0x0226, 0x0229, 0x022c, 0x022f, 0x8822, 0x8282, 0x8812, 0x8182, 0x0232, 0x0235, 0x0238, + 0x023b, 0x8722, 0x8272, 0x8462, 0x8712, 0x8552, 0x8172, 0x023e, 0x8632, 0x8362, 0x8542, 0x8452, + 0x8622, 0x8262, 0x8612, 0x0241, 0x8532, 0x7162, 0x7162, 0x8352, 0x8442, 0x7522, 0x7522, 0x7252, + 0x7252, 0x7512, 0x7512, 0x7152, 0x7152, 0x8501, 0x8051, 0x7432, 0x7432, 0x7342, 0x7342, 0x7422, + 0x7422, 0x7242, 0x7242, 0x7332, 0x7332, 0x6142, 0x6142, 0x6142, 0x6142, 0x7412, 0x7412, 0x7401, + 0x7401, 0x6322, 0x6322, 0x6322, 0x6322, 0x6232, 0x6232, 0x6232, 0x6232, 0x7041, 0x7041, 0x7301, + 0x7301, 0x6312, 0x6312, 0x6312, 0x6312, 0x6132, 0x6132, 0x6132, 0x6132, 0x6031, 0x6031, 0x6031, + 0x6031, 0x5222, 0x5222, 0x5222, 0x5222, 0x5222, 0x5222, 0x5222, 0x5222, 0x5212, 0x5212, 0x5212, + 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, + 0x5122, 0x5201, 0x5201, 0x5201, 0x5201, 0x5201, 0x5201, 0x5201, 0x5201, 0x5021, 0x5021, 0x5021, + 0x5021, 0x5021, 0x5021, 0x5021, 0x5021, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, + 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, + 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, + 0x3112, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, + 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, + 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x3000, 0x3000, 0x3000, + 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, + 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, + 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0xf005, 0x5ff2, 0x5fe2, 0x5ef2, 0x5fd2, 0x4ee2, 0x4ee2, + 0x5df2, 0x5fc2, 0x5cf2, 0x5ed2, 0x5de2, 0x5fb2, 0x4bf2, 0x4bf2, 0x5ec2, 0x5ce2, 0x4dd2, 0x4dd2, + 0x4fa2, 0x4fa2, 0x4af2, 0x4af2, 0x4eb2, 0x4eb2, 0x4be2, 0x4be2, 0x4dc2, 0x4dc2, 0x4cd2, 0x4cd2, + 0x4f92, 0x4f92, 0xf005, 0x49f2, 0x49f2, 0x4ae2, 0x4ae2, 0x4db2, 0x4db2, 0x4bd2, 0x4bd2, 0x4f82, + 0x4f82, 0x48f2, 0x48f2, 0x4cc2, 0x4cc2, 0x4e92, 0x4e92, 0x49e2, 0x49e2, 0x4f72, 0x4f72, 0x47f2, + 0x47f2, 0x4da2, 0x4da2, 0x4ad2, 0x4ad2, 0x4cb2, 0x4cb2, 0x4f62, 0x4f62, 0x5ea2, 0x5f01, 0xf004, + 0x3bc2, 0x3bc2, 0x36f2, 0x36f2, 0x4e82, 0x48e2, 0x4f52, 0x4d92, 0x35f2, 0x35f2, 0x3e72, 0x3e72, + 0x37e2, 0x37e2, 0x3ca2, 0x3ca2, 0xf004, 0x3ac2, 0x3ac2, 0x3bb2, 0x3bb2, 0x49d2, 0x4d82, 0x3f42, + 0x3f42, 0x34f2, 0x34f2, 0x3f32, 0x3f32, 0x33f2, 0x33f2, 0x38d2, 0x38d2, 0xf004, 0x36e2, 0x36e2, + 0x3f22, 0x3f22, 0x32f2, 0x32f2, 0x4e62, 0x40f1, 0x3f12, 0x3f12, 0x31f2, 0x31f2, 0x3c92, 0x3c92, + 0x39c2, 0x39c2, 0xf003, 0x3e52, 0x3ba2, 0x3ab2, 0x35e2, 0x3d72, 0x37d2, 0x3e42, 0x34e2, 0xf003, + 0x3c82, 0x38c2, 0x3e32, 0x3d62, 0x36d2, 0x33e2, 0x3b92, 0x39b2, 0xf004, 0x3e22, 0x3e22, 0x3aa2, + 0x3aa2, 0x32e2, 0x32e2, 0x3e12, 0x3e12, 0x31e2, 0x31e2, 0x4e01, 0x40e1, 0x3d52, 0x3d52, 0x35d2, + 0x35d2, 0xf003, 0x3c72, 0x37c2, 0x3d42, 0x3b82, 0x24d2, 0x24d2, 0x38b2, 0x3a92, 0xf003, 0x39a2, + 0x3c62, 0x36c2, 0x3d32, 0x23d2, 0x23d2, 0x22d2, 0x22d2, 0xf003, 0x3d22, 0x3d01, 0x2d12, 0x2d12, + 0x2b72, 0x2b72, 0x27b2, 0x27b2, 0xf003, 0x21d2, 0x21d2, 0x3c52, 0x30d1, 0x25c2, 0x25c2, 0x2a82, + 0x2a82, 0xf002, 0x28a2, 0x2c42, 0x24c2, 0x2b62, 0xf003, 0x26b2, 0x26b2, 0x3992, 0x3c01, 0x2c32, + 0x2c32, 0x23c2, 0x23c2, 0xf003, 0x2a72, 0x2a72, 0x27a2, 0x27a2, 0x26a2, 0x26a2, 0x30c1, 0x3b01, + 0xf002, 0x12c2, 0x12c2, 0x2c22, 0x2b52, 0xf002, 0x25b2, 0x2c12, 0x2982, 0x2892, 0xf002, 0x21c2, + 0x2b42, 0x24b2, 0x2a62, 0xf002, 0x2b32, 0x2972, 0x13b2, 0x13b2, 0xf002, 0x2792, 0x2882, 0x2b22, + 0x2a52, 0xf002, 0x12b2, 0x12b2, 0x25a2, 0x2b12, 0xf002, 0x11b2, 0x11b2, 0x20b1, 0x2962, 0xf002, + 0x2692, 0x2a42, 0x24a2, 0x2872, 0xf002, 0x2782, 0x2a32, 0x13a2, 0x13a2, 0xf001, 0x1952, 0x1592, + 0xf001, 0x1a22, 0x12a2, 0xf001, 0x1a12, 0x11a2, 0xf002, 0x2a01, 0x20a1, 0x1862, 0x1862, 0xf001, + 0x1682, 0x1942, 0xf001, 0x1492, 0x1932, 0xf002, 0x1392, 0x1392, 0x2772, 0x2901, 0xf001, 0x1852, + 0x1582, 0xf001, 0x1922, 0x1762, 0xf001, 0x1672, 0x1292, 0xf001, 0x1912, 0x1091, 0xf001, 0x1842, + 0x1482, 0xf001, 0x1752, 0x1572, 0xf001, 0x1832, 0x1382, 0xf001, 0x1662, 0x1742, 0xf001, 0x1472, + 0x1801, 0xf001, 0x1081, 0x1652, 0xf001, 0x1562, 0x1732, 0xf001, 0x1372, 0x1642, 0xf001, 0x1701, + 0x1071, 0xf001, 0x1601, 0x1061, + /* huffTable16[651] */ + 0xf008, 0x0101, 0x010a, 0x0113, 0x8ff2, 0x0118, 0x011d, 0x0120, 0x82f2, 0x0131, 0x8f12, 0x81f2, + 0x0134, 0x0145, 0x0156, 0x0167, 0x0178, 0x0189, 0x019a, 0x01a3, 0x01ac, 0x01b5, 0x01be, 0x01c7, + 0x01d0, 0x01d9, 0x01de, 0x01e3, 0x01e6, 0x01eb, 0x01f0, 0x8152, 0x01f3, 0x01f6, 0x01f9, 0x01fc, + 0x8412, 0x8142, 0x01ff, 0x8322, 0x8232, 0x7312, 0x7312, 0x7132, 0x7132, 0x8301, 0x8031, 0x7222, + 0x7222, 0x6212, 0x6212, 0x6212, 0x6212, 0x6122, 0x6122, 0x6122, 0x6122, 0x6201, 0x6201, 0x6201, + 0x6201, 0x6021, 0x6021, 0x6021, 0x6021, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, + 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4101, 0x4101, 0x4101, + 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, + 0x4101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0xf003, 0x3fe2, 0x3ef2, 0x3fd2, 0x3df2, 0x3fc2, 0x3cf2, + 0x3fb2, 0x3bf2, 0xf003, 0x2fa2, 0x2fa2, 0x3af2, 0x3f92, 0x39f2, 0x38f2, 0x2f82, 0x2f82, 0xf002, + 0x2f72, 0x27f2, 0x2f62, 0x26f2, 0xf002, 0x2f52, 0x25f2, 0x1f42, 0x1f42, 0xf001, 0x14f2, 0x13f2, + 0xf004, 0x10f1, 0x10f1, 0x10f1, 0x10f1, 0x10f1, 0x10f1, 0x10f1, 0x10f1, 0x2f32, 0x2f32, 0x2f32, + 0x2f32, 0x00e2, 0x00f3, 0x00fc, 0x0105, 0xf001, 0x1f22, 0x1f01, 0xf004, 0x00fa, 0x00ff, 0x0104, + 0x0109, 0x010c, 0x0111, 0x0116, 0x0119, 0x011e, 0x0123, 0x0128, 0x43e2, 0x012d, 0x0130, 0x0133, + 0x0136, 0xf004, 0x0128, 0x012b, 0x012e, 0x4d01, 0x0131, 0x0134, 0x0137, 0x4c32, 0x013a, 0x4c12, + 0x40c1, 0x013d, 0x32e2, 0x32e2, 0x4e22, 0x4e12, 0xf004, 0x43d2, 0x4d22, 0x42d2, 0x41d2, 0x4b32, + 0x012f, 0x3d12, 0x3d12, 0x44c2, 0x4b62, 0x43c2, 0x47a2, 0x3c22, 0x3c22, 0x42c2, 0x45b2, 0xf004, + 0x41c2, 0x4c01, 0x4b42, 0x44b2, 0x4a62, 0x46a2, 0x33b2, 0x33b2, 0x4a52, 0x45a2, 0x3b22, 0x3b22, + 0x32b2, 0x32b2, 0x3b12, 0x3b12, 0xf004, 0x31b2, 0x31b2, 0x4b01, 0x40b1, 0x4962, 0x4692, 0x4a42, + 0x44a2, 0x4872, 0x4782, 0x33a2, 0x33a2, 0x4a32, 0x4952, 0x3a22, 0x3a22, 0xf004, 0x4592, 0x4862, + 0x31a2, 0x31a2, 0x4682, 0x4772, 0x3492, 0x3492, 0x4942, 0x4752, 0x3762, 0x3762, 0x22a2, 0x22a2, + 0x22a2, 0x22a2, 0xf003, 0x2a12, 0x2a12, 0x3a01, 0x30a1, 0x3932, 0x3392, 0x3852, 0x3582, 0xf003, + 0x2922, 0x2922, 0x2292, 0x2292, 0x3672, 0x3901, 0x2912, 0x2912, 0xf003, 0x2192, 0x2192, 0x3091, + 0x3842, 0x3482, 0x3572, 0x3832, 0x3382, 0xf003, 0x3662, 0x3822, 0x2282, 0x2282, 0x3742, 0x3472, + 0x2812, 0x2812, 0xf003, 0x2182, 0x2182, 0x2081, 0x2081, 0x3801, 0x3652, 0x2732, 0x2732, 0xf003, + 0x2372, 0x2372, 0x3562, 0x3642, 0x2722, 0x2722, 0x2272, 0x2272, 0xf003, 0x3462, 0x3552, 0x2701, + 0x2701, 0x1712, 0x1712, 0x1712, 0x1712, 0xf002, 0x1172, 0x1172, 0x2071, 0x2632, 0xf002, 0x2362, + 0x2542, 0x2452, 0x2622, 0xf001, 0x1262, 0x1612, 0xf002, 0x1162, 0x1162, 0x2601, 0x2061, 0xf002, + 0x1352, 0x1352, 0x2532, 0x2442, 0xf001, 0x1522, 0x1252, 0xf001, 0x1512, 0x1501, 0xf001, 0x1432, + 0x1342, 0xf001, 0x1051, 0x1422, 0xf001, 0x1242, 0x1332, 0xf001, 0x1401, 0x1041, 0xf004, 0x4ec2, + 0x0086, 0x3ed2, 0x3ed2, 0x39e2, 0x39e2, 0x4ae2, 0x49d2, 0x2ee2, 0x2ee2, 0x2ee2, 0x2ee2, 0x3de2, + 0x3de2, 0x3be2, 0x3be2, 0xf003, 0x2eb2, 0x2eb2, 0x2dc2, 0x2dc2, 0x3cd2, 0x3bd2, 0x2ea2, 0x2ea2, + 0xf003, 0x2cc2, 0x2cc2, 0x3da2, 0x3ad2, 0x3e72, 0x3ca2, 0x2ac2, 0x2ac2, 0xf003, 0x39c2, 0x3d72, + 0x2e52, 0x2e52, 0x1db2, 0x1db2, 0x1db2, 0x1db2, 0xf002, 0x1e92, 0x1e92, 0x2cb2, 0x2bc2, 0xf002, + 0x2e82, 0x28e2, 0x2d92, 0x27e2, 0xf002, 0x2bb2, 0x2d82, 0x28d2, 0x2e62, 0xf001, 0x16e2, 0x1c92, + 0xf002, 0x2ba2, 0x2ab2, 0x25e2, 0x27d2, 0xf002, 0x1e42, 0x1e42, 0x24e2, 0x2c82, 0xf001, 0x18c2, + 0x1e32, 0xf002, 0x1d62, 0x1d62, 0x26d2, 0x2b92, 0xf002, 0x29b2, 0x2aa2, 0x11e2, 0x11e2, 0xf002, + 0x14d2, 0x14d2, 0x28b2, 0x29a2, 0xf002, 0x1b72, 0x1b72, 0x27b2, 0x20d1, 0xf001, 0x1e01, 0x10e1, + 0xf001, 0x1d52, 0x15d2, 0xf001, 0x1c72, 0x17c2, 0xf001, 0x1d42, 0x1b82, 0xf001, 0x1a92, 0x1c62, + 0xf001, 0x16c2, 0x1d32, 0xf001, 0x1c52, 0x15c2, 0xf001, 0x1a82, 0x18a2, 0xf001, 0x1992, 0x1c42, + 0xf001, 0x16b2, 0x1a72, 0xf001, 0x1b52, 0x1982, 0xf001, 0x1892, 0x1972, 0xf001, 0x1792, 0x1882, + 0xf001, 0x1ce2, 0x1dd2, + /* huffTable24[705] */ + 0xf009, 0x8fe2, 0x8fe2, 0x8ef2, 0x8ef2, 0x8fd2, 0x8fd2, 0x8df2, 0x8df2, 0x8fc2, 0x8fc2, 0x8cf2, + 0x8cf2, 0x8fb2, 0x8fb2, 0x8bf2, 0x8bf2, 0x7af2, 0x7af2, 0x7af2, 0x7af2, 0x8fa2, 0x8fa2, 0x8f92, + 0x8f92, 0x79f2, 0x79f2, 0x79f2, 0x79f2, 0x78f2, 0x78f2, 0x78f2, 0x78f2, 0x8f82, 0x8f82, 0x8f72, + 0x8f72, 0x77f2, 0x77f2, 0x77f2, 0x77f2, 0x7f62, 0x7f62, 0x7f62, 0x7f62, 0x76f2, 0x76f2, 0x76f2, + 0x76f2, 0x7f52, 0x7f52, 0x7f52, 0x7f52, 0x75f2, 0x75f2, 0x75f2, 0x75f2, 0x7f42, 0x7f42, 0x7f42, + 0x7f42, 0x74f2, 0x74f2, 0x74f2, 0x74f2, 0x7f32, 0x7f32, 0x7f32, 0x7f32, 0x73f2, 0x73f2, 0x73f2, + 0x73f2, 0x7f22, 0x7f22, 0x7f22, 0x7f22, 0x72f2, 0x72f2, 0x72f2, 0x72f2, 0x71f2, 0x71f2, 0x71f2, + 0x71f2, 0x8f12, 0x8f12, 0x80f1, 0x80f1, 0x9f01, 0x0201, 0x0206, 0x020b, 0x0210, 0x0215, 0x021a, + 0x021f, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, + 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, + 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x0224, 0x0229, 0x0232, + 0x0237, 0x023a, 0x023f, 0x0242, 0x0245, 0x024a, 0x024d, 0x0250, 0x0253, 0x0256, 0x0259, 0x025c, + 0x025f, 0x0262, 0x0265, 0x0268, 0x026b, 0x026e, 0x0271, 0x0274, 0x0277, 0x027a, 0x027d, 0x0280, + 0x0283, 0x0288, 0x028b, 0x028e, 0x0291, 0x0294, 0x0297, 0x029a, 0x029f, 0x94b2, 0x02a4, 0x02a7, + 0x02aa, 0x93b2, 0x9882, 0x02af, 0x92b2, 0x02b2, 0x02b5, 0x9692, 0x94a2, 0x02b8, 0x9782, 0x9a32, + 0x93a2, 0x9952, 0x9592, 0x9a22, 0x92a2, 0x91a2, 0x9862, 0x9682, 0x9772, 0x9942, 0x9492, 0x9932, + 0x9392, 0x9852, 0x9582, 0x9922, 0x9762, 0x9672, 0x9292, 0x9912, 0x9192, 0x9842, 0x9482, 0x9752, + 0x9572, 0x9832, 0x9382, 0x9662, 0x9822, 0x9282, 0x9812, 0x9742, 0x9472, 0x9182, 0x02bb, 0x9652, + 0x9562, 0x9712, 0x02be, 0x8372, 0x8372, 0x9732, 0x9722, 0x8272, 0x8272, 0x8642, 0x8642, 0x8462, + 0x8462, 0x8552, 0x8552, 0x8172, 0x8172, 0x8632, 0x8632, 0x8362, 0x8362, 0x8542, 0x8542, 0x8452, + 0x8452, 0x8622, 0x8622, 0x8262, 0x8262, 0x8612, 0x8612, 0x8162, 0x8162, 0x9601, 0x9061, 0x8532, + 0x8532, 0x8352, 0x8352, 0x8442, 0x8442, 0x8522, 0x8522, 0x8252, 0x8252, 0x8512, 0x8512, 0x9501, + 0x9051, 0x7152, 0x7152, 0x7152, 0x7152, 0x8432, 0x8432, 0x8342, 0x8342, 0x7422, 0x7422, 0x7422, + 0x7422, 0x7242, 0x7242, 0x7242, 0x7242, 0x7332, 0x7332, 0x7332, 0x7332, 0x7412, 0x7412, 0x7412, + 0x7412, 0x7142, 0x7142, 0x7142, 0x7142, 0x8401, 0x8401, 0x8041, 0x8041, 0x7322, 0x7322, 0x7322, + 0x7322, 0x7232, 0x7232, 0x7232, 0x7232, 0x6312, 0x6312, 0x6312, 0x6312, 0x6312, 0x6312, 0x6312, + 0x6312, 0x6132, 0x6132, 0x6132, 0x6132, 0x6132, 0x6132, 0x6132, 0x6132, 0x7301, 0x7301, 0x7301, + 0x7301, 0x7031, 0x7031, 0x7031, 0x7031, 0x6222, 0x6222, 0x6222, 0x6222, 0x6222, 0x6222, 0x6222, + 0x6222, 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, + 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, + 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x6201, 0x6201, 0x6201, + 0x6201, 0x6201, 0x6201, 0x6201, 0x6201, 0x6021, 0x6021, 0x6021, 0x6021, 0x6021, 0x6021, 0x6021, + 0x6021, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, + 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, + 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4101, 0x4101, 0x4101, + 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, + 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, + 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, + 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, + 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, + 0x4011, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, + 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, + 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0xf002, 0x2ee2, 0x2ed2, + 0x2de2, 0x2ec2, 0xf002, 0x2ce2, 0x2dd2, 0x2eb2, 0x2be2, 0xf002, 0x2dc2, 0x2cd2, 0x2ea2, 0x2ae2, + 0xf002, 0x2db2, 0x2bd2, 0x2cc2, 0x2e92, 0xf002, 0x29e2, 0x2da2, 0x2ad2, 0x2cb2, 0xf002, 0x2bc2, + 0x2e82, 0x28e2, 0x2d92, 0xf002, 0x29d2, 0x2e72, 0x27e2, 0x2ca2, 0xf002, 0x2ac2, 0x2bb2, 0x2d82, + 0x28d2, 0xf003, 0x3e01, 0x30e1, 0x2d01, 0x2d01, 0x16e2, 0x16e2, 0x16e2, 0x16e2, 0xf002, 0x2e62, + 0x2c92, 0x19c2, 0x19c2, 0xf001, 0x1e52, 0x1ab2, 0xf002, 0x15e2, 0x15e2, 0x2ba2, 0x2d72, 0xf001, + 0x17d2, 0x14e2, 0xf001, 0x1c82, 0x18c2, 0xf002, 0x2e42, 0x2e22, 0x1e32, 0x1e32, 0xf001, 0x1d62, + 0x16d2, 0xf001, 0x13e2, 0x1b92, 0xf001, 0x19b2, 0x1aa2, 0xf001, 0x12e2, 0x1e12, 0xf001, 0x11e2, + 0x1d52, 0xf001, 0x15d2, 0x1c72, 0xf001, 0x17c2, 0x1d42, 0xf001, 0x1b82, 0x18b2, 0xf001, 0x14d2, + 0x1a92, 0xf001, 0x19a2, 0x1c62, 0xf001, 0x16c2, 0x1d32, 0xf001, 0x13d2, 0x1d22, 0xf001, 0x12d2, + 0x1d12, 0xf001, 0x1b72, 0x17b2, 0xf001, 0x11d2, 0x1c52, 0xf001, 0x15c2, 0x1a82, 0xf001, 0x18a2, + 0x1992, 0xf001, 0x1c42, 0x14c2, 0xf001, 0x1b62, 0x16b2, 0xf002, 0x20d1, 0x2c01, 0x1c32, 0x1c32, + 0xf001, 0x13c2, 0x1a72, 0xf001, 0x17a2, 0x1c22, 0xf001, 0x12c2, 0x1b52, 0xf001, 0x15b2, 0x1c12, + 0xf001, 0x1982, 0x1892, 0xf001, 0x11c2, 0x1b42, 0xf002, 0x20c1, 0x2b01, 0x1b32, 0x1b32, 0xf002, + 0x20b1, 0x2a01, 0x1a12, 0x1a12, 0xf001, 0x1a62, 0x16a2, 0xf001, 0x1972, 0x1792, 0xf002, 0x20a1, + 0x2901, 0x1091, 0x1091, 0xf001, 0x1b22, 0x1a52, 0xf001, 0x15a2, 0x1b12, 0xf001, 0x11b2, 0x1962, + 0xf001, 0x1a42, 0x1872, 0xf001, 0x1801, 0x1081, 0xf001, 0x1701, 0x1071, +}; +/* pow(2,-i/4) * pow(j,4/3) for i=0..3 j=0..15, Q25 format */ +const int pow43_14[4][16] PROGMEM = { /* Q28 */ +{ 0x00000000, 0x10000000, 0x285145f3, 0x453a5cdb, 0x0cb2ff53, 0x111989d6, + 0x15ce31c8, 0x1ac7f203, 0x20000000, 0x257106b9, 0x2b16b4a3, 0x30ed74b4, + 0x36f23fa5, 0x3d227bd3, 0x437be656, 0x49fc823c, }, + +{ 0x00000000, 0x0d744fcd, 0x21e71f26, 0x3a36abd9, 0x0aadc084, 0x0e610e6e, + 0x12560c1d, 0x168523cf, 0x1ae89f99, 0x1f7c03a4, 0x243bae49, 0x29249c67, + 0x2e34420f, 0x33686f85, 0x38bf3dff, 0x3e370182, }, + +{ 0x00000000, 0x0b504f33, 0x1c823e07, 0x30f39a55, 0x08facd62, 0x0c176319, + 0x0f6b3522, 0x12efe2ad, 0x16a09e66, 0x1a79a317, 0x1e77e301, 0x2298d5b4, + 0x26da56fc, 0x2b3a902a, 0x2fb7e7e7, 0x3450f650, }, + +{ 0x00000000, 0x09837f05, 0x17f910d7, 0x2929c7a9, 0x078d0dfa, 0x0a2ae661, + 0x0cf73154, 0x0fec91cb, 0x1306fe0a, 0x16434a6c, 0x199ee595, 0x1d17ae3d, + 0x20abd76a, 0x2459d551, 0x28204fbb, 0x2bfe1808, }, +}; + +/* pow(j,4/3) for j=16..63, Q23 format */ +const int pow43[48] PROGMEM = { + 0x1428a2fa, 0x15db1bd6, 0x1796302c, 0x19598d85, 0x1b24e8bb, 0x1cf7fcfa, + 0x1ed28af2, 0x20b4582a, 0x229d2e6e, 0x248cdb55, 0x26832fda, 0x28800000, + 0x2a832287, 0x2c8c70a8, 0x2e9bc5d8, 0x30b0ff99, 0x32cbfd4a, 0x34eca001, + 0x3712ca62, 0x393e6088, 0x3b6f47e0, 0x3da56717, 0x3fe0a5fc, 0x4220ed72, + 0x44662758, 0x46b03e7c, 0x48ff1e87, 0x4b52b3f3, 0x4daaebfd, 0x5007b497, + 0x5268fc62, 0x54ceb29c, 0x5738c721, 0x59a72a59, 0x5c19cd35, 0x5e90a129, + 0x610b9821, 0x638aa47f, 0x660db90f, 0x6894c90b, 0x6b1fc80c, 0x6daeaa0d, + 0x70416360, 0x72d7e8b0, 0x75722ef9, 0x78102b85, 0x7ab1d3ec, 0x7d571e09, +}; + +const uint32_t polyCoef[264] PROGMEM = { + /* shuffled vs. original from 0, 1, ... 15 to 0, 15, 2, 13, ... 14, 1 */ + 0x00000000, 0x00000074, 0x00000354, 0x0000072c, 0x00001fd4, 0x00005084, 0x000066b8, 0x000249c4, + 0x00049478, 0xfffdb63c, 0x000066b8, 0xffffaf7c, 0x00001fd4, 0xfffff8d4, 0x00000354, 0xffffff8c, + 0xfffffffc, 0x00000068, 0x00000368, 0x00000644, 0x00001f40, 0x00004ad0, 0x00005d1c, 0x00022ce0, + 0x000493c0, 0xfffd9960, 0x00006f78, 0xffffa9cc, 0x0000203c, 0xfffff7e4, 0x00000340, 0xffffff84, + 0xfffffffc, 0x00000060, 0x00000378, 0x0000056c, 0x00001e80, 0x00004524, 0x000052a0, 0x00020ffc, + 0x000491a0, 0xfffd7ca0, 0x00007760, 0xffffa424, 0x00002080, 0xfffff6ec, 0x00000328, 0xffffff74, + 0xfffffffc, 0x00000054, 0x00000384, 0x00000498, 0x00001d94, 0x00003f7c, 0x00004744, 0x0001f32c, + 0x00048e18, 0xfffd6008, 0x00007e70, 0xffff9e8c, 0x0000209c, 0xfffff5ec, 0x00000310, 0xffffff68, + 0xfffffffc, 0x0000004c, 0x0000038c, 0x000003d0, 0x00001c78, 0x000039e4, 0x00003b00, 0x0001d680, + 0x00048924, 0xfffd43ac, 0x000084b0, 0xffff990c, 0x00002094, 0xfffff4e4, 0x000002f8, 0xffffff5c, + 0xfffffffc, 0x00000044, 0x00000390, 0x00000314, 0x00001b2c, 0x0000345c, 0x00002ddc, 0x0001ba04, + 0x000482d0, 0xfffd279c, 0x00008a20, 0xffff93a4, 0x0000206c, 0xfffff3d4, 0x000002dc, 0xffffff4c, + 0xfffffffc, 0x00000040, 0x00000390, 0x00000264, 0x000019b0, 0x00002ef0, 0x00001fd4, 0x00019dc8, + 0x00047b1c, 0xfffd0be8, 0x00008ecc, 0xffff8e64, 0x00002024, 0xfffff2c0, 0x000002c0, 0xffffff3c, + 0xfffffff8, 0x00000038, 0x0000038c, 0x000001bc, 0x000017fc, 0x0000299c, 0x000010e8, 0x000181d8, + 0x0004720c, 0xfffcf09c, 0x000092b4, 0xffff894c, 0x00001fc0, 0xfffff1a4, 0x000002a4, 0xffffff2c, + 0xfffffff8, 0x00000034, 0x00000380, 0x00000120, 0x00001618, 0x00002468, 0x00000118, 0x00016644, + 0x000467a4, 0xfffcd5cc, 0x000095e0, 0xffff8468, 0x00001f44, 0xfffff084, 0x00000284, 0xffffff18, + 0xfffffff8, 0x0000002c, 0x00000374, 0x00000090, 0x00001400, 0x00001f58, 0xfffff068, 0x00014b14, + 0x00045bf0, 0xfffcbb88, 0x00009858, 0xffff7fbc, 0x00001ea8, 0xffffef60, 0x00000268, 0xffffff04, + 0xfffffff8, 0x00000028, 0x0000035c, 0x00000008, 0x000011ac, 0x00001a70, 0xffffded8, 0x00013058, + 0x00044ef8, 0xfffca1d8, 0x00009a1c, 0xffff7b54, 0x00001dfc, 0xffffee3c, 0x0000024c, 0xfffffef0, + 0xfffffff4, 0x00000024, 0x00000340, 0xffffff8c, 0x00000f28, 0x000015b0, 0xffffcc70, 0x0001161c, + 0x000440bc, 0xfffc88d8, 0x00009b3c, 0xffff7734, 0x00001d38, 0xffffed18, 0x0000022c, 0xfffffedc, + 0xfffffff4, 0x00000020, 0x00000320, 0xffffff1c, 0x00000c68, 0x0000111c, 0xffffb92c, 0x0000fc6c, + 0x00043150, 0xfffc708c, 0x00009bb8, 0xffff7368, 0x00001c64, 0xffffebf4, 0x00000210, 0xfffffec4, + 0xfffffff0, 0x0000001c, 0x000002f4, 0xfffffeb4, 0x00000974, 0x00000cb8, 0xffffa518, 0x0000e350, + 0x000420b4, 0xfffc5908, 0x00009b9c, 0xffff6ff4, 0x00001b7c, 0xffffead0, 0x000001f4, 0xfffffeac, + 0xfffffff0, 0x0000001c, 0x000002c4, 0xfffffe58, 0x00000648, 0x00000884, 0xffff9038, 0x0000cad0, + 0x00040ef8, 0xfffc425c, 0x00009af0, 0xffff6ce0, 0x00001a88, 0xffffe9b0, 0x000001d4, 0xfffffe94, + 0xffffffec, 0x00000018, 0x0000028c, 0xfffffe04, 0x000002e4, 0x00000480, 0xffff7a90, 0x0000b2fc, + 0x0003fc28, 0xfffc2c90, 0x000099b8, 0xffff6a3c, 0x00001988, 0xffffe898, 0x000001bc, 0xfffffe7c, + 0x000001a0, 0x0000187c, 0x000097fc, 0x0003e84c, 0xffff6424, 0xffffff4c, 0x00000248, 0xffffffec, +}; + +/* format = Q30, range = [0.0981, 1.9976] + * + * n = 16; + * k = 0; + * for(i=0; i<5; i++, n=n/2) { + * for(p=0; pbytePtr = buf; + bsi->iCache = 0; /* 4-byte unsigned int */ + bsi->cachedBits = 0; /* i.e. zero bits in cache */ + bsi->nBytes = nBytes; +} +//---------------------------------------------------------------------------------------------------------------------- +void RefillBitstreamCache(BitStreamInfo_t *bsi) { + int nBytes = bsi->nBytes; + /* optimize for common case, independent of machine endian-ness */ + if (nBytes >= 4) { + bsi->iCache = (*bsi->bytePtr++) << 24; + bsi->iCache |= (*bsi->bytePtr++) << 16; + bsi->iCache |= (*bsi->bytePtr++) << 8; + bsi->iCache |= (*bsi->bytePtr++); + bsi->cachedBits = 32; + bsi->nBytes -= 4; + } else { + bsi->iCache = 0; + while (nBytes--) { + bsi->iCache |= (*bsi->bytePtr++); + bsi->iCache <<= 8; + } + bsi->iCache <<= ((3 - bsi->nBytes) * 8); + bsi->cachedBits = 8 * bsi->nBytes; + bsi->nBytes = 0; + } +} +//---------------------------------------------------------------------------------------------------------------------- +unsigned int GetBits(BitStreamInfo_t *bsi, int nBits) { + unsigned int data, lowBits; + + nBits &= 0x1f; /* nBits mod 32 to avoid unpredictable results like >> by negative amount */ + data = bsi->iCache >> (31 - nBits); /* unsigned >> so zero-extend */ + data >>= 1; /* do as >> 31, >> 1 so that nBits = 0 works okay (returns 0) */ + bsi->iCache <<= nBits; /* left-justify cache */ + bsi->cachedBits -= nBits; /* how many bits have we drawn from the cache so far */ + if (bsi->cachedBits < 0) {/* if we cross an int boundary, refill the cache */ + lowBits = -bsi->cachedBits; + RefillBitstreamCache(bsi); + data |= bsi->iCache >> (32 - lowBits); /* get the low-order bits */ + bsi->cachedBits -= lowBits; /* how many bits have we drawn from the cache so far */ + bsi->iCache <<= lowBits; /* left-justify cache */ + } + return data; +} +//---------------------------------------------------------------------------------------------------------------------- +int CalcBitsUsed(BitStreamInfo_t *bsi, unsigned char *startBuf, int startOffset){ + int bitsUsed; + bitsUsed = (bsi->bytePtr - startBuf) * 8; + bitsUsed -= bsi->cachedBits; + bitsUsed -= startOffset; + return bitsUsed; +} +//---------------------------------------------------------------------------------------------------------------------- +int CheckPadBit(){ + return (m_FrameHeader->paddingBit ? 1 : 0); +} +//---------------------------------------------------------------------------------------------------------------------- +int UnpackFrameHeader(unsigned char *buf){ + int verIdx; + /* validate pointers and sync word */ + if ((buf[0] & m_SYNCWORDH) != m_SYNCWORDH || (buf[1] & m_SYNCWORDL) != m_SYNCWORDL) return -1; + /* read header fields - use bitmasks instead of GetBits() for speed, since format never varies */ + verIdx = (buf[1] >> 3) & 0x03; + m_MPEGVersion = (MPEGVersion_t) (verIdx == 0 ? MPEG25 : ((verIdx & 0x01) ? MPEG1 : MPEG2)); + m_FrameHeader->layer = 4 - ((buf[1] >> 1) & 0x03); /* easy mapping of index to layer number, 4 = error */ + m_FrameHeader->crc = 1 - ((buf[1] >> 0) & 0x01); + m_FrameHeader->brIdx = (buf[2] >> 4) & 0x0f; + m_FrameHeader->srIdx = (buf[2] >> 2) & 0x03; + m_FrameHeader->paddingBit = (buf[2] >> 1) & 0x01; + m_FrameHeader->privateBit = (buf[2] >> 0) & 0x01; + m_sMode = (StereoMode_t) ((buf[3] >> 6) & 0x03); /* maps to correct enum (see definition) */ + m_FrameHeader->modeExt = (buf[3] >> 4) & 0x03; + m_FrameHeader->copyFlag = (buf[3] >> 3) & 0x01; + m_FrameHeader->origFlag = (buf[3] >> 2) & 0x01; + m_FrameHeader->emphasis = (buf[3] >> 0) & 0x03; + /* check parameters to avoid indexing tables with bad values */ + if (m_FrameHeader->srIdx == 3 || m_FrameHeader->layer == 4 || m_FrameHeader->brIdx == 15) return -1; + /* for readability (we reference sfBandTable many times in decoder) */ + m_SFBandTable = sfBandTable[m_MPEGVersion][m_FrameHeader->srIdx]; + if (m_sMode != Joint) /* just to be safe (dequant, stproc check fh->modeExt) */ + m_FrameHeader->modeExt = 0; + /* init user-accessible data */ + m_MP3DecInfo->nChans = (m_sMode == Mono ? 1 : 2); + m_MP3DecInfo->samprate = samplerateTab[m_MPEGVersion][m_FrameHeader->srIdx]; + m_MP3DecInfo->nGrans = (m_MPEGVersion == MPEG1 ? m_NGRANS_MPEG1 : m_NGRANS_MPEG2); + m_MP3DecInfo->nGranSamps = ((int) samplesPerFrameTab[m_MPEGVersion][m_FrameHeader->layer - 1])/m_MP3DecInfo->nGrans; + m_MP3DecInfo->layer = m_FrameHeader->layer; + + /* get bitrate and nSlots from table, unless brIdx == 0 (free mode) in which case caller must figure it out himself + * question - do we want to overwrite mp3DecInfo->bitrate with 0 each time if it's free mode, and + * copy the pre-calculated actual free bitrate into it in mp3dec.c (according to the spec, + * this shouldn't be necessary, since it should be either all frames free or none free) + */ + if (m_FrameHeader->brIdx) { + m_MP3DecInfo->bitrate=((int) bitrateTab[m_MPEGVersion][m_FrameHeader->layer - 1][m_FrameHeader->brIdx]) * 1000; + /* nSlots = total frame bytes (from table) - sideInfo bytes - header - CRC (if present) + pad (if present) */ + m_MP3DecInfo->nSlots= (int) slotTab[m_MPEGVersion][m_FrameHeader->srIdx][m_FrameHeader->brIdx] + - (int) sideBytesTab[m_MPEGVersion][(m_sMode == Mono ? 0 : 1)] - 4 + - (m_FrameHeader->crc ? 2 : 0) + (m_FrameHeader->paddingBit ? 1 : 0); + } + /* load crc word, if enabled, and return length of frame header (in bytes) */ + if (m_FrameHeader->crc) { + m_FrameHeader->CRCWord = ((int) buf[4] << 8 | (int) buf[5] << 0); + return 6; + } else { + m_FrameHeader->CRCWord = 0; + return 4; + } +} +//---------------------------------------------------------------------------------------------------------------------- +int UnpackSideInfo( unsigned char *buf) { + int gr, ch, bd, nBytes; + BitStreamInfo_t bitStreamInfo, *bsi; + + SideInfoSub_t *sis; + /* validate pointers and sync word */ + bsi = &bitStreamInfo; + if (m_MPEGVersion == MPEG1) { + /* MPEG 1 */ + nBytes=(m_sMode == Mono ? m_SIBYTES_MPEG1_MONO : m_SIBYTES_MPEG1_STEREO); + SetBitstreamPointer(bsi, nBytes, buf); + m_SideInfo->mainDataBegin = GetBits(bsi, 9); + m_SideInfo->privateBits= GetBits(bsi, (m_sMode == Mono ? 5 : 3)); + for (ch = 0; ch < m_MP3DecInfo->nChans; ch++) + for (bd = 0; bd < m_MAX_SCFBD; bd++) m_SideInfo->scfsi[ch][bd] = GetBits(bsi, 1); + } else { + /* MPEG 2, MPEG 2.5 */ + nBytes=(m_sMode == Mono ? m_SIBYTES_MPEG2_MONO : m_SIBYTES_MPEG2_STEREO); + SetBitstreamPointer(bsi, nBytes, buf); + m_SideInfo->mainDataBegin = GetBits(bsi, 8); + m_SideInfo->privateBits = GetBits(bsi, (m_sMode == Mono ? 1 : 2)); + } + for (gr = 0; gr < m_MP3DecInfo->nGrans; gr++) { + for (ch = 0; ch < m_MP3DecInfo->nChans; ch++) { + sis = &m_SideInfoSub[gr][ch]; /* side info subblock for this granule, channel */ + sis->part23Length = GetBits(bsi, 12); + sis->nBigvals = GetBits(bsi, 9); + sis->globalGain = GetBits(bsi, 8); + sis->sfCompress = GetBits(bsi, (m_MPEGVersion == MPEG1 ? 4 : 9)); + sis->winSwitchFlag = GetBits(bsi, 1); + if (sis->winSwitchFlag) { + /* this is a start, stop, short, or mixed block */ + sis->blockType = GetBits(bsi, 2); /* 0 = normal, 1 = start, 2 = short, 3 = stop */ + sis->mixedBlock = GetBits(bsi, 1); /* 0 = not mixed, 1 = mixed */ + sis->tableSelect[0] = GetBits(bsi, 5); + sis->tableSelect[1] = GetBits(bsi, 5); + sis->tableSelect[2] = 0; /* unused */ + sis->subBlockGain[0] = GetBits(bsi, 3); + sis->subBlockGain[1] = GetBits(bsi, 3); + sis->subBlockGain[2] = GetBits(bsi, 3); + if (sis->blockType == 0) { + /* this should not be allowed, according to spec */ + sis->nBigvals = 0; + sis->part23Length = 0; + sis->sfCompress = 0; + } else if (sis->blockType == 2 && sis->mixedBlock == 0) { + /* short block, not mixed */ + sis->region0Count = 8; + } else { + /* start, stop, or short-mixed */ + sis->region0Count = 7; + } + sis->region1Count = 20 - sis->region0Count; + } else { + /* this is a normal block */ + sis->blockType = 0; + sis->mixedBlock = 0; + sis->tableSelect[0] = GetBits(bsi, 5); + sis->tableSelect[1] = GetBits(bsi, 5); + sis->tableSelect[2] = GetBits(bsi, 5); + sis->region0Count = GetBits(bsi, 4); + sis->region1Count = GetBits(bsi, 3); + } + sis->preFlag = (m_MPEGVersion == MPEG1 ? GetBits(bsi, 1) : 0); + sis->sfactScale = GetBits(bsi, 1); + sis->count1TableSelect = GetBits(bsi, 1); + } + } + m_MP3DecInfo->mainDataBegin = m_SideInfo->mainDataBegin; /* needed by main decode loop */ + assert(nBytes == CalcBitsUsed(bsi, buf, 0) >> 3); + return nBytes; +} +/*********************************************************************************************************************** + * Function: UnpackSFMPEG1 + * + * Description: unpack MPEG 1 scalefactors from bitstream + * + * Inputs: BitStreamInfo, SideInfoSub, ScaleFactorInfoSub structs for this + * granule/channel + * vector of scfsi flags from side info, length = 4 (MAX_SCFBD) + * index of current granule + * ScaleFactorInfoSub from granule 0 (for granule 1, if scfsi[i] is set, + * then we just replicate the scale factors from granule 0 in the + * i'th set of scalefactor bands) + * + * Outputs: updated BitStreamInfo struct + * scalefactors in sfis (short and/or long arrays, as appropriate) + * + * Return: none + * + * Notes: set order of short blocks to s[band][window] instead of s[window][band] + * so that we index through consectutive memory locations when unpacking + * (make sure dequantizer follows same convention) + * Illegal Intensity Position = 7 (always) for MPEG1 scale factors + **********************************************************************************************************************/ +void UnpackSFMPEG1(BitStreamInfo_t *bsi, SideInfoSub_t *sis, + ScaleFactorInfoSub_t *sfis, int *scfsi, int gr, ScaleFactorInfoSub_t *sfisGr0){ + int sfb; + int slen0, slen1; + /* these can be 0, so make sure GetBits(bsi, 0) returns 0 (no >> 32 or anything) */ + slen0 = (int)m_SFLenTab[sis->sfCompress][0]; + slen1 = (int)m_SFLenTab[sis->sfCompress][1]; + if (sis->blockType == 2){ + /* short block, type 2 (implies winSwitchFlag == 1) */ + if (sis->mixedBlock){ + /* do long block portion */ + for(sfb = 0; sfb < 8; sfb++) + sfis->l[sfb]=(char)GetBits(bsi, slen0); + sfb=3; + } + else { + /* all short blocks */ + sfb=0; + } + for ( ; sfb < 6; sfb++){ + sfis->s[sfb][0] = (char)GetBits(bsi, slen0); + sfis->s[sfb][1] = (char)GetBits(bsi, slen0); + sfis->s[sfb][2] = (char)GetBits(bsi, slen0); + } + for ( ; sfb < 12; sfb++) { + sfis->s[sfb][0] = (char)GetBits(bsi, slen1); + sfis->s[sfb][1] = (char)GetBits(bsi, slen1); + sfis->s[sfb][2] = (char)GetBits(bsi, slen1); + } + /* last sf band not transmitted */ + sfis->s[12][0] = sfis->s[12][1] = sfis->s[12][2] = 0; + } + else{ + /* long blocks, type 0, 1, or 3 */ + if(gr == 0) { + /* first granule */ + for (sfb = 0; sfb < 11; sfb++) + sfis->l[sfb] = (char)GetBits(bsi, slen0); + for (sfb = 11; sfb < 21; sfb++) + sfis->l[sfb] = (char)GetBits(bsi, slen1); + return; + } + else{ + /* second granule + * scfsi: 0 = different scalefactors for each granule, + * 1 = copy sf's from granule 0 into granule 1 + * for block type == 2, scfsi is always 0 + */ + sfb = 0; + if(scfsi[0]) for( ; sfb < 6 ; sfb++) sfis->l[sfb] = sfisGr0->l[sfb]; + else for( ; sfb < 6 ; sfb++) sfis->l[sfb] = (char)GetBits(bsi, slen0); + if(scfsi[1]) for( ; sfb <11 ; sfb++) sfis->l[sfb] = sfisGr0->l[sfb]; + else for( ; sfb <11 ; sfb++) sfis->l[sfb] = (char)GetBits(bsi, slen0); + if(scfsi[2]) for( ; sfb <16 ; sfb++) sfis->l[sfb] = sfisGr0->l[sfb]; + else for( ; sfb <16 ; sfb++) sfis->l[sfb] = (char)GetBits(bsi, slen1); + if(scfsi[3]) for( ; sfb <21 ; sfb++) sfis->l[sfb] = sfisGr0->l[sfb]; + else for( ; sfb <21 ; sfb++) sfis->l[sfb] = (char)GetBits(bsi, slen1); + } + /* last sf band not transmitted */ + sfis->l[21] = 0; + sfis->l[22] = 0; + } +} +/*********************************************************************************************************************** + * Function: UnpackSFMPEG2 + * + * Description: unpack MPEG 2 scalefactors from bitstream + * + * Inputs: BitStreamInfo, SideInfoSub, ScaleFactorInfoSub structs for this + * granule/channel + * index of current granule and channel + * ScaleFactorInfoSub from this granule + * modeExt field from frame header, to tell whether intensity stereo is on + * ScaleFactorJS struct for storing IIP info used in Dequant() + * + * Outputs: updated BitStreamInfo struct + * scalefactors in sfis (short and/or long arrays, as appropriate) + * updated intensityScale and preFlag flags + * + * Return: none + * + * Notes: Illegal Intensity Position = (2^slen) - 1 for MPEG2 scale factors + **********************************************************************************************************************/ +void UnpackSFMPEG2(BitStreamInfo_t *bsi, SideInfoSub_t *sis, + ScaleFactorInfoSub_t *sfis, int gr, int ch, int modeExt, ScaleFactorJS_t *sfjs){ + + int i, sfb, sfcIdx, btIdx, nrIdx;// iipTest; + int slen[4], nr[4]; + int sfCompress, preFlag, intensityScale; + (void)gr; + sfCompress = sis->sfCompress; + preFlag = 0; + intensityScale = 0; + + /* stereo mode bits (1 = on): bit 1 = mid-side on/off, bit 0 = intensity on/off */ + if (! ((modeExt & 0x01) && (ch == 1)) ) { + /* in other words: if ((modeExt & 0x01) == 0 || ch == 0) */ + if (sfCompress < 400) { + /* max slen = floor[(399/16) / 5] = 4 */ + slen[0] = (sfCompress >> 4) / 5; + slen[1]= (sfCompress >> 4) % 5; + slen[2]= (sfCompress & 0x0f) >> 2; + slen[3]= (sfCompress & 0x03); + sfcIdx = 0; + } + else if(sfCompress < 500){ + /* max slen = floor[(99/4) / 5] = 4 */ + sfCompress -= 400; + slen[0] = (sfCompress >> 2) / 5; + slen[1]= (sfCompress >> 2) % 5; + slen[2]= (sfCompress & 0x03); + slen[3]= 0; + sfcIdx = 1; + } + else{ + /* max slen = floor[11/3] = 3 (sfCompress = 9 bits in MPEG2) */ + sfCompress -= 500; + slen[0] = sfCompress / 3; + slen[1] = sfCompress % 3; + slen[2] = slen[3] = 0; + if (sis->mixedBlock) { + /* adjust for long/short mix logic (see comment above in NRTab[] definition) */ + slen[2] = slen[1]; + slen[1] = slen[0]; + } + preFlag = 1; + sfcIdx = 2; + } + } + else{ + /* intensity stereo ch = 1 (right) */ + intensityScale = sfCompress & 0x01; + sfCompress >>= 1; + if (sfCompress < 180) { + /* max slen = floor[35/6] = 5 (from mod 36) */ + slen[0] = (sfCompress / 36); + slen[1] = (sfCompress % 36) / 6; + slen[2] = (sfCompress % 36) % 6; + slen[3] = 0; + sfcIdx = 3; + } + else if (sfCompress < 244){ + /* max slen = floor[63/16] = 3 */ + sfCompress -= 180; + slen[0] = (sfCompress & 0x3f) >> 4; + slen[1] = (sfCompress & 0x0f) >> 2; + slen[2] = (sfCompress & 0x03); + slen[3] = 0; + sfcIdx = 4; + } + else{ + /* max slen = floor[11/3] = 3 (max sfCompress >> 1 = 511/2 = 255) */ + sfCompress -= 244; + slen[0] = (sfCompress / 3); + slen[1] = (sfCompress % 3); + slen[2] = slen[3] = 0; + sfcIdx = 5; + } + } + /* set index based on block type: (0,1,3) --> 0, (2 non-mixed) --> 1, (2 mixed) ---> 2 */ + btIdx = 0; + if (sis->blockType == 2) + btIdx = (sis->mixedBlock ? 2 : 1); + for (i = 0; i < 4; i++) + nr[i] = (int)NRTab[sfcIdx][btIdx][i]; + + /* save intensity stereo scale factor info */ + if( (modeExt & 0x01) && (ch == 1) ) { + for (i = 0; i < 4; i++) { + sfjs->slen[i] = slen[i]; + sfjs->nr[i] = nr[i]; + } + sfjs->intensityScale = intensityScale; + } + sis->preFlag = preFlag; + + /* short blocks */ + if(sis->blockType == 2) { + if(sis->mixedBlock) { + /* do long block portion */ + //iipTest = (1 << slen[0]) - 1; + for (sfb=0; sfb < 6; sfb++) { + sfis->l[sfb] = (char)GetBits(bsi, slen[0]); + } + sfb = 3; /* start sfb for short */ + nrIdx = 1; + } + else{ + /* all short blocks, so start nr, sfb at 0 */ + sfb = 0; + nrIdx = 0; + } + + /* remaining short blocks, sfb just keeps incrementing */ + for( ; nrIdx <= 3; nrIdx++) { + //iipTest = (1 << slen[nrIdx]) - 1; + for(i=0; i < nr[nrIdx]; i++, sfb++) { + sfis->s[sfb][0] = (char)GetBits(bsi, slen[nrIdx]); + sfis->s[sfb][1] = (char)GetBits(bsi, slen[nrIdx]); + sfis->s[sfb][2] = (char)GetBits(bsi, slen[nrIdx]); + } + } + /* last sf band not transmitted */ + sfis->s[12][0] = sfis->s[12][1] = sfis->s[12][2] = 0; + } + else{ + /* long blocks */ + sfb = 0; + for (nrIdx = 0; nrIdx <= 3; nrIdx++) { + //iipTest = (1 << slen[nrIdx]) - 1; + for(i=0; i < nr[nrIdx]; i++, sfb++) { + sfis->l[sfb] = (char)GetBits(bsi, slen[nrIdx]); + } + } + /* last sf band not transmitted */ + sfis->l[21] = sfis->l[22] = 0; + + } +} +/*********************************************************************************************************************** + * Function: UnpackScaleFactors + * + * Description: parse the fields of the MP3 scale factor data section + * + * Inputs: MP3DecInfo structure filled by UnpackFrameHeader() and UnpackSideInfo() + * buffer pointing to the MP3 scale factor data + * pointer to bit offset (0-7) indicating starting bit in buf[0] + * number of bits available in data buffer + * index of current granule and channel + * + * Outputs: updated platform-specific ScaleFactorInfo struct + * updated bitOffset + * + * Return: length (in bytes) of scale factor data, -1 if null input pointers + **********************************************************************************************************************/ +int UnpackScaleFactors( unsigned char *buf, int *bitOffset, int bitsAvail, int gr, int ch){ + int bitsUsed; + unsigned char *startBuf; + BitStreamInfo_t bitStreamInfo, *bsi; + + /* init GetBits reader */ + startBuf = buf; + bsi = &bitStreamInfo; + SetBitstreamPointer(bsi, (bitsAvail + *bitOffset + 7) / 8, buf); + if (*bitOffset) + GetBits(bsi, *bitOffset); + + if (m_MPEGVersion == MPEG1) + UnpackSFMPEG1(bsi, &m_SideInfoSub[gr][ch], &m_ScaleFactorInfoSub[gr][ch], + m_SideInfo->scfsi[ch], gr, &m_ScaleFactorInfoSub[0][ch]); + else + UnpackSFMPEG2(bsi, &m_SideInfoSub[gr][ch], &m_ScaleFactorInfoSub[gr][ch], + gr, ch, m_FrameHeader->modeExt, m_ScaleFactorJS); + + m_MP3DecInfo->part23Length[gr][ch] = m_SideInfoSub[gr][ch].part23Length; + + bitsUsed = CalcBitsUsed(bsi, buf, *bitOffset); + buf += (bitsUsed + *bitOffset) >> 3; + *bitOffset = (bitsUsed + *bitOffset) & 0x07; + + return (buf - startBuf); +} +/*********************************************************************************************************************** + * M P 3 D E C + **********************************************************************************************************************/ + +/*********************************************************************************************************************** + * Function: MP3FindSyncWord + * + * Description: locate the next byte-alinged sync word in the raw mp3 stream + * + * Inputs: buffer to search for sync word + * max number of bytes to search in buffer + * + * Outputs: none + * + * Return: offset to first sync word (bytes from start of buf) + * -1 if sync not found after searching nBytes + **********************************************************************************************************************/ +int MP3FindSyncWord(unsigned char *buf, int nBytes) { + int i; + + /* find byte-aligned syncword - need 12 (MPEG 1,2) or 11 (MPEG 2.5) matching bits */ + for (i = 0; i < nBytes - 1; i++) { + if ((buf[i + 0] & m_SYNCWORDH) == m_SYNCWORDH + && (buf[i + 1] & m_SYNCWORDL) == m_SYNCWORDL) + return i; + } + + return -1; +} +/*********************************************************************************************************************** + * Function: MP3FindFreeSync + * + * Description: figure out number of bytes between adjacent sync words in "free" mode + * + * Inputs: buffer to search for next sync word + * the 4-byte frame header starting at the current sync word + * max number of bytes to search in buffer + * + * Outputs: none + * + * Return: offset to next sync word, minus any pad byte (i.e. nSlots) + * -1 if sync not found after searching nBytes + * + * Notes: this checks that the first 22 bits of the next frame header are the + * same as the current frame header, but it's still not foolproof + * (could accidentally find a sequence in the bitstream which + * appears to match but is not actually the next frame header) + * this could be made more error-resilient by checking several frames + * in a row and verifying that nSlots is the same in each case + * since free mode requires CBR (see spec) we generally only call + * this function once (first frame) then store the result (nSlots) + * and just use it from then on + **********************************************************************************************************************/ +int MP3FindFreeSync(unsigned char *buf, unsigned char firstFH[4], int nBytes){ + int offset = 0; + unsigned char *bufPtr = buf; + + /* loop until we either: + * - run out of nBytes (FindMP3SyncWord() returns -1) + * - find the next valid frame header (sync word, version, layer, CRC flag, bitrate, and sample rate + * in next header must match current header) + */ + while (1) { + offset = MP3FindSyncWord(bufPtr, nBytes); + bufPtr += offset; + if (offset < 0) { + return -1; + } else if ((bufPtr[0] == firstFH[0]) && (bufPtr[1] == firstFH[1]) + && ((bufPtr[2] & 0xfc) == (firstFH[2] & 0xfc))) { + /* want to return number of bytes per frame, + * NOT counting the padding byte, so subtract one if padFlag == 1 */ + if ((firstFH[2] >> 1) & 0x01) + bufPtr--; + return bufPtr - buf; + } + bufPtr += 3; + nBytes -= (offset + 3); + }; + + return -1; +} +/*********************************************************************************************************************** + * Function: MP3GetLastFrameInfo + * + * Description: get info about last MP3 frame decoded (number of sampled decoded, + * sample rate, bitrate, etc.) + * + * Inputs: + * + * Outputs: filled-in MP3FrameInfo struct + * + * Return: none + * + * Notes: call this right after calling MP3Decode + **********************************************************************************************************************/ +void MP3GetLastFrameInfo() { + if (m_MP3DecInfo->layer != 3){ + m_MP3FrameInfo->bitrate=0; + m_MP3FrameInfo->nChans=0; + m_MP3FrameInfo->samprate=0; + m_MP3FrameInfo->bitsPerSample=0; + m_MP3FrameInfo->outputSamps=0; + m_MP3FrameInfo->layer=0; + m_MP3FrameInfo->version=0; + } + else{ + m_MP3FrameInfo->bitrate=m_MP3DecInfo->bitrate; + m_MP3FrameInfo->nChans=m_MP3DecInfo->nChans; + m_MP3FrameInfo->samprate=m_MP3DecInfo->samprate; + m_MP3FrameInfo->bitsPerSample=16; + m_MP3FrameInfo->outputSamps=m_MP3DecInfo->nChans + * (int) samplesPerFrameTab[m_MPEGVersion][m_MP3DecInfo->layer-1]; + m_MP3FrameInfo->layer=m_MP3DecInfo->layer; + m_MP3FrameInfo->version=m_MPEGVersion; + } +} +int MP3GetSampRate(){return m_MP3FrameInfo->samprate;} +int MP3GetChannels(){return m_MP3FrameInfo->nChans;} +int MP3GetBitsPerSample(){return m_MP3FrameInfo->bitsPerSample;} +int MP3GetBitrate(){return m_MP3FrameInfo->bitrate;} +int MP3GetOutputSamps(){return m_MP3FrameInfo->outputSamps;} +/*********************************************************************************************************************** + * Function: MP3GetNextFrameInfo + * + * Description: parse MP3 frame header + * + * Inputs: pointer to buffer containing valid MP3 frame header (located using + * MP3FindSyncWord(), above) + * + * Outputs: filled-in MP3FrameInfo struct + * + * Return: error code, defined in mp3dec.h (0 means no error, < 0 means error) + **********************************************************************************************************************/ +int MP3GetNextFrameInfo(unsigned char *buf) { + + if (UnpackFrameHeader( buf) == -1 || m_MP3DecInfo->layer != 3) + return ERR_MP3_INVALID_FRAMEHEADER; + + MP3GetLastFrameInfo(); + + return ERR_MP3_NONE; +} +/*********************************************************************************************************************** + * Function: MP3ClearBadFrame + * + * Description: zero out pcm buffer if error decoding MP3 frame + * + * Inputs: mp3DecInfo struct with correct frame size parameters filled in + * pointer pcm output buffer + * + * Outputs: zeroed out pcm buffer + * + * Return: none + **********************************************************************************************************************/ +void MP3ClearBadFrame( short *outbuf) { + int i; + for (i = 0; i < m_MP3DecInfo->nGrans * m_MP3DecInfo->nGranSamps * m_MP3DecInfo->nChans; i++) + outbuf[i] = 0; +} +/*********************************************************************************************************************** + * Function: MP3Decode + * + * Description: decode one frame of MP3 data + * + * Inputs: number of valid bytes remaining in inbuf + * pointer to outbuf, big enough to hold one frame of decoded PCM samples + * flag indicating whether MP3 data is normal MPEG format (useSize = 0) + * or reformatted as "self-contained" frames (useSize = 1) + * + * Outputs: PCM data in outbuf, interleaved LRLRLR... if stereo + * number of output samples = nGrans * nGranSamps * nChans + * updated inbuf pointer, updated bytesLeft + * + * Return: error code, defined in mp3dec.h (0 means no error, < 0 means error) + * + * Notes: switching useSize on and off between frames in the same stream + * is not supported (bit reservoir is not maintained if useSize on) + **********************************************************************************************************************/ +int MP3Decode( unsigned char *inbuf, int *bytesLeft, short *outbuf, int useSize){ + int offset, bitOffset, mainBits, gr, ch, fhBytes, siBytes, freeFrameBytes; + int prevBitOffset, sfBlockBits, huffBlockBits; + unsigned char *mainPtr; + + /* unpack frame header */ + fhBytes = UnpackFrameHeader(inbuf); + if (fhBytes < 0) + return ERR_MP3_INVALID_FRAMEHEADER; /* don't clear outbuf since we don't know size (failed to parse header) */ + inbuf += fhBytes; + /* unpack side info */ + siBytes = UnpackSideInfo( inbuf); + if (siBytes < 0) { + MP3ClearBadFrame(outbuf); + return ERR_MP3_INVALID_SIDEINFO; + } + inbuf += siBytes; + *bytesLeft -= (fhBytes + siBytes); + + /* if free mode, need to calculate bitrate and nSlots manually, based on frame size */ + if (m_MP3DecInfo->bitrate == 0 || m_MP3DecInfo->freeBitrateFlag) { + if(!m_MP3DecInfo->freeBitrateFlag){ + /* first time through, need to scan for next sync word and figure out frame size */ + m_MP3DecInfo->freeBitrateFlag=1; + m_MP3DecInfo->freeBitrateSlots=MP3FindFreeSync(inbuf, inbuf - fhBytes - siBytes, *bytesLeft); + if(m_MP3DecInfo->freeBitrateSlots < 0){ + MP3ClearBadFrame(outbuf); + m_MP3DecInfo->freeBitrateFlag = 0; + return ERR_MP3_FREE_BITRATE_SYNC; + } + freeFrameBytes=m_MP3DecInfo->freeBitrateSlots + fhBytes + siBytes; + m_MP3DecInfo->bitrate=(freeFrameBytes * m_MP3DecInfo->samprate * 8) + / (m_MP3DecInfo->nGrans * m_MP3DecInfo->nGranSamps); + } + m_MP3DecInfo->nSlots = m_MP3DecInfo->freeBitrateSlots + CheckPadBit(); /* add pad byte, if required */ + } + + /* useSize != 0 means we're getting reformatted (RTP) packets (see RFC 3119) + * - calling function assembles "self-contained" MP3 frames by shifting any main_data + * from the bit reservoir (in previous frames) to AFTER the sync word and side info + * - calling function should set mainDataBegin to 0, and tell us exactly how large this + * frame is (in bytesLeft) + */ + if (useSize) { + m_MP3DecInfo->nSlots = *bytesLeft; + if (m_MP3DecInfo->mainDataBegin != 0 || m_MP3DecInfo->nSlots <= 0) { + /* error - non self-contained frame, or missing frame (size <= 0), could do loss concealment here */ + MP3ClearBadFrame(outbuf); + return ERR_MP3_INVALID_FRAMEHEADER; + } + + /* can operate in-place on reformatted frames */ + m_MP3DecInfo->mainDataBytes = m_MP3DecInfo->nSlots; + mainPtr = inbuf; + inbuf += m_MP3DecInfo->nSlots; + *bytesLeft -= (m_MP3DecInfo->nSlots); + } else { + /* out of data - assume last or truncated frame */ + if (m_MP3DecInfo->nSlots > *bytesLeft) { + MP3ClearBadFrame(outbuf); + return ERR_MP3_INDATA_UNDERFLOW; + } + /* fill main data buffer with enough new data for this frame */ + if (m_MP3DecInfo->mainDataBytes >= m_MP3DecInfo->mainDataBegin) { + /* adequate "old" main data available (i.e. bit reservoir) */ + memmove(m_MP3DecInfo->mainBuf, + m_MP3DecInfo->mainBuf + m_MP3DecInfo->mainDataBytes - m_MP3DecInfo->mainDataBegin, + m_MP3DecInfo->mainDataBegin); + memcpy (m_MP3DecInfo->mainBuf + m_MP3DecInfo->mainDataBegin, inbuf, + m_MP3DecInfo->nSlots); + + m_MP3DecInfo->mainDataBytes = m_MP3DecInfo->mainDataBegin + m_MP3DecInfo->nSlots; + inbuf += m_MP3DecInfo->nSlots; + *bytesLeft -= (m_MP3DecInfo->nSlots); + mainPtr = m_MP3DecInfo->mainBuf; + } else { + /* not enough data in bit reservoir from previous frames (perhaps starting in middle of file) */ + memcpy(m_MP3DecInfo->mainBuf + m_MP3DecInfo->mainDataBytes, inbuf, m_MP3DecInfo->nSlots); + m_MP3DecInfo->mainDataBytes += m_MP3DecInfo->nSlots; + inbuf += m_MP3DecInfo->nSlots; + *bytesLeft -= (m_MP3DecInfo->nSlots); + MP3ClearBadFrame( outbuf); + return ERR_MP3_MAINDATA_UNDERFLOW; + } + } + bitOffset = 0; + mainBits = m_MP3DecInfo->mainDataBytes * 8; + + /* decode one complete frame */ + for (gr = 0; gr < m_MP3DecInfo->nGrans; gr++) { + for (ch = 0; ch < m_MP3DecInfo->nChans; ch++) { + /* unpack scale factors and compute size of scale factor block */ + prevBitOffset = bitOffset; + offset = UnpackScaleFactors( mainPtr, &bitOffset, + mainBits, gr, ch); + sfBlockBits = 8 * offset - prevBitOffset + bitOffset; + huffBlockBits = m_MP3DecInfo->part23Length[gr][ch] - sfBlockBits; + mainPtr += offset; + mainBits -= sfBlockBits; + + if (offset < 0 || mainBits < huffBlockBits) { + MP3ClearBadFrame(outbuf); + return ERR_MP3_INVALID_SCALEFACT; + } + /* decode Huffman code words */ + prevBitOffset = bitOffset; + offset = DecodeHuffman( mainPtr, &bitOffset, huffBlockBits, gr, ch); + if (offset < 0) { + MP3ClearBadFrame( outbuf); + return ERR_MP3_INVALID_HUFFCODES; + } + mainPtr += offset; + mainBits -= (8 * offset - prevBitOffset + bitOffset); + } + /* dequantize coefficients, decode stereo, reorder short blocks */ + if (MP3Dequantize( gr) < 0) { + MP3ClearBadFrame(outbuf); + return ERR_MP3_INVALID_DEQUANTIZE; + } + + /* alias reduction, inverse MDCT, overlap-add, frequency inversion */ + for (ch = 0; ch < m_MP3DecInfo->nChans; ch++) { + if (IMDCT( gr, ch) < 0) { + MP3ClearBadFrame(outbuf); + return ERR_MP3_INVALID_IMDCT; + } + } + /* subband transform - if stereo, interleaves pcm LRLRLR */ + if (Subband( + outbuf + gr * m_MP3DecInfo->nGranSamps * m_MP3DecInfo->nChans) + < 0) { + MP3ClearBadFrame(outbuf); + return ERR_MP3_INVALID_SUBBAND; + } + } + MP3GetLastFrameInfo(); + return ERR_MP3_NONE; +} + +/*********************************************************************************************************************** + * Function: MP3Decoder_ClearBuffer + * + * Description: clear all the memory needed for the MP3 decoder + * + * Inputs: none + * + * Outputs: none + * + * Return: none + * + * Notes: if one or more mallocs fail, function frees any buffers already + * allocated before returning + **********************************************************************************************************************/ +void MP3Decoder_ClearBuffer(void) { + + /* important to do this - DSP primitives assume a bunch of state variables are 0 on first use */ + memset( m_MP3DecInfo, 0, sizeof(MP3DecInfo_t)); //Clear MP3DecInfo + memset(&m_ScaleFactorInfoSub, 0, sizeof(ScaleFactorInfoSub_t)*(m_MAX_NGRAN *m_MAX_NCHAN)); //Clear ScaleFactorInfo + memset( m_SideInfo, 0, sizeof(SideInfo_t)); //Clear SideInfo + memset( m_FrameHeader, 0, sizeof(FrameHeader_t)); //Clear FrameHeader + memset( m_HuffmanInfo, 0, sizeof(HuffmanInfo_t)); //Clear HuffmanInfo + memset( m_DequantInfo, 0, sizeof(DequantInfo_t)); //Clear DequantInfo + memset( m_IMDCTInfo, 0, sizeof(IMDCTInfo_t)); //Clear IMDCTInfo + memset( m_SubbandInfo, 0, sizeof(SubbandInfo_t)); //Clear SubbandInfo + memset(&m_CriticalBandInfo, 0, sizeof(CriticalBandInfo_t)*m_MAX_NCHAN); //Clear CriticalBandInfo + memset( m_ScaleFactorJS, 0, sizeof(ScaleFactorJS_t)); //Clear ScaleFactorJS + memset(&m_SideInfoSub, 0, sizeof(SideInfoSub_t)*(m_MAX_NGRAN *m_MAX_NCHAN)); //Clear SideInfoSub + memset(&m_SFBandTable, 0, sizeof(SFBandTable_t)); //Clear SFBandTable + memset( m_MP3FrameInfo, 0, sizeof(MP3FrameInfo_t)); //Clear MP3FrameInfo + + return; + +} +/*********************************************************************************************************************** + * Function: MP3Decoder_AllocateBuffers + * + * Description: allocate all the memory needed for the MP3 decoder + * + * Inputs: none + * + * Outputs: none + * + * Return: pointer to MP3DecInfo structure (initialized with pointers to all + * the internal buffers needed for decoding) + * + **********************************************************************************************************************/ +bool MP3Decoder_AllocateBuffers(void) { + + // try first SRAM because its faster than PSRAM + + + if(!m_MP3DecInfo) {m_MP3DecInfo = (MP3DecInfo_t*) malloc(sizeof(MP3DecInfo_t) );} + if(!m_FrameHeader) {m_FrameHeader = (FrameHeader_t*) malloc(sizeof(FrameHeader_t) );} + if(!m_SideInfo) {m_SideInfo = (SideInfo_t*) malloc(sizeof(SideInfo_t) );} + if(!m_ScaleFactorJS) {m_ScaleFactorJS = (ScaleFactorJS_t*) malloc(sizeof(ScaleFactorJS_t));} + if(!m_HuffmanInfo) {m_HuffmanInfo = (HuffmanInfo_t*) malloc(sizeof(HuffmanInfo_t) );} + if(!m_DequantInfo) {m_DequantInfo = (DequantInfo_t*) malloc(sizeof(DequantInfo_t) );} + if(!m_IMDCTInfo) {m_IMDCTInfo = (IMDCTInfo_t*) malloc(sizeof(IMDCTInfo_t) );} + if(!m_SubbandInfo) {m_SubbandInfo = (SubbandInfo_t*) malloc(sizeof(SubbandInfo_t) );} + if(!m_MP3FrameInfo) {m_MP3FrameInfo = (MP3FrameInfo_t*) malloc(sizeof(MP3FrameInfo_t) );} + + if(!m_MP3DecInfo || !m_FrameHeader || !m_SideInfo || !m_ScaleFactorJS || !m_HuffmanInfo || + !m_DequantInfo || !m_IMDCTInfo || !m_SubbandInfo || !m_MP3FrameInfo) { + log_i("heap is too small, try PSRAM"); + MP3Decoder_FreeBuffers(); + } + else{ + MP3Decoder_ClearBuffer(); + return true; // success, all buffers allocated in SRAM (Heap) + } + + if(psramFound()) { + // PSRAM found, Buffer will be allocated in PSRAM + if(!m_MP3DecInfo) {m_MP3DecInfo = (MP3DecInfo_t*) ps_calloc(sizeof(MP3DecInfo_t) , sizeof(uint8_t));} + if(!m_FrameHeader) {m_FrameHeader = (FrameHeader_t*) ps_calloc(sizeof(FrameHeader_t) , sizeof(uint8_t));} + if(!m_SideInfo) {m_SideInfo = (SideInfo_t*) ps_calloc(sizeof(SideInfo_t) , sizeof(uint8_t));} + if(!m_ScaleFactorJS) {m_ScaleFactorJS = (ScaleFactorJS_t*) ps_calloc(sizeof(ScaleFactorJS_t), sizeof(uint8_t));} + if(!m_HuffmanInfo) {m_HuffmanInfo = (HuffmanInfo_t*) ps_calloc(sizeof(HuffmanInfo_t) , sizeof(uint8_t));} + if(!m_DequantInfo) {m_DequantInfo = (DequantInfo_t*) ps_calloc(sizeof(DequantInfo_t) , sizeof(uint8_t));} + if(!m_IMDCTInfo) {m_IMDCTInfo = (IMDCTInfo_t*) ps_calloc(sizeof(IMDCTInfo_t) , sizeof(uint8_t));} + if(!m_SubbandInfo) {m_SubbandInfo = (SubbandInfo_t*) ps_calloc(sizeof(SubbandInfo_t) , sizeof(uint8_t));} + if(!m_MP3FrameInfo) {m_MP3FrameInfo = (MP3FrameInfo_t*) ps_calloc(sizeof(MP3FrameInfo_t) , sizeof(uint8_t));} + } + else { + log_e("not enough memory to allocate mp3decoder buffers"); + return false; + } + + if(!m_MP3DecInfo || !m_FrameHeader || !m_SideInfo || !m_ScaleFactorJS || !m_HuffmanInfo || + !m_DequantInfo || !m_IMDCTInfo || !m_SubbandInfo || !m_MP3FrameInfo) { + log_e("not enough memory to allocate mp3decoder buffers in PSRAM"); + return false; + } + MP3Decoder_ClearBuffer(); + return true; +} +/*********************************************************************************************************************** + * Function: MP3Decoder_FreeBuffers + * + * Description: frees all the memory used by the MP3 decoder + * + * Inputs: pointer to initialized MP3DecInfo structure + * + * Outputs: none + * + * Return: none + * + * Notes: safe to call even if some buffers were not allocated + **********************************************************************************************************************/ +void MP3Decoder_FreeBuffers() +{ +// uint32_t i = ESP.getFreeHeap(); + + if(m_MP3DecInfo) {free(m_MP3DecInfo); m_MP3DecInfo=NULL;} + if(m_FrameHeader) {free(m_FrameHeader); m_FrameHeader=NULL;} + if(m_SideInfo) {free(m_SideInfo); m_SideInfo=NULL;} + if(m_ScaleFactorJS ) {free(m_ScaleFactorJS); m_ScaleFactorJS=NULL;} + if(m_HuffmanInfo) {free(m_HuffmanInfo); m_HuffmanInfo=NULL;} + if(m_DequantInfo) {free(m_DequantInfo); m_DequantInfo=0;} + if(m_IMDCTInfo) {free(m_IMDCTInfo); m_IMDCTInfo=0;} + if(m_SubbandInfo) {free(m_SubbandInfo); m_SubbandInfo=0;} + if(m_MP3FrameInfo) {free(m_MP3FrameInfo); m_MP3FrameInfo=0;} + +// log_i("MP3Decoder: %lu bytes memory was freed", ESP.getFreeHeap() - i); +} + +/*********************************************************************************************************************** + * H U F F M A N N + **********************************************************************************************************************/ + +/*********************************************************************************************************************** + * Function: DecodeHuffmanPairs + * + * Description: decode 2-way vector Huffman codes in the "bigValues" region of spectrum + * + * Inputs: valid BitStreamInfo struct, pointing to start of pair-wise codes + * pointer to xy buffer to received decoded values + * number of codewords to decode + * index of Huffman table to use + * number of bits remaining in bitstream + * + * Outputs: pairs of decoded coefficients in vwxy + * updated BitStreamInfo struct + * + * Return: number of bits used, or -1 if out of bits + * + * Notes: assumes that nVals is an even number + * si_huff.bit tests every Huffman codeword in every table (though not + * necessarily all linBits outputs for x,y > 15) + **********************************************************************************************************************/ +// no improvement with section=data +int DecodeHuffmanPairs(int *xy, int nVals, int tabIdx, int bitsLeft, unsigned char *buf, int bitOffset){ + int i, x, y; + int cachedBits, padBits, len, startBits, linBits, maxBits, minBits; + HuffTabType_t tabType; + unsigned short cw, *tBase, *tCurr; + unsigned int cache; + + if (nVals <= 0) + return 0; + + if (bitsLeft < 0) + return -1; + startBits = bitsLeft; + + tBase = (unsigned short *) (huffTable + huffTabOffset[tabIdx]); + linBits = huffTabLookup[tabIdx].linBits; + tabType = (HuffTabType_t)huffTabLookup[tabIdx].tabType; + +// assert(!(nVals & 0x01)); +// assert(tabIdx < m_HUFF_PAIRTABS); +// assert(tabIdx >= 0); +// assert(tabType != invalidTab); + + if((nVals & 0x01)){log_i("assert(!(nVals & 0x01))"); return -1;} + if(!(tabIdx < m_HUFF_PAIRTABS)){log_i("assert(tabIdx < m_HUFF_PAIRTABS)"); return -1;} + if(!(tabIdx >= 0)){log_i("(tabIdx >= 0)"); return -1;} + if(!(tabType != invalidTab)){log_i("(tabType != invalidTab)"); return -1;} + + + /* initially fill cache with any partial byte */ + cache = 0; + cachedBits = (8 - bitOffset) & 0x07; + if (cachedBits) + cache = (unsigned int) (*buf++) << (32 - cachedBits); + bitsLeft -= cachedBits; + + if (tabType == noBits) { + /* table 0, no data, x = y = 0 */ + for (i = 0; i < nVals; i += 2) { + xy[i + 0] = 0; + xy[i + 1] = 0; + } + return 0; + } else if (tabType == oneShot) { + /* single lookup, no escapes */ + + maxBits = (int)( (((unsigned short)(pgm_read_word(&tBase[0])) >> 0) & 0x000f)); + tBase++; + padBits = 0; + while (nVals > 0) { + /* refill cache - assumes cachedBits <= 16 */ + if (bitsLeft >= 16) { + /* load 2 new bytes into left-justified cache */ + cache |= (unsigned int) (*buf++) << (24 - cachedBits); + cache |= (unsigned int) (*buf++) << (16 - cachedBits); + cachedBits += 16; + bitsLeft -= 16; + } else { + /* last time through, pad cache with zeros and drain cache */ + if (cachedBits + bitsLeft <= 0) + return -1; + if (bitsLeft > 0) + cache |= (unsigned int) (*buf++) << (24 - cachedBits); + if (bitsLeft > 8) + cache |= (unsigned int) (*buf++) << (16 - cachedBits); + cachedBits += bitsLeft; + bitsLeft = 0; + + cache &= (signed int) 0x80000000 >> (cachedBits - 1); + padBits = 11; + cachedBits += padBits; /* okay if this is > 32 (0's automatically shifted in from right) */ + } + + /* largest maxBits = 9, plus 2 for sign bits, so make sure cache has at least 11 bits */ + while (nVals > 0 && cachedBits >= 11) { + cw = pgm_read_word(&tBase[cache >> (32 - maxBits)]); + + len=(int)( (((unsigned short)(cw)) >> 12) & 0x000f); + cachedBits -= len; + cache <<= len; + + x=(int)( (((unsigned short)(cw)) >> 4) & 0x000f); + if (x) { + (x) |= ((cache) & 0x80000000); + cache <<= 1; + cachedBits--; + } + + + + y=(int)( (((unsigned short)(cw)) >> 8) & 0x000f); + if (y) { + (y) |= ((cache) & 0x80000000); + cache <<= 1; + cachedBits--; + } + + /* ran out of bits - should never have consumed padBits */ + if (cachedBits < padBits) + return -1; + + *xy++ = x; + *xy++ = y; + nVals -= 2; + } + } + bitsLeft += (cachedBits - padBits); + return (startBits - bitsLeft); + } else if (tabType == loopLinbits || tabType == loopNoLinbits) { + tCurr = tBase; + padBits = 0; + while (nVals > 0) { + /* refill cache - assumes cachedBits <= 16 */ + if (bitsLeft >= 16) { + /* load 2 new bytes into left-justified cache */ + cache |= (unsigned int) (*buf++) << (24 - cachedBits); + cache |= (unsigned int) (*buf++) << (16 - cachedBits); + cachedBits += 16; + bitsLeft -= 16; + } else { + /* last time through, pad cache with zeros and drain cache */ + if (cachedBits + bitsLeft <= 0) + return -1; + if (bitsLeft > 0) + cache |= (unsigned int) (*buf++) << (24 - cachedBits); + if (bitsLeft > 8) + cache |= (unsigned int) (*buf++) << (16 - cachedBits); + cachedBits += bitsLeft; + bitsLeft = 0; + + cache &= (signed int) 0x80000000 >> (cachedBits - 1); + padBits = 11; + cachedBits += padBits; /* okay if this is > 32 (0's automatically shifted in from right) */ + } + + /* largest maxBits = 9, plus 2 for sign bits, so make sure cache has at least 11 bits */ + while (nVals > 0 && cachedBits >= 11) { + maxBits = (int)( (((unsigned short)(pgm_read_word(&tCurr[0]))) >> 0) & 0x000f); + cw = pgm_read_word(&tCurr[(cache >> (32 - maxBits)) + 1]); + len=(int)( (((unsigned short)(cw)) >> 12) & 0x000f); + if (!len) { + cachedBits -= maxBits; + cache <<= maxBits; + tCurr += cw; + continue; + } + cachedBits -= len; + cache <<= len; + + x=(int)( (((unsigned short)(cw)) >> 4) & 0x000f); + y=(int)( (((unsigned short)(cw)) >> 8) & 0x000f); + + if (x == 15 && tabType == loopLinbits) { + minBits = linBits + 1 + (y ? 1 : 0); + if (cachedBits + bitsLeft < minBits) + return -1; + while (cachedBits < minBits) { + cache |= (unsigned int) (*buf++) << (24 - cachedBits); + cachedBits += 8; + bitsLeft -= 8; + } + if (bitsLeft < 0) { + cachedBits += bitsLeft; + bitsLeft = 0; + cache &= (signed int) 0x80000000 >> (cachedBits - 1); + } + x += (int) (cache >> (32 - linBits)); + cachedBits -= linBits; + cache <<= linBits; + } + if (x) { + (x) |= ((cache) & 0x80000000); + cache <<= 1; + cachedBits--; + } + + if (y == 15 && tabType == loopLinbits) { + minBits = linBits + 1; + if (cachedBits + bitsLeft < minBits) + return -1; + while (cachedBits < minBits) { + cache |= (unsigned int) (*buf++) << (24 - cachedBits); + cachedBits += 8; + bitsLeft -= 8; + } + if (bitsLeft < 0) { + cachedBits += bitsLeft; + bitsLeft = 0; + cache &= (signed int) 0x80000000 >> (cachedBits - 1); + } + y += (int) (cache >> (32 - linBits)); + cachedBits -= linBits; + cache <<= linBits; + } + if (y) { + (y) |= ((cache) & 0x80000000); + cache <<= 1; + cachedBits--; + } + + /* ran out of bits - should never have consumed padBits */ + if (cachedBits < padBits) + return -1; + + *xy++ = x; + *xy++ = y; + nVals -= 2; + tCurr = tBase; + } + } + bitsLeft += (cachedBits - padBits); + return (startBits - bitsLeft); + } + + /* error in bitstream - trying to access unused Huffman table */ + return -1; +} + +/*********************************************************************************************************************** + * Function: DecodeHuffmanQuads + * + * Description: decode 4-way vector Huffman codes in the "count1" region of spectrum + * + * Inputs: valid BitStreamInfo struct, pointing to start of quadword codes + * pointer to vwxy buffer to received decoded values + * maximum number of codewords to decode + * index of quadword table (0 = table A, 1 = table B) + * number of bits remaining in bitstream + * + * Outputs: quadruples of decoded coefficients in vwxy + * updated BitStreamInfo struct + * + * Return: index of the first "zero_part" value (index of the first sample + * of the quad word after which all samples are 0) + * + * Notes: si_huff.bit tests every vwxy output in both quad tables + **********************************************************************************************************************/ +// no improvement with section=data +int DecodeHuffmanQuads(int *vwxy, int nVals, int tabIdx, int bitsLeft, unsigned char *buf, int bitOffset){ + int i, v, w, x, y; + int len, maxBits, cachedBits, padBits; + unsigned int cache; + unsigned char cw, *tBase; + + if(bitsLeft<=0) return 0; + + tBase = (unsigned char *) quadTable + quadTabOffset[tabIdx]; + maxBits = quadTabMaxBits[tabIdx]; + + /* initially fill cache with any partial byte */ + cache = 0; + cachedBits=(8-bitOffset) & 0x07; + if(cachedBits)cache=(unsigned int)(*buf++) << (32 - cachedBits); + bitsLeft -= cachedBits; + + i = padBits = 0; + while (i < (nVals - 3)) { + /* refill cache - assumes cachedBits <= 16 */ + if (bitsLeft >= 16) { + /* load 2 new bytes into left-justified cache */ + cache |= (unsigned int) (*buf++) << (24 - cachedBits); + cache |= (unsigned int) (*buf++) << (16 - cachedBits); + cachedBits += 16; + bitsLeft -= 16; + } else { + /* last time through, pad cache with zeros and drain cache */ + if(cachedBits+bitsLeft <= 0) return i; + if(bitsLeft>0) cache |= (unsigned int)(*buf++)<<(24-cachedBits); + if (bitsLeft > 8) cache |= (unsigned int)(*buf++)<<(16 - cachedBits); + cachedBits += bitsLeft; + bitsLeft = 0; + + cache &= (signed int) 0x80000000 >> (cachedBits - 1); + padBits = 10; + cachedBits += padBits; /* okay if this is > 32 (0's automatically shifted in from right) */ + } + + /* largest maxBits = 6, plus 4 for sign bits, so make sure cache has at least 10 bits */ + while(i < (nVals - 3) && cachedBits >= 10){ + cw = pgm_read_byte(&tBase[cache >> (32 - maxBits)]); + len=(int)( (((unsigned char)(cw)) >> 4) & 0x0f); + cachedBits -= len; + cache <<= len; + + v=(int)( (((unsigned char)(cw)) >> 3) & 0x01); + if (v) { + (v) |= ((cache) & 0x80000000); + cache <<= 1; + cachedBits--; + } + w=(int)( (((unsigned char)(cw)) >> 2) & 0x01); + if (w) { + (w) |= ((cache) & 0x80000000); + cache <<= 1; + cachedBits--; + } + + x=(int)( (((unsigned char)(cw)) >> 1) & 0x01); + if (x) { + (x) |= ((cache) & 0x80000000); + cache <<= 1; + cachedBits--; + } + + y=(int)( (((unsigned char)(cw)) >> 0) & 0x01); + if (y) { + (y) |= ((cache) & 0x80000000); + cache <<= 1; + cachedBits--; + } + + /* ran out of bits - okay (means we're done) */ + if (cachedBits < padBits) + return i; + + *vwxy++ = v; + *vwxy++ = w; + *vwxy++ = x; + *vwxy++ = y; + i += 4; + } + } + + /* decoded max number of quad values */ + return i; +} + +/*********************************************************************************************************************** + * Function: DecodeHuffman + * + * Description: decode one granule, one channel worth of Huffman codes + * + * Inputs: MP3DecInfo structure filled by UnpackFrameHeader(), UnpackSideInfo(), + * and UnpackScaleFactors() (for this granule) + * buffer pointing to start of Huffman data in MP3 frame + * pointer to bit offset (0-7) indicating starting bit in buf[0] + * number of bits in the Huffman data section of the frame + * (could include padding bits) + * index of current granule and channel + * + * Outputs: decoded coefficients in hi->huffDecBuf[ch] (hi pointer in mp3DecInfo) + * updated bitOffset + * + * Return: length (in bytes) of Huffman codes + * bitOffset also returned in parameter (0 = MSB, 7 = LSB of + * byte located at buf + offset) + * -1 if null input pointers, huffBlockBits < 0, or decoder runs + * out of bits prematurely (invalid bitstream) + **********************************************************************************************************************/ +// .data about 1ms faster per frame +int DecodeHuffman(unsigned char *buf, int *bitOffset, int huffBlockBits, int gr, int ch){ + + int r1Start, r2Start, rEnd[4]; /* region boundaries */ + int i, w, bitsUsed, bitsLeft; + unsigned char *startBuf = buf; + + SideInfoSub_t *sis; + sis = &m_SideInfoSub[gr][ch]; + //hi = (HuffmanInfo_t*) (m_MP3DecInfo->HuffmanInfoPS); + + if (huffBlockBits < 0) + return -1; + + /* figure out region boundaries (the first 2*bigVals coefficients divided into 3 regions) */ + if (sis->winSwitchFlag && sis->blockType == 2) { + if (sis->mixedBlock == 0) { + r1Start = m_SFBandTable.s[(sis->region0Count + 1) / 3] * 3; + } else { + if (m_MPEGVersion == MPEG1) { + r1Start = m_SFBandTable.l[sis->region0Count + 1]; + } else { + /* see MPEG2 spec for explanation */ + w = m_SFBandTable.s[4] - m_SFBandTable.s[3]; + r1Start = m_SFBandTable.l[6] + 2 * w; + } + } + r2Start = m_MAX_NSAMP; /* short blocks don't have region 2 */ + } else { + r1Start = m_SFBandTable.l[sis->region0Count + 1]; + r2Start = m_SFBandTable.l[sis->region0Count + 1 + sis->region1Count + 1]; + } + + /* offset rEnd index by 1 so first region = rEnd[1] - rEnd[0], etc. */ + rEnd[3] = (m_MAX_NSAMP < (2 * sis->nBigvals) ? m_MAX_NSAMP : (2 * sis->nBigvals)); + rEnd[2] = (r2Start < rEnd[3] ? r2Start : rEnd[3]); + rEnd[1] = (r1Start < rEnd[3] ? r1Start : rEnd[3]); + rEnd[0] = 0; + + /* rounds up to first all-zero pair (we don't check last pair for (x,y) == (non-zero, zero)) */ + m_HuffmanInfo->nonZeroBound[ch] = rEnd[3]; + + /* decode Huffman pairs (rEnd[i] are always even numbers) */ + bitsLeft = huffBlockBits; + for (i = 0; i < 3; i++) { + bitsUsed = DecodeHuffmanPairs(m_HuffmanInfo->huffDecBuf[ch] + rEnd[i], + rEnd[i + 1] - rEnd[i], sis->tableSelect[i], bitsLeft, buf, + *bitOffset); + if (bitsUsed < 0 || bitsUsed > bitsLeft) /* error - overran end of bitstream */ + return -1; + + /* update bitstream position */ + buf += (bitsUsed + *bitOffset) >> 3; + *bitOffset = (bitsUsed + *bitOffset) & 0x07; + bitsLeft -= bitsUsed; + } + + /* decode Huffman quads (if any) */ + m_HuffmanInfo->nonZeroBound[ch] += DecodeHuffmanQuads(m_HuffmanInfo->huffDecBuf[ch] + rEnd[3], + m_MAX_NSAMP - rEnd[3], sis->count1TableSelect, bitsLeft, buf, + *bitOffset); + + assert(m_HuffmanInfo->nonZeroBound[ch] <= m_MAX_NSAMP); + for (i = m_HuffmanInfo->nonZeroBound[ch]; i < m_MAX_NSAMP; i++) + m_HuffmanInfo->huffDecBuf[ch][i] = 0; + + /* If bits used for 576 samples < huffBlockBits, then the extras are considered + * to be stuffing bits (throw away, but need to return correct bitstream position) + */ + buf += (bitsLeft + *bitOffset) >> 3; + *bitOffset = (bitsLeft + *bitOffset) & 0x07; + + return (buf - startBuf); +} + +/*********************************************************************************************************************** + * D E Q U A N T + **********************************************************************************************************************/ + +/*********************************************************************************************************************** + * Function: MP3Dequantize + * + * Description: dequantize coefficients, decode stereo, reorder short blocks + * (one granule-worth) + * + * Inputs: index of current granule + * + * Outputs: dequantized and reordered coefficients in hi->huffDecBuf + * (one granule-worth, all channels), format = Q26 + * operates in-place on huffDecBuf but also needs di->workBuf + * updated hi->nonZeroBound index for both channels + * + * Return: 0 on success, -1 if null input pointers + * + * Notes: In calling output Q(DQ_FRACBITS_OUT), we assume an implicit bias + * of 2^15. Some (floating-point) reference implementations factor this + * into the 2^(0.25 * gain) scaling explicitly. But to avoid precision + * loss, we don't do that. Instead take it into account in the final + * round to PCM (>> by 15 less than we otherwise would have). + * Equivalently, we can think of the dequantized coefficients as + * Q(DQ_FRACBITS_OUT - 15) with no implicit bias. + **********************************************************************************************************************/ +int MP3Dequantize(int gr){ + int i, ch, nSamps, mOut[2]; + CriticalBandInfo_t *cbi; + cbi = &m_CriticalBandInfo[0]; + mOut[0] = mOut[1] = 0; + + /* dequantize all the samples in each channel */ + for (ch = 0; ch < m_MP3DecInfo->nChans; ch++) { + m_HuffmanInfo->gb[ch] = DequantChannel(m_HuffmanInfo->huffDecBuf[ch], m_DequantInfo->workBuf, + &m_HuffmanInfo->nonZeroBound[ch], &m_SideInfoSub[gr][ch], &m_ScaleFactorInfoSub[gr][ch], &cbi[ch]); + } + + /* joint stereo processing assumes one guard bit in input samples + * it's extremely rare not to have at least one gb, so if this is the case + * just make a pass over the data and clip to [-2^30+1, 2^30-1] + * in practice this may never happen + */ + if (m_FrameHeader->modeExt && (m_HuffmanInfo->gb[0] < 1 || m_HuffmanInfo->gb[1] < 1)) { + for (i = 0; i < m_HuffmanInfo->nonZeroBound[0]; i++) { + if (m_HuffmanInfo->huffDecBuf[0][i] < -0x3fffffff) m_HuffmanInfo->huffDecBuf[0][i] = -0x3fffffff; + if (m_HuffmanInfo->huffDecBuf[0][i] > 0x3fffffff) m_HuffmanInfo->huffDecBuf[0][i] = 0x3fffffff; + } + for (i = 0; i < m_HuffmanInfo->nonZeroBound[1]; i++) { + if (m_HuffmanInfo->huffDecBuf[1][i] < -0x3fffffff) m_HuffmanInfo->huffDecBuf[1][i] = -0x3fffffff; + if (m_HuffmanInfo->huffDecBuf[1][i] > 0x3fffffff) m_HuffmanInfo->huffDecBuf[1][i] = 0x3fffffff; + } + } + + /* do mid-side stereo processing, if enabled */ + if (m_FrameHeader->modeExt >> 1) { + if (m_FrameHeader->modeExt & 0x01) { + /* intensity stereo enabled - run mid-side up to start of right zero region */ + if (cbi[1].cbType == 0) + nSamps = m_SFBandTable.l[cbi[1].cbEndL + 1]; + else + nSamps = 3 * m_SFBandTable.s[cbi[1].cbEndSMax + 1]; + } else { + /* intensity stereo disabled - run mid-side on whole spectrum */ + nSamps = (m_HuffmanInfo->nonZeroBound[0] > m_HuffmanInfo->nonZeroBound[1] ? + m_HuffmanInfo->nonZeroBound[0] : m_HuffmanInfo->nonZeroBound[1]); + } + MidSideProc(m_HuffmanInfo->huffDecBuf, nSamps, mOut); + } + + /* do intensity stereo processing, if enabled */ + if (m_FrameHeader->modeExt & 0x01) { + nSamps = m_HuffmanInfo->nonZeroBound[0]; + if (m_MPEGVersion == MPEG1) { + IntensityProcMPEG1(m_HuffmanInfo->huffDecBuf, nSamps, &m_ScaleFactorInfoSub[gr][1], &m_CriticalBandInfo[0], + m_FrameHeader->modeExt >> 1, m_SideInfoSub[gr][1].mixedBlock, mOut); + } else { + IntensityProcMPEG2(m_HuffmanInfo->huffDecBuf, nSamps, &m_ScaleFactorInfoSub[gr][1], &m_CriticalBandInfo[0], + m_ScaleFactorJS, m_FrameHeader->modeExt >> 1, m_SideInfoSub[gr][1].mixedBlock, mOut); + } + } + + /* adjust guard bit count and nonZeroBound if we did any stereo processing */ + if (m_FrameHeader->modeExt) { + m_HuffmanInfo->gb[0] = CLZ(mOut[0]) - 1; + m_HuffmanInfo->gb[1] = CLZ(mOut[1]) - 1; + nSamps = (m_HuffmanInfo->nonZeroBound[0] > m_HuffmanInfo->nonZeroBound[1] ? + m_HuffmanInfo->nonZeroBound[0] : m_HuffmanInfo->nonZeroBound[1]); + m_HuffmanInfo->nonZeroBound[0] = nSamps; + m_HuffmanInfo->nonZeroBound[1] = nSamps; + } + + /* output format Q(DQ_FRACBITS_OUT) */ + return 0; +} + +/*********************************************************************************************************************** + * D Q C H A N + **********************************************************************************************************************/ + +/*********************************************************************************************************************** + * Function: DequantBlock + * + * Description: Ken's highly-optimized, low memory dequantizer performing the operation + * y = pow(x, 4.0/3.0) * pow(2, 25 - scale/4.0) + * + * Inputs: input buffer of decode Huffman codewords (signed-magnitude) + * output buffer of same length (in-place (outbuf = inbuf) is allowed) + * number of samples + * + * Outputs: dequantized samples in Q25 format + * + * Return: bitwise-OR of the unsigned outputs (for guard bit calculations) + **********************************************************************************************************************/ +int DequantBlock(int *inbuf, int *outbuf, int num, int scale){ + int tab4[4]; + int scalef, scalei, shift; + int sx, x, y; + int mask = 0; + const int *tab16; + const unsigned int *coef; + + tab16 = pow43_14[scale & 0x3]; + scalef = pow14[scale & 0x3]; + scalei =((scale >> 2) < 31 ? (scale >> 2) : 31 ); + //scalei = MIN(scale >> 2, 31); /* smallest input scale = -47, so smallest scalei = -12 */ + + /* cache first 4 values */ + shift = (scalei + 3 < 31 ? scalei + 3 : 31); + shift = (shift > 0 ? shift : 0); + + tab4[0] = 0; + tab4[1] = tab16[1] >> shift; + tab4[2] = tab16[2] >> shift; + tab4[3] = tab16[3] >> shift; + + do { + sx = *inbuf++; + x = sx & 0x7fffffff; /* sx = sign|mag */ + if (x < 4) { + y = tab4[x]; + } else if (x < 16) { + y = tab16[x]; + y = (scalei < 0) ? y << -scalei : y >> scalei; + } else { + if (x < 64) { + y = pow43[x-16]; + /* fractional scale */ + y = MULSHIFT32(y, scalef); + shift = scalei - 3; + } else { + /* normalize to [0x40000000, 0x7fffffff] */ + x <<= 17; + shift = 0; + if (x < 0x08000000) + x <<= 4, shift += 4; + if (x < 0x20000000) + x <<= 2, shift += 2; + if (x < 0x40000000) + x <<= 1, shift += 1; + + coef = (x < m_SQRTHALF) ? poly43lo : poly43hi; + + /* polynomial */ + y = coef[0]; + y = MULSHIFT32(y, x) + coef[1]; + y = MULSHIFT32(y, x) + coef[2]; + y = MULSHIFT32(y, x) + coef[3]; + y = MULSHIFT32(y, x) + coef[4]; + y = MULSHIFT32(y, pow2frac[shift]) << 3; + + /* fractional scale */ + y = MULSHIFT32(y, scalef); + shift = scalei - pow2exp[shift]; + } + + /* integer scale */ + if (shift < 0) { + shift = -shift; + if (y > (0x7fffffff >> shift)) + y = 0x7fffffff; /* clip */ + else + y <<= shift; + } else { + y >>= shift; + } + } + + /* sign and store */ + mask |= y; + *outbuf++ = (sx < 0) ? -y : y; + + } while (--num); + + return mask; +} + +/*********************************************************************************************************************** + * Function: DequantChannel + * + * Description: dequantize one granule, one channel worth of decoded Huffman codewords + * + * Inputs: sample buffer (decoded Huffman codewords), length = m_MAX_NSAMP samples + * work buffer for reordering short-block, length = m_MAX_REORDER_SAMPS + * samples (3 * width of largest short-block critical band) + * non-zero bound for this channel/granule + * valid FrameHeader, SideInfoSub, ScaleFactorInfoSub, and CriticalBandInfo + * structures for this channel/granule + * + * Outputs: MAX_NSAMP dequantized samples in sampleBuf + * updated non-zero bound (indicating which samples are != 0 after DQ) + * filled-in cbi structure indicating start and end critical bands + * + * Return: minimum number of guard bits in dequantized sampleBuf + * + * Notes: dequantized samples in Q(DQ_FRACBITS_OUT) format + **********************************************************************************************************************/ +int DequantChannel(int *sampleBuf, int *workBuf, int *nonZeroBound, SideInfoSub_t *sis, ScaleFactorInfoSub_t *sfis, + CriticalBandInfo_t *cbi) +{ + int i, j, w, cb; + int /* cbStartL, */ cbEndL, cbStartS, cbEndS; + int nSamps, nonZero, sfactMultiplier, gbMask; + int globalGain, gainI; + int cbMax[3]; + typedef int ARRAY3[3]; /* for short-block reordering */ + ARRAY3 *buf; /* short block reorder */ + + /* set default start/end points for short/long blocks - will update with non-zero cb info */ + if (sis->blockType == 2) { + // cbStartL = 0; + if (sis->mixedBlock) { + cbEndL = (m_MPEGVersion == MPEG1 ? 8 : 6); + cbStartS = 3; + } else { + cbEndL = 0; + cbStartS = 0; + } + cbEndS = 13; + } else { + /* long block */ + //cbStartL = 0; + cbEndL = 22; + cbStartS = 13; + cbEndS = 13; + } + cbMax[2] = cbMax[1] = cbMax[0] = 0; + gbMask = 0; + i = 0; + + /* sfactScale = 0 --> quantizer step size = 2 + * sfactScale = 1 --> quantizer step size = sqrt(2) + * so sfactMultiplier = 2 or 4 (jump through globalGain by powers of 2 or sqrt(2)) + */ + sfactMultiplier = 2 * (sis->sfactScale + 1); + + /* offset globalGain by -2 if midSide enabled, for 1/sqrt(2) used in MidSideProc() + * (DequantBlock() does 0.25 * gainI so knocking it down by two is the same as + * dividing every sample by sqrt(2) = multiplying by 2^-.5) + */ + globalGain = sis->globalGain; + if (m_FrameHeader->modeExt >> 1) + globalGain -= 2; + globalGain += m_IMDCT_SCALE; /* scale everything by sqrt(2), for fast IMDCT36 */ + + /* long blocks */ + for (cb = 0; cb < cbEndL; cb++) { + + nonZero = 0; + nSamps = m_SFBandTable.l[cb + 1] - m_SFBandTable.l[cb]; + gainI = 210 - globalGain + sfactMultiplier * (sfis->l[cb] + (sis->preFlag ? (int)preTab[cb] : 0)); + + nonZero |= DequantBlock(sampleBuf + i, sampleBuf + i, nSamps, gainI); + i += nSamps; + + /* update highest non-zero critical band */ + if (nonZero) + cbMax[0] = cb; + gbMask |= nonZero; + + if (i >= *nonZeroBound) + break; + } + + /* set cbi (Type, EndS[], EndSMax will be overwritten if we proceed to do short blocks) */ + cbi->cbType = 0; /* long only */ + cbi->cbEndL = cbMax[0]; + cbi->cbEndS[0] = cbi->cbEndS[1] = cbi->cbEndS[2] = 0; + cbi->cbEndSMax = 0; + + /* early exit if no short blocks */ + if (cbStartS >= 12) + return CLZ(gbMask) - 1; + + /* short blocks */ + cbMax[2] = cbMax[1] = cbMax[0] = cbStartS; + for (cb = cbStartS; cb < cbEndS; cb++) { + + nSamps = m_SFBandTable.s[cb + 1] - m_SFBandTable.s[cb]; + for (w = 0; w < 3; w++) { + nonZero = 0; + gainI = 210 - globalGain + 8*sis->subBlockGain[w] + sfactMultiplier*(sfis->s[cb][w]); + + nonZero |= DequantBlock(sampleBuf + i + nSamps*w, workBuf + nSamps*w, nSamps, gainI); + + /* update highest non-zero critical band */ + if (nonZero) + cbMax[w] = cb; + gbMask |= nonZero; + } + + /* reorder blocks */ + buf = (ARRAY3 *)(sampleBuf + i); + i += 3*nSamps; + for (j = 0; j < nSamps; j++) { + buf[j][0] = workBuf[0*nSamps + j]; + buf[j][1] = workBuf[1*nSamps + j]; + buf[j][2] = workBuf[2*nSamps + j]; + } + + assert(3*nSamps <= m_MAX_REORDER_SAMPS); + + if (i >= *nonZeroBound) + break; + } + + /* i = last non-zero INPUT sample processed, which corresponds to highest possible non-zero + * OUTPUT sample (after reorder) + * however, the original nzb is no longer necessarily true + * for each cb, buf[][] is updated with 3*nSamps samples (i increases 3*nSamps each time) + * (buf[j + 1][0] = 3 (input) samples ahead of buf[j][0]) + * so update nonZeroBound to i + */ + *nonZeroBound = i; + + assert(*nonZeroBound <= m_MAX_NSAMP); + + cbi->cbType = (sis->mixedBlock ? 2 : 1); /* 2 = mixed short/long, 1 = short only */ + + cbi->cbEndS[0] = cbMax[0]; + cbi->cbEndS[1] = cbMax[1]; + cbi->cbEndS[2] = cbMax[2]; + + cbi->cbEndSMax = cbMax[0]; + cbi->cbEndSMax = (cbi->cbEndSMax > cbMax[1] ? cbi->cbEndSMax : cbMax[1]); + cbi->cbEndSMax = (cbi->cbEndSMax > cbMax[2] ? cbi->cbEndSMax : cbMax[2]); + + return CLZ(gbMask) - 1; +} + +/*********************************************************************************************************************** + * S T P R O C + **********************************************************************************************************************/ + +/*********************************************************************************************************************** + * Function: MidSideProc + * + * Description: sum-difference stereo reconstruction + * + * Inputs: vector x with dequantized samples from left and right channels + * number of non-zero samples (MAX of left and right) + * assume 1 guard bit in input + * guard bit mask (left and right channels) + * + * Outputs: updated sample vector x + * updated guard bit mask + * + * Return: none + * + * Notes: assume at least 1 GB in input + **********************************************************************************************************************/ +void MidSideProc(int x[m_MAX_NCHAN][m_MAX_NSAMP], int nSamps, int mOut[2]){ + int i, xr, xl, mOutL, mOutR; + + /* L = (M+S)/sqrt(2), R = (M-S)/sqrt(2) + * NOTE: 1/sqrt(2) done in DequantChannel() - see comments there + */ + mOutL = mOutR = 0; + for (i = 0; i < nSamps; i++) { + xl = x[0][i]; + xr = x[1][i]; + x[0][i] = xl + xr; + x[1][i] = xl - xr; + mOutL |= FASTABS(x[0][i]); + mOutR |= FASTABS(x[1][i]); + } + mOut[0] |= mOutL; + mOut[1] |= mOutR; +} + +/*********************************************************************************************************************** + * Function: IntensityProcMPEG1 + * + * Description: intensity stereo processing for MPEG1 + * + * Inputs: vector x with dequantized samples from left and right channels + * number of non-zero samples in left channel + * valid FrameHeader struct + * two each of ScaleFactorInfoSub, CriticalBandInfo structs (both channels) + * flags indicating midSide on/off, mixedBlock on/off + * guard bit mask (left and right channels) + * + * Outputs: updated sample vector x + * updated guard bit mask + * + * Return: none + * + * Notes: assume at least 1 GB in input + * + **********************************************************************************************************************/ +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]) +{ + int i = 0, j = 0, n = 0, cb = 0, w = 0; + int sampsLeft, isf, mOutL, mOutR, xl, xr; + int fl, fr, fls[3], frs[3]; + int cbStartL = 0, cbStartS = 0, cbEndL = 0, cbEndS = 0; + int *isfTab; + (void) mixFlag; + + /* NOTE - this works fine for mixed blocks, as long as the switch point starts in the + * short block section (i.e. on or after sample 36 = sfBand->l[8] = 3*sfBand->s[3] + * is this a safe assumption? + */ + if (cbi[1].cbType == 0) { + /* long block */ + cbStartL = cbi[1].cbEndL + 1; + cbEndL = cbi[0].cbEndL + 1; + cbStartS = cbEndS = 0; + i = m_SFBandTable.l[cbStartL]; + } else if (cbi[1].cbType == 1 || cbi[1].cbType == 2) { + /* short or mixed block */ + cbStartS = cbi[1].cbEndSMax + 1; + cbEndS = cbi[0].cbEndSMax + 1; + cbStartL = cbEndL = 0; + i = 3 * m_SFBandTable.s[cbStartS]; + } + sampsLeft = nSamps - i; /* process to length of left */ + isfTab = (int *) ISFMpeg1[midSideFlag]; + mOutL = mOutR = 0; + + /* long blocks */ + for (cb = cbStartL; cb < cbEndL && sampsLeft > 0; cb++) { + isf = sfis->l[cb]; + if (isf == 7) { + fl = ISFIIP[midSideFlag][0]; + fr = ISFIIP[midSideFlag][1]; + } else { + fl = isfTab[isf]; + fr = isfTab[6] - isfTab[isf]; + } + + n = m_SFBandTable.l[cb + 1] - m_SFBandTable.l[cb]; + for (j = 0; j < n && sampsLeft > 0; j++, i++) { + xr = MULSHIFT32(fr, x[0][i]) << 2; + x[1][i] = xr; + mOutR |= FASTABS(xr); + xl = MULSHIFT32(fl, x[0][i]) << 2; + x[0][i] = xl; + mOutL |= FASTABS(xl); + sampsLeft--; + } + } + /* short blocks */ + for (cb = cbStartS; cb < cbEndS && sampsLeft >= 3; cb++) { + for (w = 0; w < 3; w++) { + isf = sfis->s[cb][w]; + if (isf == 7) { + fls[w] = ISFIIP[midSideFlag][0]; + frs[w] = ISFIIP[midSideFlag][1]; + } else { + fls[w] = isfTab[isf]; + frs[w] = isfTab[6] - isfTab[isf]; + } + } + n = m_SFBandTable.s[cb + 1] - m_SFBandTable.s[cb]; + for (j = 0; j < n && sampsLeft >= 3; j++, i += 3) { + xr = MULSHIFT32(frs[0], x[0][i + 0]) << 2; + x[1][i + 0] = xr; + mOutR |= FASTABS(xr); + xl = MULSHIFT32(fls[0], x[0][i + 0]) << 2; + x[0][i + 0] = xl; + mOutL |= FASTABS(xl); + xr = MULSHIFT32(frs[1], x[0][i + 1]) << 2; + x[1][i + 1] = xr; + mOutR |= FASTABS(xr); + xl = MULSHIFT32(fls[1], x[0][i + 1]) << 2; + x[0][i + 1] = xl; + mOutL |= FASTABS(xl); + xr = MULSHIFT32(frs[2], x[0][i + 2]) << 2; + x[1][i + 2] = xr; + mOutR |= FASTABS(xr); + xl = MULSHIFT32(fls[2], x[0][i + 2]) << 2; + x[0][i + 2] = xl; + mOutL |= FASTABS(xl); + sampsLeft -= 3; + } + } + mOut[0] = mOutL; + mOut[1] = mOutR; + return; +} + +/*********************************************************************************************************************** + * Function: IntensityProcMPEG2 + * + * Description: intensity stereo processing for MPEG2 + * + * Inputs: vector x with dequantized samples from left and right channels + * number of non-zero samples in left channel + * valid FrameHeader struct + * two each of ScaleFactorInfoSub, CriticalBandInfo structs (both channels) + * ScaleFactorJS struct with joint stereo info from UnpackSFMPEG2() + * flags indicating midSide on/off, mixedBlock on/off + * guard bit mask (left and right channels) + * + * Outputs: updated sample vector x + * updated guard bit mask + * + * Return: none + * + * Notes: assume at least 1 GB in input + * + **********************************************************************************************************************/ +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]) { + int i, j, k, n, r, cb, w; + int fl, fr, mOutL, mOutR, xl, xr; + int sampsLeft; + int isf, sfIdx, tmp, il[23]; + int *isfTab; + int cbStartL, cbStartS, cbEndL, cbEndS; + + (void) mixFlag; + + isfTab = (int *) ISFMpeg2[sfjs->intensityScale][midSideFlag]; + mOutL = mOutR = 0; + + /* fill buffer with illegal intensity positions (depending on slen) */ + for (k = r = 0; r < 4; r++) { + tmp = (1 << sfjs->slen[r]) - 1; + for (j = 0; j < sfjs->nr[r]; j++, k++) + il[k] = tmp; + } + + if (cbi[1].cbType == 0) { + /* long blocks */ + il[21] = il[22] = 1; + cbStartL = cbi[1].cbEndL + 1; /* start at end of right */ + cbEndL = cbi[0].cbEndL + 1; /* process to end of left */ + i = m_SFBandTable.l[cbStartL]; + sampsLeft = nSamps - i; + + for (cb = cbStartL; cb < cbEndL; cb++) { + sfIdx = sfis->l[cb]; + if (sfIdx == il[cb]) { + fl = ISFIIP[midSideFlag][0]; + fr = ISFIIP[midSideFlag][1]; + } else { + isf = (sfis->l[cb] + 1) >> 1; + fl = isfTab[(sfIdx & 0x01 ? isf : 0)]; + fr = isfTab[(sfIdx & 0x01 ? 0 : isf)]; + } + int r=m_SFBandTable.l[cb + 1] - m_SFBandTable.l[cb]; + n=(r < sampsLeft ? r : sampsLeft); + //n = MIN(fh->sfBand->l[cb + 1] - fh->sfBand->l[cb], sampsLeft); + for (j = 0; j < n; j++, i++) { + xr = MULSHIFT32(fr, x[0][i]) << 2; + x[1][i] = xr; + mOutR |= FASTABS(xr); + xl = MULSHIFT32(fl, x[0][i]) << 2; + x[0][i] = xl; + mOutL |= FASTABS(xl); + } + /* early exit once we've used all the non-zero samples */ + sampsLeft -= n; + if (sampsLeft == 0) + break; + } + } else { + /* short or mixed blocks */ + il[12] = 1; + + for (w = 0; w < 3; w++) { + cbStartS = cbi[1].cbEndS[w] + 1; /* start at end of right */ + cbEndS = cbi[0].cbEndS[w] + 1; /* process to end of left */ + i = 3 * m_SFBandTable.s[cbStartS] + w; + + /* skip through sample array by 3, so early-exit logic would be more tricky */ + for (cb = cbStartS; cb < cbEndS; cb++) { + sfIdx = sfis->s[cb][w]; + if (sfIdx == il[cb]) { + fl = ISFIIP[midSideFlag][0]; + fr = ISFIIP[midSideFlag][1]; + } else { + isf = (sfis->s[cb][w] + 1) >> 1; + fl = isfTab[(sfIdx & 0x01 ? isf : 0)]; + fr = isfTab[(sfIdx & 0x01 ? 0 : isf)]; + } + n = m_SFBandTable.s[cb + 1] - m_SFBandTable.s[cb]; + + for (j = 0; j < n; j++, i += 3) { + xr = MULSHIFT32(fr, x[0][i]) << 2; + x[1][i] = xr; + mOutR |= FASTABS(xr); + xl = MULSHIFT32(fl, x[0][i]) << 2; + x[0][i] = xl; + mOutL |= FASTABS(xl); + } + } + } + } + mOut[0] = mOutL; + mOut[1] = mOutR; + return; +} + +/*********************************************************************************************************************** + * I M D C T + **********************************************************************************************************************/ + +/*********************************************************************************************************************** + * Function: AntiAlias + * + * Description: smooth transition across DCT block boundaries (every 18 coefficients) + * + * Inputs: vector of dequantized coefficients, length = (nBfly+1) * 18 + * number of "butterflies" to perform (one butterfly means one + * inter-block smoothing operation) + * + * Outputs: updated coefficient vector x + * + * Return: none + * + * Notes: weighted average of opposite bands (pairwise) from the 8 samples + * before and after each block boundary + * nBlocks = (nonZeroBound + 7) / 18, since nZB is the first ZERO sample + * above which all other samples are also zero + * max gain per sample = 1.372 + * MAX(i) (abs(csa[i][0]) + abs(csa[i][1])) + * bits gained = 0 + * assume at least 1 guard bit in x[] to avoid overflow + * (should be guaranteed from dequant, and max gain from stproc * max + * gain from AntiAlias < 2.0) + **********************************************************************************************************************/ +// a little bit faster in RAM (< 1 ms per block) +/* __attribute__ ((section (".data"))) */ +void AntiAlias(int *x, int nBfly){ + int k, a0, b0, c0, c1; + const uint32_t *c; + + /* csa = Q31 */ + for (k = nBfly; k > 0; k--) { + c = csa[0]; + x += 18; + a0 = x[-1]; + c0 = *c; + c++; + b0 = x[0]; + c1 = *c; + c++; + x[-1] = (MULSHIFT32(c0, a0) - MULSHIFT32(c1, b0)) << 1; + x[0] = (MULSHIFT32(c0, b0) + MULSHIFT32(c1, a0)) << 1; + + a0 = x[-2]; + c0 = *c; + c++; + b0 = x[1]; + c1 = *c; + c++; + x[-2] = (MULSHIFT32(c0, a0) - MULSHIFT32(c1, b0)) << 1; + x[1] = (MULSHIFT32(c0, b0) + MULSHIFT32(c1, a0)) << 1; + + a0 = x[-3]; + c0 = *c; + c++; + b0 = x[2]; + c1 = *c; + c++; + x[-3] = (MULSHIFT32(c0, a0) - MULSHIFT32(c1, b0)) << 1; + x[2] = (MULSHIFT32(c0, b0) + MULSHIFT32(c1, a0)) << 1; + + a0 = x[-4]; + c0 = *c; + c++; + b0 = x[3]; + c1 = *c; + c++; + x[-4] = (MULSHIFT32(c0, a0) - MULSHIFT32(c1, b0)) << 1; + x[3] = (MULSHIFT32(c0, b0) + MULSHIFT32(c1, a0)) << 1; + + a0 = x[-5]; + c0 = *c; + c++; + b0 = x[4]; + c1 = *c; + c++; + x[-5] = (MULSHIFT32(c0, a0) - MULSHIFT32(c1, b0)) << 1; + x[4] = (MULSHIFT32(c0, b0) + MULSHIFT32(c1, a0)) << 1; + + a0 = x[-6]; + c0 = *c; + c++; + b0 = x[5]; + c1 = *c; + c++; + x[-6] = (MULSHIFT32(c0, a0) - MULSHIFT32(c1, b0)) << 1; + x[5] = (MULSHIFT32(c0, b0) + MULSHIFT32(c1, a0)) << 1; + + a0 = x[-7]; + c0 = *c; + c++; + b0 = x[6]; + c1 = *c; + c++; + x[-7] = (MULSHIFT32(c0, a0) - MULSHIFT32(c1, b0)) << 1; + x[6] = (MULSHIFT32(c0, b0) + MULSHIFT32(c1, a0)) << 1; + + a0 = x[-8]; + c0 = *c; + c++; + b0 = x[7]; + c1 = *c; + c++; + x[-8] = (MULSHIFT32(c0, a0) - MULSHIFT32(c1, b0)) << 1; + x[7] = (MULSHIFT32(c0, b0) + MULSHIFT32(c1, a0)) << 1; + } +} + +/*********************************************************************************************************************** + * Function: WinPrevious + * + * Description: apply specified window to second half of previous IMDCT (overlap part) + * + * Inputs: vector of 9 coefficients (xPrev) + * + * Outputs: 18 windowed output coefficients (gain 1 integer bit) + * window type (0, 1, 2, 3) + * + * Return: none + * + * Notes: produces 9 output samples from 18 input samples via symmetry + * all blocks gain at least 1 guard bit via window (long blocks get extra + * sign bit, short blocks can have one addition but max gain < 1.0) + **********************************************************************************************************************/ + +void WinPrevious(int *xPrev, int *xPrevWin, int btPrev){ + int i, x, *xp, *xpwLo, *xpwHi, wLo, wHi; + const uint32_t *wpLo, *wpHi; + + xp = xPrev; + /* mapping (see IMDCT12x3): xPrev[0-2] = sum[6-8], xPrev[3-8] = sum[12-17] */ + if (btPrev == 2) { + /* this could be reordered for minimum loads/stores */ + wpLo = imdctWin[btPrev]; + xPrevWin[0] = MULSHIFT32(wpLo[6], xPrev[2]) + + MULSHIFT32(wpLo[0], xPrev[6]); + xPrevWin[1] = MULSHIFT32(wpLo[7], xPrev[1]) + + MULSHIFT32(wpLo[1], xPrev[7]); + xPrevWin[2] = MULSHIFT32(wpLo[8], xPrev[0]) + + MULSHIFT32(wpLo[2], xPrev[8]); + xPrevWin[3] = MULSHIFT32(wpLo[9], xPrev[0]) + + MULSHIFT32(wpLo[3], xPrev[8]); + xPrevWin[4] = MULSHIFT32(wpLo[10], xPrev[1]) + + MULSHIFT32(wpLo[4], xPrev[7]); + xPrevWin[5] = MULSHIFT32(wpLo[11], xPrev[2]) + + MULSHIFT32(wpLo[5], xPrev[6]); + xPrevWin[6] = MULSHIFT32(wpLo[6], xPrev[5]); + xPrevWin[7] = MULSHIFT32(wpLo[7], xPrev[4]); + xPrevWin[8] = MULSHIFT32(wpLo[8], xPrev[3]); + xPrevWin[9] = MULSHIFT32(wpLo[9], xPrev[3]); + xPrevWin[10] = MULSHIFT32(wpLo[10], xPrev[4]); + xPrevWin[11] = MULSHIFT32(wpLo[11], xPrev[5]); + xPrevWin[12] = xPrevWin[13] = xPrevWin[14] = xPrevWin[15] = + xPrevWin[16] = xPrevWin[17] = 0; + } else { + /* use ARM-style pointers (*ptr++) so that ADS compiles well */ + wpLo = imdctWin[btPrev] + 18; + wpHi = wpLo + 17; + xpwLo = xPrevWin; + xpwHi = xPrevWin + 17; + for (i = 9; i > 0; i--) { + x = *xp++; + wLo = *wpLo++; + wHi = *wpHi--; + *xpwLo++ = MULSHIFT32(wLo, x); + *xpwHi-- = MULSHIFT32(wHi, x); + } + } +} + +/*********************************************************************************************************************** + * Function: FreqInvertRescale + * + * Description: do frequency inversion (odd samples of odd blocks) and rescale + * if necessary (extra guard bits added before IMDCT) + * + * Inputs: output vector y (18 new samples, spaced NBANDS apart) + * previous sample vector xPrev (9 samples) + * index of current block + * number of extra shifts added before IMDCT (usually 0) + * + * Outputs: inverted and rescaled (as necessary) outputs + * rescaled (as necessary) previous samples + * + * Return: updated mOut (from new outputs y) + **********************************************************************************************************************/ + +int FreqInvertRescale(int *y, int *xPrev, int blockIdx, int es) { + + if (es == 0) { + /* fast case - frequency invert only (no rescaling) */ + if (blockIdx & 0x01) { + y += m_NBANDS; + for (int i = 0; i < 9; i++) { + *y = - *y; y += 2 * m_NBANDS; + } + } + return 0; + } + + int d, mOut; + /* undo pre-IMDCT scaling, clipping if necessary */ + mOut = 0; + if (blockIdx & 0x01) { + /* frequency invert */ + for (int i = 0; i < 9; i++) { + d = *y; CLIP_2N(d, (31 - es)); *y = d << es; mOut |= FASTABS(*y); y += m_NBANDS; + d = -*y; CLIP_2N(d, (31 - es)); *y = d << es; mOut |= FASTABS(*y); y += m_NBANDS; + d = *xPrev; CLIP_2N(d, (31 - es)); *xPrev++ = d << es; + } + } else { + for (int i = 0; i < 9; i++) { + d = *y; CLIP_2N(d, (31 - es)); *y = d << es; mOut |= FASTABS(*y); y += m_NBANDS; + d = *y; CLIP_2N(d, (31 - es)); *y = d << es; mOut |= FASTABS(*y); y += m_NBANDS; + d = *xPrev; CLIP_2N(d, (31 - es)); *xPrev++ = d << es; + } + } + return mOut; + +} + + +/* require at least 3 guard bits in x[] to ensure no overflow */ +void idct9(int *x) { + int a1, a2, a3, a4, a5, a6, a7, a8, a9; + int a10, a11, a12, a13, a14, a15, a16, a17, a18; + int a19, a20, a21, a22, a23, a24, a25, a26, a27; + int m1, m3, m5, m6, m7, m8, m9, m10, m11, m12; + int x0, x1, x2, x3, x4, x5, x6, x7, x8; + + x0 = x[0]; + x1 = x[1]; + x2 = x[2]; + x3 = x[3]; + x4 = x[4]; + x5 = x[5]; + x6 = x[6]; + x7 = x[7]; + x8 = x[8]; + + a1 = x0 - x6; + a2 = x1 - x5; + a3 = x1 + x5; + a4 = x2 - x4; + a5 = x2 + x4; + a6 = x2 + x8; + a7 = x1 + x7; + + a8 = a6 - a5; /* ie x[8] - x[4] */ + a9 = a3 - a7; /* ie x[5] - x[7] */ + a10 = a2 - x7; /* ie x[1] - x[5] - x[7] */ + a11 = a4 - x8; /* ie x[2] - x[4] - x[8] */ + + /* do the << 1 as constant shifts where mX is actually used (free, no stall or extra inst.) */ + m1 = MULSHIFT32(c9_0, x3); + m3 = MULSHIFT32(c9_0, a10); + m5 = MULSHIFT32(c9_1, a5); + m6 = MULSHIFT32(c9_2, a6); + m7 = MULSHIFT32(c9_1, a8); + m8 = MULSHIFT32(c9_2, a5); + m9 = MULSHIFT32(c9_3, a9); + m10 = MULSHIFT32(c9_4, a7); + m11 = MULSHIFT32(c9_3, a3); + m12 = MULSHIFT32(c9_4, a9); + + a12 = x[0] + (x[6] >> 1); + a13 = a12 + (m1 << 1); + a14 = a12 - (m1 << 1); + a15 = a1 + (a11 >> 1); + a16 = (m5 << 1) + (m6 << 1); + a17 = (m7 << 1) - (m8 << 1); + a18 = a16 + a17; + a19 = (m9 << 1) + (m10 << 1); + a20 = (m11 << 1) - (m12 << 1); + + a21 = a20 - a19; + a22 = a13 + a16; + a23 = a14 + a16; + a24 = a14 + a17; + a25 = a13 + a17; + a26 = a14 - a18; + a27 = a13 - a18; + + x0 = a22 + a19; + x[0] = x0; + x1 = a15 + (m3 << 1); + x[1] = x1; + x2 = a24 + a20; + x[2] = x2; + x3 = a26 - a21; + x[3] = x3; + x4 = a1 - a11; + x[4] = x4; + x5 = a27 + a21; + x[5] = x5; + x6 = a25 - a20; + x[6] = x6; + x7 = a15 - (m3 << 1); + x[7] = x7; + x8 = a23 - a19; + x[8] = x8; +} + + +/*********************************************************************************************************************** + * Function: IMDCT36 + * + * Description: 36-point modified DCT, with windowing and overlap-add (50% overlap) + * + * Inputs: vector of 18 coefficients (N/2 inputs produces N outputs, by symmetry) + * overlap part of last IMDCT (9 samples - see output comments) + * window type (0,1,2,3) of current and previous block + * current block index (for deciding whether to do frequency inversion) + * number of guard bits in input vector + * + * Outputs: 18 output samples, after windowing and overlap-add with last frame + * second half of (unwindowed) 36-point IMDCT - save for next time + * only save 9 xPrev samples, using symmetry (see WinPrevious()) + * + * Notes: this is Ken's hyper-fast algorithm, including symmetric sin window + * optimization, if applicable + * total number of multiplies, general case: + * 2*10 (idct9) + 9 (last stage imdct) + 36 (for windowing) = 65 + * total number of multiplies, btCurr == 0 && btPrev == 0: + * 2*10 (idct9) + 9 (last stage imdct) + 18 (for windowing) = 47 + * + * blockType == 0 is by far the most common case, so it should be + * possible to use the fast path most of the time + * this is the fastest known algorithm for performing + * long IMDCT + windowing + overlap-add in MP3 + * + * Return: mOut (OR of abs(y) for all y calculated here) + **********************************************************************************************************************/ +// barely faster in RAM + +int IMDCT36(int *xCurr, int *xPrev, int *y, int btCurr, int btPrev, int blockIdx, int gb){ + int i, es, xBuf[18], xPrevWin[18]; + int acc1, acc2, s, d, t, mOut; + int xo, xe, c, *xp, yLo, yHi; + const uint32_t *cp, *wp; + acc1 = acc2 = 0; + xCurr += 17; + /* 7 gb is always adequate for antialias + accumulator loop + idct9 */ + if (gb < 7) { + /* rarely triggered - 5% to 10% of the time on normal clips (with Q25 input) */ + es = 7 - gb; + for (i = 8; i >= 0; i--) { + acc1 = ((*xCurr--) >> es) - acc1; + acc2 = acc1 - acc2; + acc1 = ((*xCurr--) >> es) - acc1; + xBuf[i + 9] = acc2; /* odd */ + xBuf[i + 0] = acc1; /* even */ + xPrev[i] >>= es; + } + } else { + es = 0; + /* max gain = 18, assume adequate guard bits */ + for (i = 8; i >= 0; i--) { + acc1 = (*xCurr--) - acc1; + acc2 = acc1 - acc2; + acc1 = (*xCurr--) - acc1; + xBuf[i + 9] = acc2; /* odd */ + xBuf[i + 0] = acc1; /* even */ + } + } + /* xEven[0] and xOdd[0] scaled by 0.5 */ + xBuf[9] >>= 1; + xBuf[0] >>= 1; + + /* do 9-point IDCT on even and odd */ + idct9(xBuf + 0); /* even */ + idct9(xBuf + 9); /* odd */ + + xp = xBuf + 8; + cp = c18 + 8; + mOut = 0; + if (btPrev == 0 && btCurr == 0) { + /* fast path - use symmetry of sin window to reduce windowing multiplies to 18 (N/2) */ + wp = fastWin36; + for (i = 0; i < 9; i++) { + /* do ARM-style pointer arithmetic (i still needed for y[] indexing - compiler spills if 2 y pointers) */ + c = *cp--; + xo = *(xp + 9); + xe = *xp--; + /* gain 2 int bits here */ + xo = MULSHIFT32(c, xo); /* 2*c18*xOdd (mul by 2 implicit in scaling) */ + xe >>= 2; + + s = -(*xPrev); /* sum from last block (always at least 2 guard bits) */ + d = -(xe - xo); /* gain 2 int bits, don't shift xo (effective << 1 to eat sign bit, << 1 for mul by 2) */ + (*xPrev++) = xe + xo; /* symmetry - xPrev[i] = xPrev[17-i] for long blocks */ + t = s - d; + + yLo = (d + (MULSHIFT32(t, *wp++) << 2)); + yHi = (s + (MULSHIFT32(t, *wp++) << 2)); + y[(i) * m_NBANDS] = yLo; + y[(17 - i) * m_NBANDS] = yHi; + mOut |= FASTABS(yLo); + mOut |= FASTABS(yHi); + } + } else { + /* slower method - either prev or curr is using window type != 0 so do full 36-point window + * output xPrevWin has at least 3 guard bits (xPrev has 2, gain 1 in WinPrevious) + */ + WinPrevious(xPrev, xPrevWin, btPrev); + + wp = imdctWin[btCurr]; + for (i = 0; i < 9; i++) { + c = *cp--; + xo = *(xp + 9); + xe = *xp--; + /* gain 2 int bits here */ + xo = MULSHIFT32(c, xo); /* 2*c18*xOdd (mul by 2 implicit in scaling) */ + xe >>= 2; + + d = xe - xo; + (*xPrev++) = xe + xo; /* symmetry - xPrev[i] = xPrev[17-i] for long blocks */ + + yLo = (xPrevWin[i] + MULSHIFT32(d, wp[i])) << 2; + yHi = (xPrevWin[17 - i] + MULSHIFT32(d, wp[17 - i])) << 2; + y[(i) * m_NBANDS] = yLo; + y[(17 - i) * m_NBANDS] = yHi; + mOut |= FASTABS(yLo); + mOut |= FASTABS(yHi); + } + } + + xPrev -= 9; + mOut |= FreqInvertRescale(y, xPrev, blockIdx, es); + + return mOut; +} + + + +/* 12-point inverse DCT, used in IMDCT12x3() + * 4 input guard bits will ensure no overflow + */ +void imdct12(int *x, int *out) { + int a0, a1, a2; + int x0, x1, x2, x3, x4, x5; + + x0 = *x; + x += 3; + x1 = *x; + x += 3; + x2 = *x; + x += 3; + x3 = *x; + x += 3; + x4 = *x; + x += 3; + x5 = *x; + x += 3; + + x4 -= x5; + x3 -= x4; + x2 -= x3; + x3 -= x5; + x1 -= x2; + x0 -= x1; + x1 -= x3; + + x0 >>= 1; + x1 >>= 1; + + a0 = MULSHIFT32(c3_0, x2) << 1; + a1 = x0 + (x4 >> 1); + a2 = x0 - x4; + x0 = a1 + a0; + x2 = a2; + x4 = a1 - a0; + + a0 = MULSHIFT32(c3_0, x3) << 1; + a1 = x1 + (x5 >> 1); + a2 = x1 - x5; + + /* cos window odd samples, mul by 2, eat sign bit */ + x1 = MULSHIFT32(c6[0], a1 + a0) << 2; + x3 = MULSHIFT32(c6[1], a2) << 2; + x5 = MULSHIFT32(c6[2], a1 - a0) << 2; + + *out = x0 + x1; + out++; + *out = x2 + x3; + out++; + *out = x4 + x5; + out++; + *out = x4 - x5; + out++; + *out = x2 - x3; + out++; + *out = x0 - x1; +} + +/*********************************************************************************************************************** + * Function: IMDCT12x3 + * + * Description: three 12-point modified DCT's for short blocks, with windowing, + * short block concatenation, and overlap-add + * + * Inputs: 3 interleaved vectors of 6 samples each + * (block0[0], block1[0], block2[0], block0[1], block1[1]....) + * overlap part of last IMDCT (9 samples - see output comments) + * window type (0,1,2,3) of previous block + * current block index (for deciding whether to do frequency inversion) + * number of guard bits in input vector + * + * Outputs: updated sample vector x, net gain of 1 integer bit + * second half of (unwindowed) IMDCT's - save for next time + * only save 9 xPrev samples, using symmetry (see WinPrevious()) + * + * Return: mOut (OR of abs(y) for all y calculated here) + **********************************************************************************************************************/ +// barely faster in RAM +int IMDCT12x3(int *xCurr, int *xPrev, int *y, int btPrev, int blockIdx, int gb){ + int i, es, mOut, yLo, xBuf[18], xPrevWin[18]; /* need temp buffer for reordering short blocks */ + const uint32_t *wp; + es = 0; + /* 7 gb is always adequate for accumulator loop + idct12 + window + overlap */ + if (gb < 7) { + es = 7 - gb; + for (i = 0; i < 18; i += 2) { + xCurr[i + 0] >>= es; + xCurr[i + 1] >>= es; + *xPrev++ >>= es; + } + xPrev -= 9; + } + + /* requires 4 input guard bits for each imdct12 */ + imdct12(xCurr + 0, xBuf + 0); + imdct12(xCurr + 1, xBuf + 6); + imdct12(xCurr + 2, xBuf + 12); + + /* window previous from last time */ + WinPrevious(xPrev, xPrevWin, btPrev); + + /* could unroll this for speed, minimum loads (short blocks usually rare, so doesn't make much overall difference) + * xPrevWin[i] << 2 still has 1 gb always, max gain of windowed xBuf stuff also < 1.0 and gain the sign bit + * so y calculations won't overflow + */ + wp = imdctWin[2]; + mOut = 0; + for (i = 0; i < 3; i++) { + yLo = (xPrevWin[0 + i] << 2); + mOut |= FASTABS(yLo); + y[(0 + i) * m_NBANDS] = yLo; + yLo = (xPrevWin[3 + i] << 2); + mOut |= FASTABS(yLo); + y[(3 + i) * m_NBANDS] = yLo; + yLo = (xPrevWin[6 + i] << 2) + (MULSHIFT32(wp[0 + i], xBuf[3 + i])); + mOut |= FASTABS(yLo); + y[(6 + i) * m_NBANDS] = yLo; + yLo = (xPrevWin[9 + i] << 2) + (MULSHIFT32(wp[3 + i], xBuf[5 - i])); + mOut |= FASTABS(yLo); + y[(9 + i) * m_NBANDS] = yLo; + yLo = (xPrevWin[12 + i] << 2) + + (MULSHIFT32(wp[6 + i], xBuf[2 - i]) + + MULSHIFT32(wp[0 + i], xBuf[(6 + 3) + i])); + mOut |= FASTABS(yLo); + y[(12 + i) * m_NBANDS] = yLo; + yLo = (xPrevWin[15 + i] << 2) + + (MULSHIFT32(wp[9 + i], xBuf[0 + i]) + + MULSHIFT32(wp[3 + i], xBuf[(6 + 5) - i])); + mOut |= FASTABS(yLo); + y[(15 + i) * m_NBANDS] = yLo; + } + + /* save previous (unwindowed) for overlap - only need samples 6-8, 12-17 */ + for (i = 6; i < 9; i++) + *xPrev++ = xBuf[i] >> 2; + for (i = 12; i < 18; i++) + *xPrev++ = xBuf[i] >> 2; + + xPrev -= 9; + mOut |= FreqInvertRescale(y, xPrev, blockIdx, es); + + return mOut; +} + +/*********************************************************************************************************************** + * Function: HybridTransform + * + * Description: IMDCT's, windowing, and overlap-add on long/short/mixed blocks + * + * Inputs: vector of input coefficients, length = nBlocksTotal * 18) + * vector of overlap samples from last time, length = nBlocksPrev * 9) + * buffer for output samples, length = MAXNSAMP + * SideInfoSub struct for this granule/channel + * BlockCount struct with necessary info + * number of non-zero input and overlap blocks + * number of long blocks in input vector (rest assumed to be short blocks) + * number of blocks which use long window (type) 0 in case of mixed block + * (bc->currWinSwitch, 0 for non-mixed blocks) + * + * Outputs: transformed, windowed, and overlapped sample buffer + * does frequency inversion on odd blocks + * updated buffer of samples for overlap + * + * Return: number of non-zero IMDCT blocks calculated in this call + * (including overlap-add) + **********************************************************************************************************************/ +int HybridTransform(int *xCurr, int *xPrev, int y[m_BLOCK_SIZE][m_NBANDS], SideInfoSub_t *sis, BlockCount_t *bc){ + int xPrevWin[18], currWinIdx, prevWinIdx; + int i, j, nBlocksOut, nonZero, mOut; + int fiBit, xp; + + assert(bc->nBlocksLong <= m_NBANDS); + assert(bc->nBlocksTotal <= m_NBANDS); + assert(bc->nBlocksPrev <= m_NBANDS); + + mOut = 0; + + /* do long blocks, if any */ + for (i = 0; i < bc->nBlocksLong; i++) { + /* currWinIdx picks the right window for long blocks (if mixed, long blocks use window type 0) */ + currWinIdx = sis->blockType; + if (sis->mixedBlock && i < bc->currWinSwitch) + currWinIdx = 0; + + prevWinIdx = bc->prevType; + if (i < bc->prevWinSwitch) + prevWinIdx = 0; + + /* do 36-point IMDCT, including windowing and overlap-add */ + mOut |= IMDCT36(xCurr, xPrev, &(y[0][i]), currWinIdx, prevWinIdx, i, + bc->gbIn); + xCurr += 18; + xPrev += 9; + } + + /* do short blocks (if any) */ + for (; i < bc->nBlocksTotal; i++) { + assert(sis->blockType == 2); + + prevWinIdx = bc->prevType; + if (i < bc->prevWinSwitch) + prevWinIdx = 0; + + mOut |= IMDCT12x3(xCurr, xPrev, &(y[0][i]), prevWinIdx, i, bc->gbIn); + xCurr += 18; + xPrev += 9; + } + nBlocksOut = i; + + /* window and overlap prev if prev longer that current */ + for (; i < bc->nBlocksPrev; i++) { + prevWinIdx = bc->prevType; + if (i < bc->prevWinSwitch) + prevWinIdx = 0; + WinPrevious(xPrev, xPrevWin, prevWinIdx); + + nonZero = 0; + fiBit = i << 31; + for (j = 0; j < 9; j++) { + xp = xPrevWin[2 * j + 0] << 2; /* << 2 temp for scaling */ + nonZero |= xp; + y[2 * j + 0][i] = xp; + mOut |= FASTABS(xp); + + /* frequency inversion on odd blocks/odd samples (flip sign if i odd, j odd) */ + xp = xPrevWin[2 * j + 1] << 2; + xp = (xp ^ (fiBit >> 31)) + (i & 0x01); + nonZero |= xp; + y[2 * j + 1][i] = xp; + mOut |= FASTABS(xp); + + xPrev[j] = 0; + } + xPrev += 9; + if (nonZero) + nBlocksOut = i; + } + + /* clear rest of blocks */ + for (; i < 32; i++) { + for (j = 0; j < 18; j++) + y[j][i] = 0; + } + + bc->gbOut = CLZ(mOut) - 1; + + return nBlocksOut; +} + +/*********************************************************************************************************************** + * Function: IMDCT + * + * Description: do alias reduction, inverse MDCT, overlap-add, and frequency inversion + * + * Inputs: MP3DecInfo structure filled by UnpackFrameHeader(), UnpackSideInfo(), + * UnpackScaleFactors(), and DecodeHuffman() (for this granule, channel) + * includes PCM samples in overBuf (from last call to IMDCT) for OLA + * index of current granule and channel + * + * Outputs: PCM samples in outBuf, for input to subband transform + * PCM samples in overBuf, for OLA next time + * updated hi->nonZeroBound index for this channel + * + * Return: 0 on success, -1 if null input pointers + **********************************************************************************************************************/ +// a bit faster in RAM +/*__attribute__ ((section (".data")))*/ +int IMDCT( int gr, int ch) { + int nBfly, blockCutoff; + BlockCount_t bc; + + /* m_SideInfo is an array of up to 4 structs, stored as gr0ch0, gr0ch1, gr1ch0, gr1ch1 */ + /* anti-aliasing done on whole long blocks only + * for mixed blocks, nBfly always 1, except 3 for 8 kHz MPEG 2.5 (see sfBandTab) + * nLongBlocks = number of blocks with (possibly) non-zero power + * nBfly = number of butterflies to do (nLongBlocks - 1, unless no long blocks) + */ + blockCutoff = m_SFBandTable.l[(m_MPEGVersion == MPEG1 ? 8 : 6)] / 18; /* same as 3* num short sfb's in spec */ + if (m_SideInfoSub[gr][ch].blockType != 2) { + /* all long transforms */ + int x=(m_HuffmanInfo->nonZeroBound[ch] + 7) / 18 + 1; + bc.nBlocksLong=(x<32 ? x : 32); + //bc.nBlocksLong = min((hi->nonZeroBound[ch] + 7) / 18 + 1, 32); + nBfly = bc.nBlocksLong - 1; + } else if (m_SideInfoSub[gr][ch].blockType == 2 && m_SideInfoSub[gr][ch].mixedBlock) { + /* mixed block - long transforms until cutoff, then short transforms */ + bc.nBlocksLong = blockCutoff; + nBfly = bc.nBlocksLong - 1; + } else { + /* all short transforms */ + bc.nBlocksLong = 0; + nBfly = 0; + } + + AntiAlias(m_HuffmanInfo->huffDecBuf[ch], nBfly); + int x=m_HuffmanInfo->nonZeroBound[ch]; + int y=nBfly * 18 + 8; + m_HuffmanInfo->nonZeroBound[ch]=(x>y ? x: y); + + assert(m_HuffmanInfo->nonZeroBound[ch] <= m_MAX_NSAMP); + + /* for readability, use a struct instead of passing a million parameters to HybridTransform() */ + bc.nBlocksTotal = (m_HuffmanInfo->nonZeroBound[ch] + 17) / 18; + bc.nBlocksPrev = m_IMDCTInfo->numPrevIMDCT[ch]; + bc.prevType = m_IMDCTInfo->prevType[ch]; + bc.prevWinSwitch = m_IMDCTInfo->prevWinSwitch[ch]; + /* where WINDOW switches (not nec. transform) */ + bc.currWinSwitch = (m_SideInfoSub[gr][ch].mixedBlock ? blockCutoff : 0); + bc.gbIn = m_HuffmanInfo->gb[ch]; + + m_IMDCTInfo->numPrevIMDCT[ch] = HybridTransform(m_HuffmanInfo->huffDecBuf[ch], m_IMDCTInfo->overBuf[ch], + m_IMDCTInfo->outBuf[ch], &m_SideInfoSub[gr][ch], &bc); + m_IMDCTInfo->prevType[ch] = m_SideInfoSub[gr][ch].blockType; + m_IMDCTInfo->prevWinSwitch[ch] = bc.currWinSwitch; /* 0 means not a mixed block (either all short or all long) */ + m_IMDCTInfo->gb[ch] = bc.gbOut; + + assert(m_IMDCTInfo->numPrevIMDCT[ch] <= m_NBANDS); + + /* output has gained 2 int bits */ + return 0; +} + +/*********************************************************************************************************************** + * S U B B A N D + **********************************************************************************************************************/ + +/*********************************************************************************************************************** + * Function: Subband + * + * Description: do subband transform on all the blocks in one granule, all channels + * + * Inputs: filled MP3DecInfo structure, after calling IMDCT for all channels + * vbuf[ch] and vindex[ch] must be preserved between calls + * + * Outputs: decoded PCM data, interleaved LRLRLR... if stereo + * + * Return: 0 on success, -1 if null input pointers + **********************************************************************************************************************/ +int Subband( short *pcmBuf) { + int b; + if (m_MP3DecInfo->nChans == 2) { + /* stereo */ + for (b = 0; b < m_BLOCK_SIZE; b++) { + FDCT32(m_IMDCTInfo->outBuf[0][b], m_SubbandInfo->vbuf + 0 * 32, m_SubbandInfo->vindex, + (b & 0x01), m_IMDCTInfo->gb[0]); + FDCT32(m_IMDCTInfo->outBuf[1][b], m_SubbandInfo->vbuf + 1 * 32, m_SubbandInfo->vindex, + (b & 0x01), m_IMDCTInfo->gb[1]); + PolyphaseStereo(pcmBuf, + m_SubbandInfo->vbuf + m_SubbandInfo->vindex + m_VBUF_LENGTH * (b & 0x01), + polyCoef); + m_SubbandInfo->vindex = (m_SubbandInfo->vindex - (b & 0x01)) & 7; + pcmBuf += (2 * m_NBANDS); + } + } else { + /* mono */ + for (b = 0; b < m_BLOCK_SIZE; b++) { + FDCT32(m_IMDCTInfo->outBuf[0][b], m_SubbandInfo->vbuf + 0 * 32, m_SubbandInfo->vindex, + (b & 0x01), m_IMDCTInfo->gb[0]); + PolyphaseMono(pcmBuf, + m_SubbandInfo->vbuf + m_SubbandInfo->vindex + m_VBUF_LENGTH * (b & 0x01), + polyCoef); + m_SubbandInfo->vindex = (m_SubbandInfo->vindex - (b & 0x01)) & 7; + pcmBuf += m_NBANDS; + } + } + + return 0; +} + +/*********************************************************************************************************************** + * D C T 3 2 + **********************************************************************************************************************/ + +/*********************************************************************************************************************** + * Function: FDCT32 + * + * Description: Ken's highly-optimized 32-point DCT (radix-4 + radix-8) + * + * Inputs: input buffer, length = 32 samples + * require at least 6 guard bits in input vector x to avoid possibility + * of overflow in internal calculations (see bbtest_imdct test app) + * buffer offset and oddblock flag for polyphase filter input buffer + * number of guard bits in input + * + * Outputs: output buffer, data copied and interleaved for polyphase filter + * no guarantees about number of guard bits in output + * + * Return: none + * + * Notes: number of muls = 4*8 + 12*4 = 80 + * final stage of DCT is hardcoded to shuffle data into the proper order + * for the polyphase filterbank + * fully unrolled stage 1, for max precision (scale the 1/cos() factors + * differently, depending on magnitude) + * guard bit analysis verified by exhaustive testing of all 2^32 + * combinations of max pos/max neg values in x[] + **********************************************************************************************************************/ +#define D32FP(i, s1, s2) { \ + a0 = buf[i]; a3 = buf[31-i]; \ + a1 = buf[15-i]; a2 = buf[16+i]; \ + b0 = a0 + a3; b3 = MULSHIFT32(*cptr++, a0 - a3) << 1; \ + b1 = a1 + a2; b2 = MULSHIFT32(*cptr++, a1 - a2) << (s1); \ + buf[i] = b0 + b1; buf[15-i] = MULSHIFT32(*cptr, b0 - b1) << (s2); \ + buf[16+i] = b2 + b3; buf[31-i] = MULSHIFT32(*cptr++, b3 - b2) << (s2); \ +} + +static const uint8_t FDCT32s1s2[16] = {5,3,3,2,2,1,1,1, 1,1,1,1,1,2,2,4}; + +void FDCT32(int *buf, int *dest, int offset, int oddBlock, int gb) { + int i, s, tmp, es; + const int *cptr = (const int*)m_dcttab; + int a0, a1, a2, a3, a4, a5, a6, a7; + int b0, b1, b2, b3, b4, b5, b6, b7; + int *d; + + /* scaling - ensure at least 6 guard bits for DCT + * (in practice this is already true 99% of time, so this code is + * almost never triggered) + */ + es = 0; + if (gb < 6) { + es = 6 - gb; + for (i = 0; i < 32; i++) + buf[i] >>= es; + } + + /* first pass */ + for (unsigned i=0; i < 8; i++) { + D32FP(i, FDCT32s1s2[0 + i], FDCT32s1s2[8 + i]); + } + + /* second pass */ + for (i = 4; i > 0; i--) { + a0 = buf[0]; a7 = buf[7]; a3 = buf[3]; a4 = buf[4]; + b0 = a0 + a7; b7 = MULSHIFT32(*cptr++, a0 - a7) << 1; + b3 = a3 + a4; b4 = MULSHIFT32(*cptr++, a3 - a4) << 3; + a0 = b0 + b3; a3 = MULSHIFT32(*cptr, b0 - b3) << 1; + a4 = b4 + b7; a7 = MULSHIFT32(*cptr++, b7 - b4) << 1; + + a1 = buf[1]; a6 = buf[6]; a2 = buf[2]; a5 = buf[5]; + b1 = a1 + a6; b6 = MULSHIFT32(*cptr++, a1 - a6) << 1; + b2 = a2 + a5; b5 = MULSHIFT32(*cptr++, a2 - a5) << 1; + a1 = b1 + b2; a2 = MULSHIFT32(*cptr, b1 - b2) << 2; + a5 = b5 + b6; a6 = MULSHIFT32(*cptr++, b6 - b5) << 2; + + b0 = a0 + a1; b1 = MULSHIFT32(m_COS4_0, a0 - a1) << 1; + b2 = a2 + a3; b3 = MULSHIFT32(m_COS4_0, a3 - a2) << 1; + buf[0] = b0; buf[1] = b1; + buf[2] = b2 + b3; buf[3] = b3; + + b4 = a4 + a5; b5 = MULSHIFT32(m_COS4_0, a4 - a5) << 1; + b6 = a6 + a7; b7 = MULSHIFT32(m_COS4_0, a7 - a6) << 1; + b6 += b7; + buf[4] = b4 + b6; buf[5] = b5 + b7; + buf[6] = b5 + b6; buf[7] = b7; + + buf += 8; + } + buf -= 32; /* reset */ + + /* sample 0 - always delayed one block */ + d = dest + 64*16 + ((offset - oddBlock) & 7) + (oddBlock ? 0 : m_VBUF_LENGTH); + s = buf[ 0]; d[0] = d[8] = s; + + /* samples 16 to 31 */ + d = dest + offset + (oddBlock ? m_VBUF_LENGTH : 0); + + s = buf[ 1]; d[0] = d[8] = s; d += 64; + + tmp = buf[25] + buf[29]; + s = buf[17] + tmp; d[0] = d[8] = s; d += 64; + s = buf[ 9] + buf[13]; d[0] = d[8] = s; d += 64; + s = buf[21] + tmp; d[0] = d[8] = s; d += 64; + + tmp = buf[29] + buf[27]; + s = buf[ 5]; d[0] = d[8] = s; d += 64; + s = buf[21] + tmp; d[0] = d[8] = s; d += 64; + s = buf[13] + buf[11]; d[0] = d[8] = s; d += 64; + s = buf[19] + tmp; d[0] = d[8] = s; d += 64; + + tmp = buf[27] + buf[31]; + s = buf[ 3]; d[0] = d[8] = s; d += 64; + s = buf[19] + tmp; d[0] = d[8] = s; d += 64; + s = buf[11] + buf[15]; d[0] = d[8] = s; d += 64; + s = buf[23] + tmp; d[0] = d[8] = s; d += 64; + + tmp = buf[31]; + s = buf[ 7]; d[0] = d[8] = s; d += 64; + s = buf[23] + tmp; d[0] = d[8] = s; d += 64; + s = buf[15]; d[0] = d[8] = s; d += 64; + s = tmp; d[0] = d[8] = s; + + /* samples 16 to 1 (sample 16 used again) */ + d = dest + 16 + ((offset - oddBlock) & 7) + (oddBlock ? 0 : m_VBUF_LENGTH); + + s = buf[ 1]; d[0] = d[8] = s; d += 64; + + tmp = buf[30] + buf[25]; + s = buf[17] + tmp; d[0] = d[8] = s; d += 64; + s = buf[14] + buf[ 9]; d[0] = d[8] = s; d += 64; + s = buf[22] + tmp; d[0] = d[8] = s; d += 64; + s = buf[ 6]; d[0] = d[8] = s; d += 64; + + tmp = buf[26] + buf[30]; + s = buf[22] + tmp; d[0] = d[8] = s; d += 64; + s = buf[10] + buf[14]; d[0] = d[8] = s; d += 64; + s = buf[18] + tmp; d[0] = d[8] = s; d += 64; + s = buf[ 2]; d[0] = d[8] = s; d += 64; + + tmp = buf[28] + buf[26]; + s = buf[18] + tmp; d[0] = d[8] = s; d += 64; + s = buf[12] + buf[10]; d[0] = d[8] = s; d += 64; + s = buf[20] + tmp; d[0] = d[8] = s; d += 64; + s = buf[ 4]; d[0] = d[8] = s; d += 64; + + tmp = buf[24] + buf[28]; + s = buf[20] + tmp; d[0] = d[8] = s; d += 64; + s = buf[ 8] + buf[12]; d[0] = d[8] = s; d += 64; + s = buf[16] + tmp; d[0] = d[8] = s; + + /* this is so rarely invoked that it's not worth making two versions of the output + * shuffle code (one for no shift, one for clip + variable shift) like in IMDCT + * here we just load, clip, shift, and store on the rare instances that es != 0 + */ + if (es) { + d = dest + 64*16 + ((offset - oddBlock) & 7) + (oddBlock ? 0 : m_VBUF_LENGTH); + s = d[0]; CLIP_2N(s, (31 - es)); d[0] = d[8] = (s << es); + + d = dest + offset + (oddBlock ? m_VBUF_LENGTH : 0); + for (i = 16; i <= 31; i++) { + s = d[0]; CLIP_2N(s, (31 - es)); d[0] = d[8] = (s << es); d += 64; + } + + d = dest + 16 + ((offset - oddBlock) & 7) + (oddBlock ? 0 : m_VBUF_LENGTH); + for (i = 15; i >= 0; i--) { + s = d[0]; CLIP_2N(s, (31 - es)); d[0] = d[8] = (s << es); d += 64; + } + } +} + +/*********************************************************************************************************************** + * P O L Y P H A S E + **********************************************************************************************************************/ +inline +short ClipToShort(int x, int fracBits){ + + /* assumes you've already rounded (x += (1 << (fracBits-1))) */ + x >>= fracBits; + +#ifndef __XTENSA__ + /* Ken's trick: clips to [-32768, 32767] */ + //ok vor generic case (fb) + int sign = x >> 31; + if (sign != (x >> 15)) + x = sign ^ ((1 << 15) - 1); + + return (short)x; +#else + //this is better on xtensa (fb) + asm ("clamps %0, %1, 15" : "=a" (x) : "a" (x) : ); + return x; +#endif +} +/*********************************************************************************************************************** + * Function: PolyphaseMono + * + * Description: filter one subband and produce 32 output PCM samples for one channel + * + * Inputs: pointer to PCM output buffer + * number of "extra shifts" (vbuf format = Q(DQ_FRACBITS_OUT-2)) + * pointer to start of vbuf (preserved from last call) + * start of filter coefficient table (in proper, shuffled order) + * no minimum number of guard bits is required for input vbuf + * (see additional scaling comments below) + * + * Outputs: 32 samples of one channel of decoded PCM data, (i.e. Q16.0) + * + * Return: none + **********************************************************************************************************************/ +void PolyphaseMono(short *pcm, int *vbuf, const uint32_t *coefBase){ + int i; + const uint32_t *coef; + int *vb1; + int vLo, vHi, c1, c2; + uint64_t sum1L, sum2L, rndVal; + + rndVal = (uint64_t)( 1ULL << ((m_DQ_FRACBITS_OUT - 2 - 2 - 15) - 1 + (32 - m_CSHIFT)) ); + + /* special case, output sample 0 */ + coef = coefBase; + vb1 = vbuf; + sum1L = rndVal; + for(int j=0; j<8; j++){ + c1=*coef; coef++; c2=*coef; coef++; vLo=*(vb1+(j)); vHi=*(vb1+(23-(j))); // 0...7 + sum1L=MADD64(sum1L, vLo, c1); sum1L=MADD64(sum1L, vHi, -c2); + } + *(pcm + 0) = ClipToShort((int)SAR64(sum1L, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15); + + /* special case, output sample 16 */ + coef = coefBase + 256; + vb1 = vbuf + 64*16; + sum1L = rndVal; + for(int j=0; j<8; j++){ + c1=*coef; coef++; vLo=*(vb1+(j)); sum1L = MADD64(sum1L, vLo, c1); // 0...7 + } + *(pcm + 16) = ClipToShort((int)SAR64(sum1L, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15); + + /* main convolution loop: sum1L = samples 1, 2, 3, ... 15 sum2L = samples 31, 30, ... 17 */ + coef = coefBase + 16; + vb1 = vbuf + 64; + pcm++; + + /* right now, the compiler creates bad asm from this... */ + for (i = 15; i > 0; i--) { + sum1L = sum2L = rndVal; + for(int j=0; j<8; j++){ + c1=*coef; coef++; c2=*coef; coef++; vLo=*(vb1+(j)); vHi = *(vb1+(23-(j))); + sum1L=MADD64(sum1L, vLo, c1); sum2L = MADD64(sum2L, vLo, c2); + sum1L=MADD64(sum1L, vHi, -c2); sum2L = MADD64(sum2L, vHi, c1); + } + vb1 += 64; + *(pcm) = ClipToShort((int)SAR64(sum1L, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15); + *(pcm + 2*i) = ClipToShort((int)SAR64(sum2L, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15); + pcm++; + } +} +/*********************************************************************************************************************** + * Function: PolyphaseStereo + * + * Description: filter one subband and produce 32 output PCM samples for each channel + * + * Inputs: pointer to PCM output buffer + * number of "extra shifts" (vbuf format = Q(DQ_FRACBITS_OUT-2)) + * pointer to start of vbuf (preserved from last call) + * start of filter coefficient table (in proper, shuffled order) + * no minimum number of guard bits is required for input vbuf + * (see additional scaling comments below) + * + * Outputs: 32 samples of two channels of decoded PCM data, (i.e. Q16.0) + * + * Return: none + * + * Notes: interleaves PCM samples LRLRLR... + **********************************************************************************************************************/ +void PolyphaseStereo(short *pcm, int *vbuf, const uint32_t *coefBase){ + int i; + const uint32_t *coef; + int *vb1; + int vLo, vHi, c1, c2; + uint64_t sum1L, sum2L, sum1R, sum2R, rndVal; + + rndVal = (uint64_t)( 1 << ((m_DQ_FRACBITS_OUT - 2 - 2 - 15) - 1 + (32 - m_CSHIFT)) ); + + /* special case, output sample 0 */ + coef = coefBase; + vb1 = vbuf; + sum1L = sum1R = rndVal; + + for(int j=0; j<8; j++){ + c1=*coef; coef++; c2=*coef; coef++; vLo=*(vb1+(j)); vHi = *(vb1+(23-(j))); + sum1L=MADD64(sum1L, vLo, c1); sum1L=MADD64(sum1L, vHi, -c2); + vLo=*(vb1+32+(j)); vHi=*(vb1+32+(23-(j))); + sum1R=MADD64(sum1R, vLo, c1); sum1R=MADD64(sum1R, vHi, -c2); \ + } + *(pcm + 0) = ClipToShort((int)SAR64(sum1L, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15); + *(pcm + 1) = ClipToShort((int)SAR64(sum1R, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15); + + /* special case, output sample 16 */ + coef = coefBase + 256; + vb1 = vbuf + 64*16; + sum1L = sum1R = rndVal; + + for(int j=0; j<8; j++){ + c1=*coef; coef++; vLo = *(vb1+(j)); sum1L = MADD64(sum1L, vLo, c1); + vLo = *(vb1+32+(j)); sum1R = MADD64(sum1R, vLo, c1); + } + *(pcm + 2*16 + 0) = ClipToShort((int)SAR64(sum1L, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15); + *(pcm + 2*16 + 1) = ClipToShort((int)SAR64(sum1R, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15); + + /* main convolution loop: sum1L = samples 1, 2, 3, ... 15 sum2L = samples 31, 30, ... 17 */ + coef = coefBase + 16; + vb1 = vbuf + 64; + pcm += 2; + + /* right now, the compiler creates bad asm from this... */ + for (i = 15; i > 0; i--) { + sum1L = sum2L = rndVal; + sum1R = sum2R = rndVal; + + for(int j=0; j<8; j++){ + c1=*coef; coef++; c2=*coef; coef++; vLo=*(vb1+(j)); vHi = *(vb1+(23-(j))); + sum1L=MADD64(sum1L, vLo, c1); sum2L=MADD64(sum2L, vLo, c2); + sum1L=MADD64(sum1L, vHi, -c2); sum2L=MADD64(sum2L, vHi, c1); + vLo=*(vb1+32+(j)); vHi=*(vb1+32+(23-(j))); + sum1R=MADD64(sum1R, vLo, c1); sum2R=MADD64(sum2R, vLo, c2); + sum1R=MADD64(sum1R, vHi, -c2); sum2R=MADD64(sum2R, vHi, c1); + } + vb1 += 64; + *(pcm + 0) = ClipToShort((int)SAR64(sum1L, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15); + *(pcm + 1) = ClipToShort((int)SAR64(sum1R, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15); + *(pcm + 2*2*i + 0) = ClipToShort((int)SAR64(sum2L, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15); + *(pcm + 2*2*i + 1) = ClipToShort((int)SAR64(sum2R, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15); + pcm += 2; + } +} diff --git a/yoRadio/src/audioI2S/mp3_decoder/mp3_decoder.h b/yoRadio/src/audioI2S/mp3_decoder/mp3_decoder.h new file mode 100644 index 0000000..b202147 --- /dev/null +++ b/yoRadio/src/audioI2S/mp3_decoder/mp3_decoder.h @@ -0,0 +1,513 @@ +// based om helix mp3 decoder +#pragma once + +#include "Arduino.h" +#include "assert.h" + +static const uint8_t m_HUFF_PAIRTABS =32; +static const uint8_t m_BLOCK_SIZE =18; +static const uint8_t m_NBANDS =32; +static const uint8_t m_MAX_REORDER_SAMPS =(192-126)*3; // largest critical band for short blocks (see sfBandTable) +static const uint16_t m_VBUF_LENGTH =17*2* m_NBANDS; // for double-sized vbuf FIFO +static const uint8_t m_MAX_SCFBD =4; // max scalefactor bands per channel +static const uint16_t m_MAINBUF_SIZE =1940; +static const uint8_t m_MAX_NGRAN =2; // max granules +static const uint8_t m_MAX_NCHAN =2; // max channels +static const uint16_t m_MAX_NSAMP =576; // max samples per channel, per granule + +enum { + ERR_MP3_NONE = 0, + ERR_MP3_INDATA_UNDERFLOW = -1, + ERR_MP3_MAINDATA_UNDERFLOW = -2, + ERR_MP3_FREE_BITRATE_SYNC = -3, + ERR_MP3_OUT_OF_MEMORY = -4, + ERR_MP3_NULL_POINTER = -5, + ERR_MP3_INVALID_FRAMEHEADER = -6, + ERR_MP3_INVALID_SIDEINFO = -7, + ERR_MP3_INVALID_SCALEFACT = -8, + ERR_MP3_INVALID_HUFFCODES = -9, + ERR_MP3_INVALID_DEQUANTIZE = -10, + ERR_MP3_INVALID_IMDCT = -11, + ERR_MP3_INVALID_SUBBAND = -12, + + ERR_UNKNOWN = -9999 +}; + +typedef struct MP3FrameInfo { + int bitrate; + int nChans; + int samprate; + int bitsPerSample; + int outputSamps; + int layer; + int version; +} MP3FrameInfo_t; + +typedef struct SFBandTable { + int/*short*/ l[23]; + int/*short*/ s[14]; +} SFBandTable_t; + +typedef struct BitStreamInfo { + unsigned char *bytePtr; + unsigned int iCache; + int cachedBits; + int nBytes; +} BitStreamInfo_t; + +typedef enum { /* map these to the corresponding 2-bit values in the frame header */ + Stereo = 0x00, /* two independent channels, but L and R frames might have different # of bits */ + Joint = 0x01, /* coupled channels - layer III: mix of M-S and intensity, Layers I/II: intensity and direct coding only */ + Dual = 0x02, /* two independent channels, L and R always have exactly 1/2 the total bitrate */ + Mono = 0x03 /* one channel */ +} StereoMode_t; + +typedef enum { /* map to 0,1,2 to make table indexing easier */ + MPEG1 = 0, + MPEG2 = 1, + MPEG25 = 2 +} MPEGVersion_t; + +typedef struct FrameHeader { + int layer; /* layer index (1, 2, or 3) */ + int crc; /* CRC flag: 0 = disabled, 1 = enabled */ + int brIdx; /* bitrate index (0 - 15) */ + int srIdx; /* sample rate index (0 - 2) */ + int paddingBit; /* padding flag: 0 = no padding, 1 = single pad byte */ + int privateBit; /* unused */ + int modeExt; /* used to decipher joint stereo mode */ + int copyFlag; /* copyright flag: 0 = no, 1 = yes */ + int origFlag; /* original flag: 0 = copy, 1 = original */ + int emphasis; /* deemphasis mode */ + int CRCWord; /* CRC word (16 bits, 0 if crc not enabled) */ +} FrameHeader_t; + +typedef struct SideInfoSub { + int part23Length; /* number of bits in main data */ + int nBigvals; /* 2x this = first set of Huffman cw's (maximum amplitude can be > 1) */ + int globalGain; /* overall gain for dequantizer */ + int sfCompress; /* unpacked to figure out number of bits in scale factors */ + int winSwitchFlag; /* window switching flag */ + int blockType; /* block type */ + int mixedBlock; /* 0 = regular block (all short or long), 1 = mixed block */ + int tableSelect[3]; /* index of Huffman tables for the big values regions */ + int subBlockGain[3]; /* subblock gain offset, relative to global gain */ + int region0Count; /* 1+region0Count = num scale factor bands in first region of bigvals */ + int region1Count; /* 1+region1Count = num scale factor bands in second region of bigvals */ + int preFlag; /* for optional high frequency boost */ + int sfactScale; /* scaling of the scalefactors */ + int count1TableSelect; /* index of Huffman table for quad codewords */ +} SideInfoSub_t; + +typedef struct SideInfo { + int mainDataBegin; + int privateBits; + int scfsi[m_MAX_NCHAN][m_MAX_SCFBD]; /* 4 scalefactor bands per channel */ +} SideInfo_t; + +typedef struct { + int cbType; /* pure long = 0, pure short = 1, mixed = 2 */ + int cbEndS[3]; /* number nonzero short cb's, per subbblock */ + int cbEndSMax; /* max of cbEndS[] */ + int cbEndL; /* number nonzero long cb's */ +} CriticalBandInfo_t; + +typedef struct DequantInfo { + int workBuf[m_MAX_REORDER_SAMPS]; /* workbuf for reordering short blocks */ +} DequantInfo_t; + +typedef struct HuffmanInfo { + int huffDecBuf[m_MAX_NCHAN][m_MAX_NSAMP]; /* used both for decoded Huffman values and dequantized coefficients */ + int nonZeroBound[m_MAX_NCHAN]; /* number of coeffs in huffDecBuf[ch] which can be > 0 */ + int gb[m_MAX_NCHAN]; /* minimum number of guard bits in huffDecBuf[ch] */ +} HuffmanInfo_t; + +typedef enum HuffTabType { + noBits, + oneShot, + loopNoLinbits, + loopLinbits, + quadA, + quadB, + invalidTab +} HuffTabType_t; + +typedef struct HuffTabLookup { + int linBits; + int tabType; /*HuffTabType*/ +} HuffTabLookup_t; + +typedef struct IMDCTInfo { + int outBuf[m_MAX_NCHAN][m_BLOCK_SIZE][m_NBANDS]; /* output of IMDCT */ + int overBuf[m_MAX_NCHAN][m_MAX_NSAMP / 2]; /* overlap-add buffer (by symmetry, only need 1/2 size) */ + int numPrevIMDCT[m_MAX_NCHAN]; /* how many IMDCT's calculated in this channel on prev. granule */ + int prevType[m_MAX_NCHAN]; + int prevWinSwitch[m_MAX_NCHAN]; + int gb[m_MAX_NCHAN]; +} IMDCTInfo_t; + +typedef struct BlockCount { + int nBlocksLong; + int nBlocksTotal; + int nBlocksPrev; + int prevType; + int prevWinSwitch; + int currWinSwitch; + int gbIn; + int gbOut; +} BlockCount_t; + +typedef struct ScaleFactorInfoSub { /* max bits in scalefactors = 5, so use char's to save space */ + char l[23]; /* [band] */ + char s[13][3]; /* [band][window] */ +} ScaleFactorInfoSub_t; + +typedef struct ScaleFactorJS { /* used in MPEG 2, 2.5 intensity (joint) stereo only */ + int intensityScale; + int slen[4]; + int nr[4]; +} ScaleFactorJS_t; + +/* NOTE - could get by with smaller vbuf if memory is more important than speed + * (in Subband, instead of replicating each block in FDCT32 you would do a memmove on the + * last 15 blocks to shift them down one, a hardware style FIFO) + */ +typedef struct SubbandInfo { + int vbuf[m_MAX_NCHAN * m_VBUF_LENGTH]; /* vbuf for fast DCT-based synthesis PQMF - double size for speed (no modulo indexing) */ + int vindex; /* internal index for tracking position in vbuf */ +} SubbandInfo_t; + +typedef struct MP3DecInfo { + /* buffer which must be large enough to hold largest possible main_data section */ + unsigned char mainBuf[m_MAINBUF_SIZE]; + /* special info for "free" bitrate files */ + int freeBitrateFlag; + int freeBitrateSlots; + /* user-accessible info */ + int bitrate; + int nChans; + int samprate; + int nGrans; /* granules per frame */ + int nGranSamps; /* samples per granule */ + int nSlots; + int layer; + + int mainDataBegin; + int mainDataBytes; + int part23Length[m_MAX_NGRAN][m_MAX_NCHAN]; +} MP3DecInfo_t; + + + + +/* format = Q31 + * #define M_PI 3.14159265358979323846 + * double u = 2.0 * M_PI / 9.0; + * float c0 = sqrt(3.0) / 2.0; + * float c1 = cos(u); + * float c2 = cos(2*u); + * float c3 = sin(u); + * float c4 = sin(2*u); + */ + +const int c9_0 = 0x6ed9eba1; +const int c9_1 = 0x620dbe8b; +const int c9_2 = 0x163a1a7e; +const int c9_3 = 0x5246dd49; +const int c9_4 = 0x7e0e2e32; + + + +const int c3_0 = 0x6ed9eba1; /* format = Q31, cos(pi/6) */ +const int c6[3] = { 0x7ba3751d, 0x5a82799a, 0x2120fb83 }; /* format = Q31, cos(((0:2) + 0.5) * (pi/6)) */ + +/* format = Q31 + * cos(((0:8) + 0.5) * (pi/18)) + */ +const uint32_t c18[9] = { 0x7f834ed0, 0x7ba3751d, 0x7401e4c1, 0x68d9f964, 0x5a82799a, 0x496af3e2, 0x36185aee, 0x2120fb83, 0x0b27eb5c}; + +/* scale factor lengths (num bits) */ +const char m_SFLenTab[16][2] = { {0, 0}, {0, 1}, {0, 2}, {0, 3}, {3, 0}, {1, 1}, {1, 2}, {1, 3}, + {2, 1}, {2, 2}, {2, 3}, {3, 1}, {3, 2}, {3, 3}, {4, 2}, {4, 3}}; + +/* NRTab[size + 3*is_right][block type][partition] + * block type index: 0 = (bt0,bt1,bt3), 1 = bt2 non-mixed, 2 = bt2 mixed + * partition: scale factor groups (sfb1 through sfb4) + * for block type = 2 (mixed or non-mixed) / by 3 is rolled into this table + * (for 3 short blocks per long block) + * see 2.4.3.2 in MPEG 2 (low sample rate) spec + * stuff rolled into this table: + * NRTab[x][1][y] --> (NRTab[x][1][y]) / 3 + * NRTab[x][2][>=1] --> (NRTab[x][2][>=1]) / 3 (first partition is long block) + */ +const char NRTab[6][3][4] = { + {{ 6, 5, 5, 5}, {3, 3, 3, 3}, {6, 3, 3, 3}}, + {{ 6, 5, 7, 3}, {3, 3, 4, 2}, {6, 3, 4, 2}}, + {{11, 10, 0, 0}, {6, 6, 0, 0}, {6, 3, 6, 0}}, + {{ 7, 7, 7, 0}, {4, 4, 4, 0}, {6, 5, 4, 0}}, + {{ 6, 6, 6, 3}, {4, 3, 3, 2}, {6, 4, 3, 2}}, + {{ 8, 8, 5, 0}, {5, 4, 3, 0}, {6, 6, 3, 0}} +}; + + + +/* optional pre-emphasis for high-frequency scale factor bands */ +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 }; + +/* pow(2,-i/4) for i=0..3, Q31 format */ +const int pow14[4] PROGMEM = { + 0x7fffffff, 0x6ba27e65, 0x5a82799a, 0x4c1bf829 +}; + + +/* + * Minimax polynomial approximation to pow(x, 4/3), over the range + * poly43lo: x = [0.5, 0.7071] + * poly43hi: x = [0.7071, 1.0] + * + * Relative error < 1E-7 + * Coefs are scaled by 4, 2, 1, 0.5, 0.25 + */ +const unsigned int poly43lo[5] PROGMEM = { 0x29a0bda9, 0xb02e4828, 0x5957aa1b, 0x236c498d, 0xff581859 }; +const unsigned int poly43hi[5] PROGMEM = { 0x10852163, 0xd333f6a4, 0x46e9408b, 0x27c2cef0, 0xfef577b4 }; + +/* pow(2, i*4/3) as exp and frac */ +const int pow2exp[8] PROGMEM = { 14, 13, 11, 10, 9, 7, 6, 5 }; + +const int pow2frac[8] PROGMEM = { + 0x6597fa94, 0x50a28be6, 0x7fffffff, 0x6597fa94, + 0x50a28be6, 0x7fffffff, 0x6597fa94, 0x50a28be6 +}; + +const uint16_t m_HUFF_OFFSET_01= 0; +const uint16_t m_HUFF_OFFSET_02= 9 + m_HUFF_OFFSET_01; +const uint16_t m_HUFF_OFFSET_03= 65 + m_HUFF_OFFSET_02; +const uint16_t m_HUFF_OFFSET_05= 65 + m_HUFF_OFFSET_03; +const uint16_t m_HUFF_OFFSET_06=257 + m_HUFF_OFFSET_05; +const uint16_t m_HUFF_OFFSET_07=129 + m_HUFF_OFFSET_06; +const uint16_t m_HUFF_OFFSET_08=110 + m_HUFF_OFFSET_07; +const uint16_t m_HUFF_OFFSET_09=280 + m_HUFF_OFFSET_08; +const uint16_t m_HUFF_OFFSET_10= 93 + m_HUFF_OFFSET_09; +const uint16_t m_HUFF_OFFSET_11=320 + m_HUFF_OFFSET_10; +const uint16_t m_HUFF_OFFSET_12=296 + m_HUFF_OFFSET_11; +const uint16_t m_HUFF_OFFSET_13=185 + m_HUFF_OFFSET_12; +const uint16_t m_HUFF_OFFSET_15=497 + m_HUFF_OFFSET_13; +const uint16_t m_HUFF_OFFSET_16=580 + m_HUFF_OFFSET_15; +const uint16_t m_HUFF_OFFSET_24=651 + m_HUFF_OFFSET_16; + +const int huffTabOffset[m_HUFF_PAIRTABS] PROGMEM = { + 0, m_HUFF_OFFSET_01, m_HUFF_OFFSET_02, m_HUFF_OFFSET_03, + 0, m_HUFF_OFFSET_05, m_HUFF_OFFSET_06, m_HUFF_OFFSET_07, + m_HUFF_OFFSET_08, m_HUFF_OFFSET_09, m_HUFF_OFFSET_10, m_HUFF_OFFSET_11, + m_HUFF_OFFSET_12, m_HUFF_OFFSET_13, 0, m_HUFF_OFFSET_15, + m_HUFF_OFFSET_16, m_HUFF_OFFSET_16, m_HUFF_OFFSET_16, m_HUFF_OFFSET_16, + m_HUFF_OFFSET_16, m_HUFF_OFFSET_16, m_HUFF_OFFSET_16, m_HUFF_OFFSET_16, + m_HUFF_OFFSET_24, m_HUFF_OFFSET_24, m_HUFF_OFFSET_24, m_HUFF_OFFSET_24, + m_HUFF_OFFSET_24, m_HUFF_OFFSET_24, m_HUFF_OFFSET_24, m_HUFF_OFFSET_24,}; + +const HuffTabLookup_t huffTabLookup[m_HUFF_PAIRTABS] PROGMEM = { + { 0, noBits }, + { 0, oneShot }, + { 0, oneShot }, + { 0, oneShot }, + { 0, invalidTab }, + { 0, oneShot }, + { 0, oneShot }, + { 0, loopNoLinbits }, + { 0, loopNoLinbits }, + { 0, loopNoLinbits }, + { 0, loopNoLinbits }, + { 0, loopNoLinbits }, + { 0, loopNoLinbits }, + { 0, loopNoLinbits }, + { 0, invalidTab }, + { 0, loopNoLinbits }, + { 1, loopLinbits }, + { 2, loopLinbits }, + { 3, loopLinbits }, + { 4, loopLinbits }, + { 6, loopLinbits }, + { 8, loopLinbits }, + { 10, loopLinbits }, + { 13, loopLinbits }, + { 4, loopLinbits }, + { 5, loopLinbits }, + { 6, loopLinbits }, + { 7, loopLinbits }, + { 8, loopLinbits }, + { 9, loopLinbits }, + { 11, loopLinbits }, + { 13, loopLinbits }, +}; + + +const int quadTabOffset[2] PROGMEM = {0, 64}; +const int quadTabMaxBits[2] PROGMEM = {6, 4}; + +/* indexing = [version][samplerate index] + * sample rate of frame (Hz) + */ +const int samplerateTab[3][3] PROGMEM = { + { 44100, 48000, 32000 }, /* MPEG-1 */ + { 22050, 24000, 16000 }, /* MPEG-2 */ + { 11025, 12000, 8000 }, /* MPEG-2.5 */ +}; + + + +/* indexing = [version][layer] + * number of samples in one frame (per channel) + */ +const int/*short*/samplesPerFrameTab[3][3] PROGMEM = { { 384, 1152, 1152 }, /* MPEG1 */ +{ 384, 1152, 576 }, /* MPEG2 */ +{ 384, 1152, 576 }, /* MPEG2.5 */ +}; + +/* layers 1, 2, 3 */ +const short bitsPerSlotTab[3] = { 32, 8, 8 }; + +/* indexing = [version][mono/stereo] + * number of bytes in side info section of bitstream + */ +const int/*short*/sideBytesTab[3][2] PROGMEM = { { 17, 32 }, /* MPEG-1: mono, stereo */ +{ 9, 17 }, /* MPEG-2: mono, stereo */ +{ 9, 17 }, /* MPEG-2.5: mono, stereo */ +}; + +/* indexing = [version][sampleRate][long (.l) or short (.s) block] + * sfBandTable[v][s].l[cb] = index of first bin in critical band cb (long blocks) + * sfBandTable[v][s].s[cb] = index of first bin in critical band cb (short blocks) + */ +const SFBandTable_t sfBandTable[3][3] PROGMEM = { + { /* MPEG-1 (44, 48, 32 kHz) */ + { {0, 4, 8, 12, 16, 20, 24, 30, 36, 44, 52, 62, 74, 90, 110, 134, 162, 196, 238, 288, 342, 418, 576 }, + {0, 4, 8, 12, 16, 22, 30, 40, 52, 66, 84, 106, 136, 192} }, + { {0, 4, 8, 12, 16, 20, 24, 30, 36, 42, 50, 60, 72, 88, 106, 128, 156, 190, 230, 276, 330, 384, 576 }, + {0, 4, 8, 12, 16, 22, 28, 38, 50, 64, 80, 100, 126, 192} }, + { {0, 4, 8, 12, 16, 20, 24, 30, 36, 44, 54, 66, 82, 102, 126, 156, 194, 240, 296, 364, 448, 550, 576 }, + {0, 4, 8, 12, 16, 22, 30, 42, 58, 78, 104, 138, 180, 192} } }, + { /* MPEG-2 (22, 24, 16 kHz) */ + { {0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576 }, + {0, 4, 8, 12, 18, 24, 32, 42, 56, 74, 100, 132, 174, 192} }, + { {0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 114, 136, 162, 194, 232, 278, 332, 394, 464, 540, 576 }, + {0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 136, 180, 192} }, + { {0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576 }, + {0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192} }, }, + { /* MPEG-2.5 (11, 12, 8 kHz) */ + { {0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576 }, + {0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192 } }, + { {0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576 }, + {0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192 } }, + { {0, 12, 24, 36, 48, 60, 72, 88, 108, 132, 160, 192, 232, 280, 336, 400, 476, 566, 568, 570, 572, 574, 576 }, + {0, 8, 16, 24, 36, 52, 72, 96, 124, 160, 162, 164, 166, 192 } }, }, +}; + + +/* indexing = [intensity scale on/off][left/right] + * format = Q30, range = [0.0, 1.414] + * + * illegal intensity position scalefactors (see comments on ISFMpeg1) + */ +const int ISFIIP[2][2] PROGMEM = { + {0x40000000, 0x00000000}, /* mid-side off */ + {0x40000000, 0x40000000}, /* mid-side on */ +}; + +const unsigned char uniqueIDTab[8] = {0x5f, 0x4b, 0x43, 0x5f, 0x5f, 0x4a, 0x52, 0x5f}; + +/* anti-alias coefficients - see spec Annex B, table 3-B.9 + * csa[0][i] = CSi, csa[1][i] = CAi + * format = Q31 + */ +const uint32_t csa[8][2] PROGMEM = { + {0x6dc253f0, 0xbe2500aa}, + {0x70dcebe4, 0xc39e4949}, + {0x798d6e73, 0xd7e33f4a}, + {0x7ddd40a7, 0xe8b71176}, + {0x7f6d20b7, 0xf3e4fe2f}, + {0x7fe47e40, 0xfac1a3c7}, + {0x7ffcb263, 0xfe2ebdc6}, + {0x7fffc694, 0xff86c25d}, +}; + +/* format = Q30, right shifted by 12 (sign bits only in top 12 - undo this when rounding to short) + * this is to enable early-terminating multiplies on ARM + * range = [-1.144287109, 1.144989014] + * max gain of filter (per output sample) ~= 2.731 + * + * new (properly sign-flipped) values + * - these actually are correct to 32 bits, (floating-pt coefficients in spec + * chosen such that only ~20 bits are required) + * + * Reordering - see table 3-B.3 in spec (appendix B) + * + * polyCoef[i] = + * D[ 0, 32, 64, ... 480], i = [ 0, 15] + * D[ 1, 33, 65, ... 481], i = [ 16, 31] + * D[ 2, 34, 66, ... 482], i = [ 32, 47] + * ... + * D[15, 47, 79, ... 495], i = [240,255] + * + * also exploits symmetry: D[i] = -D[512 - i], for i = [1, 255] + * + * polyCoef[256, 257, ... 263] are for special case of sample 16 (out of 0) + * see PolyphaseStereo() and PolyphaseMono() + */ + +// prototypes +bool MP3Decoder_AllocateBuffers(void); +void MP3Decoder_FreeBuffers(); +int MP3Decode( unsigned char *inbuf, int *bytesLeft, short *outbuf, int useSize); +void MP3GetLastFrameInfo(); +int MP3GetNextFrameInfo(unsigned char *buf); +int MP3FindSyncWord(unsigned char *buf, int nBytes); +int MP3GetSampRate(); +int MP3GetChannels(); +int MP3GetBitsPerSample(); +int MP3GetBitrate(); +int MP3GetOutputSamps(); + +//internally used +void MP3Decoder_ClearBuffer(void); +void PolyphaseMono(short *pcm, int *vbuf, const uint32_t *coefBase); +void PolyphaseStereo(short *pcm, int *vbuf, const uint32_t *coefBase); +void SetBitstreamPointer(BitStreamInfo_t *bsi, int nBytes, unsigned char *buf); +unsigned int GetBits(BitStreamInfo_t *bsi, int nBits); +int CalcBitsUsed(BitStreamInfo_t *bsi, unsigned char *startBuf, int startOffset); +int DequantChannel(int *sampleBuf, int *workBuf, int *nonZeroBound, SideInfoSub_t *sis, ScaleFactorInfoSub_t *sfis, CriticalBandInfo_t *cbi); +void MidSideProc(int x[m_MAX_NCHAN][m_MAX_NSAMP], int nSamps, int mOut[2]); +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]); +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]); +void FDCT32(int *x, int *d, int offset, int oddBlock, int gb);// __attribute__ ((section (".data"))); +void FreeBuffers(); +int CheckPadBit(); +int UnpackFrameHeader(unsigned char *buf); +int UnpackSideInfo(unsigned char *buf); +int DecodeHuffman( unsigned char *buf, int *bitOffset, int huffBlockBits, int gr, int ch); +int MP3Dequantize( int gr); +int IMDCT( int gr, int ch); +int UnpackScaleFactors( unsigned char *buf, int *bitOffset, int bitsAvail, int gr, int ch); +int Subband(short *pcmBuf); +short ClipToShort(int x, int fracBits); +void RefillBitstreamCache(BitStreamInfo_t *bsi); +void UnpackSFMPEG1(BitStreamInfo_t *bsi, SideInfoSub_t *sis, ScaleFactorInfoSub_t *sfis, int *scfsi, int gr, ScaleFactorInfoSub_t *sfisGr0); +void UnpackSFMPEG2(BitStreamInfo_t *bsi, SideInfoSub_t *sis, ScaleFactorInfoSub_t *sfis, int gr, int ch, int modeExt, ScaleFactorJS_t *sfjs); +int MP3FindFreeSync(unsigned char *buf, unsigned char firstFH[4], int nBytes); +void MP3ClearBadFrame( short *outbuf); +int DecodeHuffmanPairs(int *xy, int nVals, int tabIdx, int bitsLeft, unsigned char *buf, int bitOffset); +int DecodeHuffmanQuads(int *vwxy, int nVals, int tabIdx, int bitsLeft, unsigned char *buf, int bitOffset); +int DequantBlock(int *inbuf, int *outbuf, int num, int scale); +void AntiAlias(int *x, int nBfly); +void WinPrevious(int *xPrev, int *xPrevWin, int btPrev); +int FreqInvertRescale(int *y, int *xPrev, int blockIdx, int es); +void idct9(int *x); +int IMDCT36(int *xCurr, int *xPrev, int *y, int btCurr, int btPrev, int blockIdx, int gb); +void imdct12(int *x, int *out); +int IMDCT12x3(int *xCurr, int *xPrev, int *y, int btPrev, int blockIdx, int gb); +int HybridTransform(int *xCurr, int *xPrev, int y[m_BLOCK_SIZE][m_NBANDS], SideInfoSub_t *sis, BlockCount_t *bc); +inline uint64_t SAR64(uint64_t x, int n) {return x >> n;} +inline int MULSHIFT32(int x, int y) { int z; z = (uint64_t) x * (uint64_t) y >> 32; return z;} +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] */ +inline uint64_t xSAR64(uint64_t x, int n){return x >> n;} +inline int FASTABS(int x){ return __builtin_abs(x);} //xtensa has a fast abs instruction //fb +#define CLZ(x) __builtin_clz(x) //fb diff --git a/yoRadio/telnet.cpp b/yoRadio/telnet.cpp new file mode 100644 index 0000000..9c05de5 --- /dev/null +++ b/yoRadio/telnet.cpp @@ -0,0 +1,269 @@ +#include +#include "WiFi.h" + +#include "config.h" +#include "telnet.h" +#include "player.h" +#include "display.h" + +Telnet telnet; + +bool Telnet::_isIPSet(IPAddress ip) { + return ip.toString() == "0.0.0.0"; +} + +bool Telnet::begin() { + if (WiFi.status() == WL_CONNECTED || _isIPSet(WiFi.softAPIP())) { + server.begin(); + server.setNoDelay(true); + Serial.printf("Ready! Use 'telnet %s 23' to connect\n", WiFi.localIP().toString().c_str()); + return true; + } else { + return false; + } +} + +void Telnet::stop() { + server.stop(); +} + +void Telnet::emptyClientStream(WiFiClient client) { + client.flush(); + delay(50); + while (client.available()) { + client.read(); + } +} + +void Telnet::cleanupClients() { + for (int i = 0; i < MAX_TLN_CLIENTS; i++) { + if (!clients[i].connected()) { + if (clients[i]) { + Serial.printf("Client [%d] is %s\n", i, clients[i].connected() ? "connected" : "disconnected"); + clients[i].stop(); + } + } + } +} + +void Telnet::loop() { + uint8_t i; + if (WiFi.status() == WL_CONNECTED) { + if (server.hasClient()) { + for (i = 0; i < MAX_TLN_CLIENTS; i++) { + if (!clients[i] || !clients[i].connected()) { + if (clients[i]) { + clients[i].stop(); + } + clients[i] = server.available(); + if (!clients[i]) Serial.println("available broken"); + on_connect(clients[i].remoteIP().toString().c_str(), i); + clients[i].setNoDelay(true); + emptyClientStream(clients[i]); + break; + } + } + if (i >= MAX_TLN_CLIENTS) { + server.available().stop(); + } + } + for (i = 0; i < MAX_TLN_CLIENTS; i++) { + if (clients[i] && clients[i].connected() && clients[i].available()) { + String inputstr = clients[i].readStringUntil('\n'); + inputstr.trim(); + on_input(inputstr.c_str(), i); + } + } + } else { + for (i = 0; i < MAX_TLN_CLIENTS; i++) { + if (clients[i]) { + clients[i].stop(); + } + } + delay(1000); + } + yield(); +} + +void Telnet::print(const char *buf) { + for (int id = 0; id < MAX_TLN_CLIENTS; id++) { + if (clients[id] && clients[id].connected()) { + print(id, buf); + } + } + Serial.print(buf); +} + +void Telnet::print(byte id, const char *buf) { + if (clients[id] && clients[id].connected()) { + clients[id].print(buf); + } +} + +void Telnet::printf(const char *format, ...) { + char buf[MAX_PRINTF_LEN]; + va_list args; + va_start (args, format ); + vsnprintf(buf, MAX_PRINTF_LEN, format, args); + va_end (args); + for (int id = 0; id < MAX_TLN_CLIENTS; id++) { + if (clients[id] && clients[id].connected()) { + clients[id].print(buf); + } + } + Serial.print(buf); +} + +void Telnet::printf(byte id, const char *format, ...) { + if (clients[id] && clients[id].connected()) { + va_list argptr; + va_start(argptr, format); + char *szBuffer = 0; + const size_t nBufferLength = vsnprintf(szBuffer, 0, format, argptr) + 1; + if (nBufferLength == 1) return; + szBuffer = (char *) malloc(nBufferLength); + if (! szBuffer) return; + vsnprintf(szBuffer, nBufferLength, format, argptr); + va_end(argptr); + clients[id].print(szBuffer); + free(szBuffer); + } +} + +void Telnet::on_connect(const char* str, byte clientId) { + Serial.printf("Telnet: [%d] %s connected\n", clientId, str); + print(clientId, "\nWelcome to ёRadio!\n(Use ^] + q to disconnect.)\n> "); +} + +void Telnet::info() { + byte volume; + telnet.printf("##CLI.INFO#\n"); + char timeStringBuff[50]; + strftime(timeStringBuff, sizeof(timeStringBuff), "%Y-%m-%dT%H:%M:%S+03:00", &display.timeinfo); + telnet.printf("##SYS.DATE#: %s\n", timeStringBuff); //TODO timezone offset + telnet.printf("##CLI.NAMESET#: %d %s\n", config.store.lastStation, config.station.name); + if (player.mode == PLAYING) { + telnet.printf("##CLI.META#: %s\n", config.station.title); + } + telnet.printf("##CLI.VOL#: %d\n", config.store.volume); + if (player.mode == PLAYING) { + telnet.printf("##CLI.PLAYING#\n"); + } else { + telnet.printf("##CLI.STOPPED#\n"); + } + telnet.printf("> "); +} + +void Telnet::on_input(const char* str, byte clientId) { + if (strlen(str) == 0) return; + if (strcmp(str, "cli.prev") == 0 || strcmp(str, "prev") == 0) { + player.prev(); + return; + } + if (strcmp(str, "cli.next") == 0 || strcmp(str, "next") == 0) { + player.next(); + return; + } + if (strcmp(str, "cli.toggle") == 0 || strcmp(str, "toggle") == 0) { + player.toggle(); + return; + } + if (strcmp(str, "cli.stop") == 0 || strcmp(str, "stop") == 0) { + player.mode = STOPPED; + display.title("[stopped]"); + info(); + return; + } + if (strcmp(str, "cli.start") == 0 || strcmp(str, "start") == 0 || strcmp(str, "cli.play") == 0 || strcmp(str, "play") == 0) { + player.play(config.store.lastStation); + return; + } + if (strcmp(str, "sys.boot") == 0 || strcmp(str, "boot") == 0 || strcmp(str, "reboot") == 0) { + ESP.restart(); + return; + } + if (strcmp(str, "cli.vol") == 0 || strcmp(str, "vol") == 0) { + printf(clientId, "##CLI.VOL#: %d\n", config.store.volume); + return; + } + if (strcmp(str, "sys.date") == 0) { + char timeStringBuff[50]; + strftime(timeStringBuff, sizeof(timeStringBuff), "%Y-%m-%dT%H:%M:%S+03:00", &display.timeinfo); + telnet.printf("##SYS.DATE#: %s\n", timeStringBuff); //TODO timezone offset + return; + } + int volume; + if (sscanf(str, "vol(%d)", &volume) == 1 || sscanf(str, "cli.vol(\"%d\")", &volume) == 1 || sscanf(str, "vol %d", &volume) == 1) { + if (volume < 0) volume = 0; + if (volume > 254) volume = 254; + player.setVol(volume, false); + return; + } + + if (strcmp(str, "cli.audioinfo") == 0 || strcmp(str, "audioinfo") == 0) { + printf(clientId, "##CLI.AUDIOINFO#: %d\n", config.store.audioinfo>0); + return; + } + byte ainfo; + if (sscanf(str, "audioinfo(%d)", &ainfo) == 1 || sscanf(str, "cli.audioinfo(\"%d\")", &ainfo) == 1 || sscanf(str, "audioinfo %d", &ainfo) == 1) { + config.store.audioinfo = ainfo>0; + config.save(); + return; + } + if (strcmp(str, "cli.smartstart") == 0 || strcmp(str, "smartstart") == 0) { + printf(clientId, "##CLI.SMARTSTART#: %d\n", config.store.smartstart); + return; + } + byte sstart; + if (sscanf(str, "smartstart(%d)", &sstart) == 1 || sscanf(str, "cli.smartstart(\"%d\")", &sstart) == 1 || sscanf(str, "smartstart %d", &sstart) == 1) { + config.store.smartstart = sstart; + config.save(); + return; + } + if (strcmp(str, "cli.list") == 0 || strcmp(str, "list") == 0) { + printf(clientId, "#CLI.LIST#\n"); + File file = SPIFFS.open(PLAYLIST_PATH, "r"); + if (!file || file.isDirectory()) { + return; + } + char sName[BUFLEN], sUrl[BUFLEN]; + int sOvol; + byte c = 1; + while (file.available()) { + if (config.parseCSV(file.readStringUntil('\n').c_str(), sName, sUrl, sOvol)) { + printf(clientId, "#CLI.LISTNUM#: %*d: %s, %s\n", 3, c, sName, sUrl); + c++; + } + } + printf(clientId, "##CLI.LIST#\n"); + printf(clientId, "> "); + return; + } + if (strcmp(str, "cli.info") == 0 || strcmp(str, "info") == 0) { + printf(clientId, "##CLI.INFO#\n"); + char timeStringBuff[50]; + strftime(timeStringBuff, sizeof(timeStringBuff), "%Y-%m-%dT%H:%M:%S+03:00", &display.timeinfo); + printf(clientId, "##SYS.DATE#: %s\n", timeStringBuff); //TODO timezone offset + printf(clientId, "##CLI.NAMESET#: %d %s\n", config.store.lastStation, config.station.name); + if (player.mode == PLAYING) { + printf(clientId, "##CLI.META#: %s\n", config.station.title); + } + printf(clientId, "##CLI.VOL#: %d\n", config.store.volume); + if (player.mode == PLAYING) { + printf(clientId, "##CLI.PLAYING#\n"); + } else { + printf(clientId, "##CLI.STOPPED#\n"); + } + printf(clientId, "> "); + return; + } + + uint8_t sb; + if (sscanf(str, "play(%d)", &sb) == 1 || sscanf(str, "cli.play(\"%d\")", &sb) == 1 || sscanf(str, "play %d", &sb) == 1 ) { + if (sb < 1) sb = 1; + if (sb >= config.store.countStation) sb = config.store.countStation; + player.play(sb); + return; + } + telnet.printf(clientId, "unknown command: %s\n> ", str); +} diff --git a/yoRadio/telnet.h b/yoRadio/telnet.h new file mode 100644 index 0000000..5e3cb4e --- /dev/null +++ b/yoRadio/telnet.h @@ -0,0 +1,33 @@ +#ifndef telnet_h +#define telnet_h + +#include + +#define MAX_TLN_CLIENTS 5 +#define MAX_PRINTF_LEN BUFLEN+50 + +class Telnet { + public: + Telnet() {}; + bool begin(); + void loop(); + void stop(); + void print(byte id, const char *buf); + void print(const char *buf); + void printf(byte id, const char *format, ...); + void printf(const char *format, ...); + void cleanupClients(); + void info(); + protected: + WiFiServer server = WiFiServer(23); + WiFiClient clients[MAX_TLN_CLIENTS]; + void emptyClientStream(WiFiClient client); + void on_connect(const char* str, byte clientId); + void on_input(const char* str, byte clientId); + private: + bool _isIPSet(IPAddress ip); +}; + +extern Telnet telnet; + +#endif diff --git a/yoRadio/yoRadio.ino b/yoRadio/yoRadio.ino new file mode 100644 index 0000000..9897095 --- /dev/null +++ b/yoRadio/yoRadio.ino @@ -0,0 +1,42 @@ +#include "Arduino.h" + +#include "options.h" +#include "config.h" +#include "telnet.h" +#include "player.h" +#include "display.h" +#include "player.h" +#include "network.h" +#include "netserver.h" +#include "controls.h" + +void setup() { + Serial.begin(115200); + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, LOW); + config.init(); + display.init(); + player.init(); + network.begin(); + if (network.status != CONNECTED) { + netserver.begin(); + display.start(); + return; + } + initControls(); + netserver.begin(); + telnet.begin(); + player.setVol(config.store.volume, true); + display.start(); + if(config.store.smartstart==1) player.play(config.store.lastStation); +} + +void loop() { + if (network.status == CONNECTED) { + telnet.loop(); + player.loop(); + loopControls(); + } + display.loop(); + netserver.loop(); +}