code upload

This commit is contained in:
e2002
2022-02-04 17:30:12 +03:00
parent fe3f0a261e
commit 3565d2fa17
44 changed files with 25103 additions and 1 deletions

View File

@@ -1,3 +1,4 @@
# yoradio
# ёRadio
![Logo](yoRadio/data/www/elogo100.png)
Web-radio based on ESP32-audioI2S library
--

BIN
images/page1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

BIN
images/page2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

BIN
images/page3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

BIN
images/page4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

39
yoRadio/audiohandlers.ino Normal file
View File

@@ -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);
}
}
}

328
yoRadio/config.cpp Normal file
View File

@@ -0,0 +1,328 @@
#include "config.h"
#include <EEPROM.h>
#include <SPIFFS.h>
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 <class T> 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 <class T> 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();
}

78
yoRadio/config.h Normal file
View File

@@ -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 <class T> int eepromWrite(int ee, const T& value);
template <class T> int eepromRead(int ee, T& value);
void setDefaults();
};
extern Config config;
#endif

101
yoRadio/controls.cpp Normal file
View File

@@ -0,0 +1,101 @@
#include "controls.h"
#include "options.h"
#include "config.h"
#include "player.h"
#include "display.h"
#include <ESP32Encoder.h>
#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();
}

18
yoRadio/controls.h Normal file
View File

@@ -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

View File

View File

@@ -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);
}
}
});

BIN
yoRadio/data/www/elogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

130
yoRadio/data/www/index.html Normal file
View File

@@ -0,0 +1,130 @@
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, minimum-scale=0.25">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<link rel="apple-touch-icon" type="image/png" href="elogo.png">
<link rel="icon" type="image/png" href="elogo.png">
<link rel="stylesheet" title="base" href="/style.css" type="text/css">
<title>ёRadio</title>
<style> </style>
</head>
<body>
<div class="content">
<div class="logo"></div>
<div id="navbar"%APMODE%>
<div class="playerbytton navbutton" id="playlistbutton" onclick="showEditor()"></div>
<div class="playerbytton navbutton" id="settingsbutton" onclick="showSettings()"></div>
</div>
<div class="playerwrap">
<div class="player">
<div id="nameset">&nbsp;</div>
<div id="meta">&nbsp;</div>
<div class="playerbyttonwrap">
<div class="playerbytton" id="prevbutton"></div>
<div class="stopped" id="playbutton"></div>
<div class="playerbytton" id="nextbutton"></div>
<div class="playerbytton" id="volmbutton"></div>
<div class="playerbytton" id="volpbutton"></div>
<div class="playerbytton" id="eqbutton" onclick="showEqualizer(true)"></div>
</div>
<div id="equalizerwrap">
<div id="equalizerbg" class="hidden">
<ul id="equalizer">
<li>
<li>
balance<span class="eqinfo" id="eqbalinfo">0</span>
<input type="range" id="eqbal" class="slider" data-slaveid="eqbalinfo" onchange="onRangeBalChange(this)" name="lovpass" min="-16" max="16" value="0">
</li>
<li>
<li>
treble<span class="eqinfo" id="eqtrebleinfo">0</span>
<input type="range" id="eqtreble" class="slider" data-slaveid="eqtrebleinfo" onchange="onRangeEqChange(this)" name="lovpass" min="-16" max="16" value="0">
</li>
<li>
middle<span class="eqinfo" id="eqmiddleinfo">0</span>
<input type="range" id="eqmiddle" class="slider" data-slaveid="eqmiddleinfo" onchange="onRangeEqChange(this)" name="bandpass" min="-16" max="16" value="0">
</li>
<li>
bass<span class="eqinfo" id="eqbassinfo">0</span>
<input type="range" id="eqbass" class="slider" data-slaveid="eqbassinfo" onchange="onRangeEqChange(this)" name="highpass" min="-16" max="16" value="0">
</li>
<li class="formbuttons">
<div class="button" id="accept_button" onclick="showEqualizer(false)">Acceptable...</div>
</li>
</ul>
</div>
<input type="range" id="volrange" class="slider" name="vol" data-slaveid="volinfo" onchange="onRangeVolChange(this.value)" min="0" max="254" value="0">
<div class="infowrap">
<div class="infoitem">volume: <span id="volinfo">0</span></div>
<div class="infoitem" id="bitinfo">bitrate: 0kBit</div>
<div class="infoitem" id="rsiinfo">rssi: 0dBm</div>
</div>
<ul id="playlist">
</ul>
</div><!--equalizerwrap-->
</div><!--player-->
<div id="settings"%NOTAPMODE%>
<h2>WiFi Settings</h2>
<ul id="credentialwrap">
<li class="credentialitem">
<span>1.</span>
<div class="textinput"><label for="ssid0">SSID</label><input name="ssid" id="ssid0" type="text" value="%SSID%" maxlength="20"/ ></div>
<div class="textinput"><label for"pass0">Password</label><input name="pass" id="pass0" type="password" value="%PASS%" maxlength="40" /></div>
</li>
<li class="credentialitem">
<span>2.</span>
<div class="textinput"><label for="ssid1">SSID</label><input name="ssid" id="ssid1" type="text" value="%SSID%" maxlength="20" /></div>
<div class="textinput"><label for"pass1">Password</label><input name="pass" id="pass1" type="password" value="%PASS%" maxlength="40" /></div>
</li>
<li class="credentialitem">
<span>3.</span>
<div class="textinput"><label for="ssid2">SSID</label><input name="ssid" id="ssid2" type="text" value="%SSID%" maxlength="20" /></div>
<div class="textinput"><label for"pass2">Password</label><input name="pass" id="pass2" type="password" value="%PASS%" maxlength="40" /></div>
</li>
<li class="credentialitem">
<span>4.</span>
<div class="textinput"><label for="ssid3">SSID</label><input name="ssid" id="ssid3" type="text" value="%SSID%" maxlength="20" /></div>
<div class="textinput"><label for"pass3">Password</label><input name="pass" id="pass3" type="password" value="%PASS%" maxlength="40" /></div>
</li>
<li class="credentialitem">
<span>5.</span>
<div class="textinput"><label for="ssid4">SSID</label><input name="ssid" id="ssid4" type="text" value="%SSID%" maxlength="20" /></div>
<div class="textinput"><label for"pass4">Password</label><input name="pass" id="pass4" type="password" value="%PASS%" maxlength="40" /></div>
</li>
</ul>
<div class="formbuttons">
<div class="button" id="cancel_button" onclick="doCancel()"%APMODE%>Cancel</div>
<div class="button" id="save_button" onclick="submitWiFi()">Save</div>
</div>
</div><!--settings-->
<div id="pleditorwrap" hidden>
<div id="pleditor">
<h2>Playlist Editor<span onclick="doCancel()"></span></h2>
<div id="pleheader"><span class="space"></span><span class="plename">Name</span><span class="pleurl">URL</span><span class="pleovol">Ovol</span></div>
<ol id="pleditorcontent">
<li class="pleitem">
<span>1.</span>
<input class="pleinput plename" type="text" value="" maxlength="140" />
<input class="pleinput pleurl" type="text" value="" maxlength="140" />
<input class="pleinput pleovol" type="number" min="-30" max="30" step="1" value="0" />
</li>
</ol><!--pleditorcontent-->
<div class="formbuttons">
<label for="file-upload" class="button">Import</label><input id="file-upload" type="file" accept=".txt, .csv" onchange="doUpload(this)" hidden/>
<div class="button" onclick="doExport()">Export</div>
<div class="button" onclick="doAdd()">Add</div>
<div class="button" onclick="doRemove()">Remove</div>
<!--<div class="button" onclick="doCancel()">Cancel</div>-->
<div class="button" onclick="submitPlaylist()">Save</div>
</div>
</div><!--pleditor-->
</div><!--pleditorwrap-->
</div><!--playerwrap-->
<div id="copy">powered by <a target="_blank" href="https://github.com/e2002/yoradio/">ёRadio</a> | v%VERSION%</div>
</div>
<script src="script.js"></script>
<script src="dragpl.js"></script>
</body>
</html>

347
yoRadio/data/www/script.js Normal file
View File

@@ -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<input.length; i++) {
if (input.charCodeAt(i) <= 127 || input.charCodeAt(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 = '<span class="grabbable" draggable="true">'+("00"+(i+1)).slice(-3)+'</span>\
<span class="pleinput plecheck"><input type="checkbox" /></span>\
<input class="pleinput plename" type="text" value="'+pName+'" maxlength="140" />\
<input class="pleinput pleurl" type="text" value="'+pUrl+'" maxlength="140" />\
<input class="pleinput pleovol" type="number" min="-30" max="30" step="1" value="'+pOvol+'" />';
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 = '<span class="grabbable" draggable="true">'+("00"+(cnt.length+1)).slice(-3)+'</span>\
<span class="pleinput plecheck"><input type="checkbox" /></span>\
<input class="pleinput plename" type="text" value="" maxlength="140" />\
<input class="pleinput pleurl" type="text" value="" maxlength="140" />\
<input class="pleinput pleovol" type="number" min="-30" max="30" step="1" value="0" />';
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="<h2>Settings saved. Rebooting...</h2>";
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");
}

505
yoRadio/data/www/style.css Normal file
View File

@@ -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%;
}
}

311
yoRadio/display.cpp Normal file
View File

@@ -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);
}

81
yoRadio/display.h Normal file
View File

@@ -0,0 +1,81 @@
#ifndef display_h
#define display_h
#include "Arduino.h"
#include <Ticker.h>
#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

163
yoRadio/displayDummy.cpp Normal file
View File

@@ -0,0 +1,163 @@
#include "displayDummy.h"
#include <SPI.h>
#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) {
}

80
yoRadio/displayDummy.h Normal file
View File

@@ -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

321
yoRadio/displayST7735.cpp Normal file
View File

@@ -0,0 +1,321 @@
#include "displayST7735.h"
#include <SPI.h>
#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);
}

83
yoRadio/displayST7735.h Normal file
View File

@@ -0,0 +1,83 @@
#ifndef displayST7735_h
#define displayST7735_h
#include "Arduino.h"
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#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

View File

@@ -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 };

157
yoRadio/fonts/bootlogo.h Normal file
View File

@@ -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 <stdint.h>
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

273
yoRadio/fonts/glcdfont.c Normal file
View File

@@ -0,0 +1,273 @@
#ifndef FONT5X7_H
#define FONT5X7_H
#ifdef __AVR__
#include <avr/io.h>
#include <avr/pgmspace.h>
#elif defined(ESP8266)
#include <pgmspace.h>
#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

384
yoRadio/netserver.cpp Normal file
View File

@@ -0,0 +1,384 @@
#include "netserver.h"
#include <SPIFFS.h>
#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);
}

31
yoRadio/netserver.h Normal file
View File

@@ -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

55
yoRadio/network.cpp Normal file
View File

@@ -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;
}

21
yoRadio/network.h Normal file
View File

@@ -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

47
yoRadio/options.h Normal file
View File

@@ -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

128
yoRadio/player.cpp Normal file
View File

@@ -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;
}
}

36
yoRadio/player.h Normal file
View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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 <Arduino.h>
#include <libb64/cencode.h>
#include <SPI.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <driver/i2s.h>
#ifdef SDFATFS_USED
#include <SdFat.h> // https://github.com/greiman/SdFat
#else
#include <SD.h>
#include <SD_MMC.h>
#include <SPIFFS.h>
#include <FS.h>
#include <FFat.h>
#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;
};
//----------------------------------------------------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@@ -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);

View File

@@ -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;
vector<int32_t>coefs;
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);
}
}
//----------------------------------------------------------------------------------------------------------------------

View File

@@ -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);

File diff suppressed because it is too large Load Diff

View File

@@ -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

269
yoRadio/telnet.cpp Normal file
View File

@@ -0,0 +1,269 @@
#include <stdarg.h>
#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);
}

33
yoRadio/telnet.h Normal file
View File

@@ -0,0 +1,33 @@
#ifndef telnet_h
#define telnet_h
#include <WiFi.h>
#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

42
yoRadio/yoRadio.ino Normal file
View File

@@ -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();
}