diff --git a/Images.md b/Images.md index fbd9363..d6ef758 100644 --- a/Images.md +++ b/Images.md @@ -8,6 +8,8 @@ \ ![ёRadio](images/img6.jpg)\ \ +![ёRadio](images/img7.jpg)\ +\ ![ёRadio](images/page1.jpg)\ \ ![ёRadio](images/page2.jpg)\ diff --git a/README.md b/README.md index 9776a4e..c7113e4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # ёRadio ![ёRadio Logo](yoRadio/data/www/elogo100.png) -##### Web-radio based on [ESP32-audioI2S](https://github.com/schreibfaul1/ESP32-audioI2S) library +##### Web-radio based on [ESP32-audioI2S](https://github.com/schreibfaul1/ESP32-audioI2S) or/and [ESP32-vs1053_ext](https://github.com/schreibfaul1/ESP32-vs1053_ext) library --- - [Hardware](#hardware) - [Connection tables](#connection-tables) @@ -20,7 +20,10 @@ #### Required: **ESP32 board**: https://aliexpress.ru/item/32847027609.html \ **I2S DAC**, roughly like this one: https://aliexpress.ru/item/1005001993192815.html \ -https://aliexpress.ru/item/1005002011542576.html +https://aliexpress.ru/item/1005002011542576.html \ +or **VS1053b module** : https://aliexpress.ru/item/32893187079.html \ +https://aliexpress.ru/item/32838958284.html + #### Optional: ##### Displays - **ST7735** 1.8' or 1.44' https://aliexpress.ru/item/1005002822797745.html @@ -67,6 +70,20 @@ Three tact buttons or Encoder or all together | BCLK | 26* | I2S_BCLK | | LRC(WSEL) | 25* | I2S_LRC | +| VS1053 | ESP-32 | options.h | +| ------ | ------ | ------ | +| XDCS | 25* | VS1053_DCS | +| XCS | 27* | VS1053_CS | +| XRST | EN | VS1053_RST | +| DERQ | 26* | VS1053_DREQ | +| SCK | 18 | - | +| MOSI | 23 | - | +| MISO | 19 | - | +| 5V | +5V | - | +| DGND | GND | - | + +_\#\# Important! You must choose between I2S DAC and VS1053 by disabling the second module in the settings (see below)_ + | Buttons, Encoder | ESP-32 | options.h | | ------ | ------ | ------ | | GND | GND | - | @@ -85,7 +102,19 @@ Adafruit_GFX, Adafruit_ST7735\*, Adafruit_SSD1306\*, Adafruit_PCD8544\*, (\* dep --- ## Hardware setup Hardware is connected in the **[options.h](yoRadio/options.h)** file. \ -_so that the settings are not overwritten when updating git, you need to put the file **myoptions.h** ([exsample](exsamples/myoptions.h)) in the root of the project and make settings in it_ +_so that the settings are not overwritten when updating git, you need to put the file **myoptions.h** ([exsample](exsamples/myoptions.h)) in the root of the project and make settings in it_ \ + +**Important!** +You must choose between I2S DAC and VS1053 by disabling the second module in the settings: +````c++ +// If I2S DAC used: +#define I2S_DOUT 27 +#define VS1053_CS 255 +// If VS1053 used: +#define I2S_DOUT 255 +#define VS1053_CS 27 +```` +Define display model: ````c++ /* DISPLAY MODEL * 0 - DUMMY @@ -95,12 +124,12 @@ _so that the settings are not overwritten when updating git, you need to put the */ #define DSP_MODEL 1 ```` -The ST7735 display model is configured in the file [src/displays/displayST7735.cpp](yoRadio/src/displays/displayST7735.cpp) +The ST7735 display submodel is configured in the file [src/displays/displayST7735.cpp](yoRadio/src/displays/displayST7735.cpp) ````c++ #define DTYPE INITR_BLACKTAB // 1.8' https://aliexpress.ru/item/1005002822797745.html //#define DTYPE INITR_144GREENTAB // 1.44' https://aliexpress.ru/item/1005002822797745.html ```` -Rotation of the ST7735 display is configured in the file [src/displays/displayST7735.h](yoRadio/src/displays/displayST7735.h) +Rotation of the displays is configured in the files [src/displays/displayXXXXX.h](yoRadio/src/displays/displayST7735.h) ````c++ #define TFT_ROTATE 3 // 180 degress ```` @@ -154,6 +183,9 @@ _\*this step can be skipped if you add WiFiSSID WiFiPassword pairs to the [yoRad --- ## Version history +#### v0.4.248 +- added support for VS1053 module _in testing mode_ + #### v0.4.210 - added timezone config by telnet - fix telnet output diff --git a/exsamples/myoptions.h b/exsamples/myoptions.h index 0963ec9..85ad2dc 100644 --- a/exsamples/myoptions.h +++ b/exsamples/myoptions.h @@ -7,8 +7,9 @@ * 1: white SSD1306 without controls * 2: ST7735 with encoder * 3: Nokia 5110 dev board + * 4: VS1053 dev */ -#define HWID 3 +#define HWID 2 /******************************************/ @@ -35,6 +36,12 @@ #define BTN_CENTER 12 #define BTN_RIGHT 14 +#elif HWID==4 +#define DSP_MODEL 3 +#define VS1053_CS 27 +#define I2S_DOUT 255 +#define VS1053_RST 14 + #endif /******************************************/ diff --git a/images/img7.jpg b/images/img7.jpg new file mode 100644 index 0000000..1848fd5 Binary files /dev/null and b/images/img7.jpg differ diff --git a/yoRadio-0.4.210.tar.gz b/yoRadio-0.4.210.tar.gz new file mode 100644 index 0000000..7571eb0 Binary files /dev/null and b/yoRadio-0.4.210.tar.gz differ diff --git a/yoRadio-0.4.210.zip b/yoRadio-0.4.210.zip new file mode 100644 index 0000000..fc99186 Binary files /dev/null and b/yoRadio-0.4.210.zip differ diff --git a/yoRadio/options.h b/yoRadio/options.h index 4a3be75..5d05237 100644 --- a/yoRadio/options.h +++ b/yoRadio/options.h @@ -1,7 +1,7 @@ #ifndef options_h #define options_h -#define VERSION "0.4.210" +#define VERSION "0.4.248" /* DISPLAY MODEL * 0 - DUMMY @@ -22,7 +22,7 @@ * ************ */ #define TFT_CS 5 -#define TFT_RST 15 // Or set to -1 and connect to Arduino RESET pin +#define TFT_RST 15 // Or set to -1 and connect to Esp EN pin //#define TFT_RST -1 // we use the seesaw for resetting to save a pin #define TFT_DC 4 /* @@ -31,6 +31,13 @@ #define I2C_SDA 13 #define I2C_SCL 14 #define I2C_RST -1 +/* + * VS1053 + */ +#define VS1053_CS 255 // 27 +#define VS1053_DCS 25 +#define VS1053_DREQ 26 +#define VS1053_RST -1 // set to -1 if connected to Esp EN pin /* * I2S DAC */ diff --git a/yoRadio/player.cpp b/yoRadio/player.cpp index be6fabc..a8f6576 100644 --- a/yoRadio/player.cpp +++ b/yoRadio/player.cpp @@ -1,19 +1,44 @@ +#include "options.h" + #include "player.h" + #include "config.h" #include "telnet.h" #include "display.h" -#include "options.h" + #include "netserver.h" Player player; +#if VS1053_CS!=255 +Player::Player(): Audio(VS1053_CS, VS1053_DCS, VS1053_DREQ) { + +} +void ResetChip(){ + digitalWrite(VS1053_RST, LOW); + delay(10); + digitalWrite(VS1053_RST, HIGH); + delay(100); +} +#else +Player::Player() {} +#endif + + + void Player::init() { +#if I2S_DOUT!=255 setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT); +#else + SPI.begin(); + if(VS1053_RST>0) ResetChip(); + begin(); +#endif + setBalance(config.store.balance); + setTone(config.store.bass, config.store.middle, config.store.trebble); setVolume(0); mode = STOPPED; requesToStart = true; - setBalance(config.store.balance); - setTone(config.store.bass, config.store.middle, config.store.trebble); zeroRequest(); } @@ -25,6 +50,7 @@ void Player::stopInfo() { } void Player::loop() { + //Serial.println(mode == PLAYING?"mode == PLAYING":"mode == STOPPED"); if (mode == PLAYING) { Audio::loop(); } else { diff --git a/yoRadio/player.h b/yoRadio/player.h index 732632f..0d650c5 100644 --- a/yoRadio/player.h +++ b/yoRadio/player.h @@ -1,7 +1,12 @@ #ifndef player_h #define player_h +#include "options.h" +#if I2S_DOUT!=255 #include "src/audioI2S/AudioEx.h" +#else +#include "src/audioVS1053/audioVS1053Ex.h" +#endif enum audioMode_e { PLAYING, STOPPED }; @@ -18,6 +23,7 @@ class Player: public Audio { audiorequest_t request; bool requesToStart; public: + Player(); void init(); void loop(); void zeroRequest(); @@ -33,4 +39,5 @@ class Player: public Audio { extern Player player; + #endif diff --git a/yoRadio/src/audioI2S/Audio.cpp b/yoRadio/src/audioI2S/Audio.cpp index 89b43a0..3a2394e 100644 --- a/yoRadio/src/audioI2S/Audio.cpp +++ b/yoRadio/src/audioI2S/Audio.cpp @@ -1,3 +1,5 @@ +#include "../../options.h" +#if VS1053_CS==255 /* * Audio.cpp * @@ -4538,3 +4540,4 @@ int16_t* Audio::IIR_filterChain2(int16_t iir_in[2], bool clear){ // Infinite Im return iir_out; } +#endif diff --git a/yoRadio/src/audioVS1053/audioVS1053Ex.cpp b/yoRadio/src/audioVS1053/audioVS1053Ex.cpp new file mode 100644 index 0000000..9ef8310 --- /dev/null +++ b/yoRadio/src/audioVS1053/audioVS1053Ex.cpp @@ -0,0 +1,2365 @@ +/* + * vs1053_ext.cpp + * + * Created on: Jul 09.2017 + * Updated on: Feb 11 2022 + * Author: Wolle + */ +#include "../../options.h" +#if I2S_DOUT==255 + +#include "audioVS1053Ex.h" + +//--------------------------------------------------------------------------------------------------------------------- +AudioBuffer::AudioBuffer(size_t maxBlockSize) { + // if maxBlockSize isn't set use defaultspace (1600 bytes) is enough for aac and mp3 player + if(maxBlockSize) m_resBuffSizeRAM = maxBlockSize; + if(maxBlockSize) m_maxBlockSize = maxBlockSize; +} + +AudioBuffer::~AudioBuffer() { + if(m_buffer) + free(m_buffer); + m_buffer = NULL; +} + +size_t AudioBuffer::init() { + if(m_buffer) free(m_buffer); + m_buffer = NULL; + if(psramInit()) { + // PSRAM found, AudioBuffer will be allocated in PSRAM + m_buffSize = m_buffSizePSRAM; + if(m_buffer == NULL) { + m_buffer = (uint8_t*) ps_calloc(m_buffSize, sizeof(uint8_t)); + m_buffSize = m_buffSizePSRAM - m_resBuffSizePSRAM; + if(m_buffer == NULL) { + // not enough space in PSRAM, use ESP32 Flash Memory instead + m_buffer = (uint8_t*) calloc(m_buffSize, sizeof(uint8_t)); + m_buffSize = m_buffSizeRAM - m_resBuffSizeRAM; + } + } + } else { // no PSRAM available, use ESP32 Flash Memory" + m_buffSize = m_buffSizeRAM; + m_buffer = (uint8_t*) calloc(m_buffSize, sizeof(uint8_t)); + m_buffSize = m_buffSizeRAM - m_resBuffSizeRAM; + } + if(!m_buffer) + return 0; + resetBuffer(); + return m_buffSize; +} + +void AudioBuffer::changeMaxBlockSize(uint16_t mbs){ + m_maxBlockSize = mbs; + return; +} + +uint16_t AudioBuffer::getMaxBlockSize(){ + return m_maxBlockSize; +} + +size_t AudioBuffer::freeSpace() { + if(m_readPtr >= m_writePtr) { + m_freeSpace = (m_readPtr - m_writePtr); + } else { + m_freeSpace = (m_endPtr - m_writePtr) + (m_readPtr - m_buffer); + } + if(m_f_start) + m_freeSpace = m_buffSize; + return m_freeSpace - 1; +} + +size_t AudioBuffer::writeSpace() { + if(m_readPtr >= m_writePtr) { + m_writeSpace = (m_readPtr - m_writePtr - 1); // readPtr must not be overtaken + } else { + if(getReadPos() == 0) + m_writeSpace = (m_endPtr - m_writePtr - 1); + else + m_writeSpace = (m_endPtr - m_writePtr); + } + if(m_f_start) + m_writeSpace = m_buffSize - 1; + return m_writeSpace; +} + +size_t AudioBuffer::bufferFilled() { + if(m_writePtr >= m_readPtr) { + m_dataLength = (m_writePtr - m_readPtr); + } else { + m_dataLength = (m_endPtr - m_readPtr) + (m_writePtr - m_buffer); + } + return m_dataLength; +} + +void AudioBuffer::bytesWritten(size_t bw) { + m_writePtr += bw; + if(m_writePtr == m_endPtr) { + m_writePtr = m_buffer; + } + if(bw && m_f_start) + m_f_start = false; +} + +void AudioBuffer::bytesWasRead(size_t br) { + m_readPtr += br; + if(m_readPtr >= m_endPtr) { + size_t tmp = m_readPtr - m_endPtr; + m_readPtr = m_buffer + tmp; + } +} + +uint8_t* AudioBuffer::getWritePtr() { + return m_writePtr; +} + +uint8_t* AudioBuffer::getReadPtr() { + size_t len = m_endPtr - m_readPtr; + if(len < m_maxBlockSize) { // be sure the last frame is completed + memcpy(m_endPtr, m_buffer, m_maxBlockSize - len); // cpy from m_buffer to m_endPtr with len + } +return m_readPtr; +} + +void AudioBuffer::resetBuffer() { + m_writePtr = m_buffer; + m_readPtr = m_buffer; + m_endPtr = m_buffer + m_buffSize; + m_f_start = true; + // memset(m_buffer, 0, m_buffSize); //Clear Inputbuffer +} + +uint32_t AudioBuffer::getWritePos() { + return m_writePtr - m_buffer; +} + +uint32_t AudioBuffer::getReadPos() { + return m_readPtr - m_buffer; +} +//--------------------------------------------------------------------------------------------------------------------- +// **** VS1053 Impl **** +//--------------------------------------------------------------------------------------------------------------------- +Audio::Audio(uint8_t _cs_pin, uint8_t _dcs_pin, uint8_t _dreq_pin, uint8_t spi, uint8_t mosi, uint8_t miso, uint8_t sclk) : + cs_pin(_cs_pin), dcs_pin(_dcs_pin), dreq_pin(_dreq_pin) +{ + // default is VSPI (VSPI_MISO 19, VSPI_MOSI 23, VSPI_SCLK 18) + // HSPI (HSPI_MISO 12, HSPI_MOSI 13, HSPI_SCLK 14) + + if(spi == VSPI){ + spi_VS1053 = &SPI; + } + else if(spi == HSPI){ + spi_VS1053 = new SPIClass(HSPI); + spi_VS1053->begin(sclk, miso, mosi, -1); + } + else{ + log_e("unknown SPI authority"); + } + + clientsecure.setInsecure(); // update to ESP32 Arduino version 1.0.5-rc05 or higher + m_endFillByte=0; + curvol=50; + m_LFcount=0; +} +Audio::~Audio(){ + // destructor +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::initInBuff() { + static bool f_already_done = false; + if(!f_already_done) { + size_t size = InBuff.init(); + if(size == m_buffSizeRAM - m_resBuffSizeRAM) { + sprintf(chbuf, "PSRAM not found, inputBufferSize: %u bytes", size - 1); + if(audio_info) audio_info(chbuf); + f_already_done = true; + } + if(size == m_buffSizePSRAM - m_resBuffSizePSRAM) { + sprintf(chbuf, "PSRAM found, inputBufferSize: %u bytes", size - 1); + if(audio_info) audio_info(chbuf); + f_already_done = true; + } + } + changeMaxBlockSize(4096); +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::control_mode_off() +{ + CS_HIGH(); // End control mode + spi_VS1053->endTransaction(); // Allow other SPI users +} +void Audio::control_mode_on() +{ + spi_VS1053->beginTransaction(VS1053_SPI); // Prevent other SPI users + DCS_HIGH(); // Bring slave in control mode + CS_LOW(); +} +void Audio::data_mode_on() +{ + spi_VS1053->beginTransaction(VS1053_SPI); // Prevent other SPI users + CS_HIGH(); // Bring slave in data mode + DCS_LOW(); +} +void Audio::data_mode_off() +{ + //digitalWrite(dcs_pin, HIGH); // End data mode + DCS_HIGH(); + spi_VS1053->endTransaction(); // Allow other SPI users +} +//--------------------------------------------------------------------------------------------------------------------- +uint16_t Audio::read_register(uint8_t _reg) +{ + uint16_t result=0; + control_mode_on(); + spi_VS1053->write(3); // Read operation + spi_VS1053->write(_reg); // Register to write (0..0xF) + // Note: transfer16 does not seem to work + result=(spi_VS1053->transfer(0xFF) << 8) | (spi_VS1053->transfer(0xFF)); // Read 16 bits data + await_data_request(); // Wait for DREQ to be HIGH again + control_mode_off(); + return result; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::write_register(uint8_t _reg, uint16_t _value) +{ + control_mode_on(); + spi_VS1053->write(2); // Write operation + spi_VS1053->write(_reg); // Register to write (0..0xF) + spi_VS1053->write16(_value); // Send 16 bits data + await_data_request(); + control_mode_off(); +} +//--------------------------------------------------------------------------------------------------------------------- +size_t Audio::sendBytes(uint8_t* data, size_t len){ + size_t chunk_length = 0; // Length of chunk 32 byte or shorter + size_t bytesDecoded = 0; + + data_mode_on(); + while(len){ // More to do? + if(!digitalRead(dreq_pin)) break; + chunk_length = len; + if(len > vs1053_chunk_size){ + chunk_length = vs1053_chunk_size; + } + spi_VS1053->writeBytes(data, chunk_length); + data += chunk_length; + len -= chunk_length; + bytesDecoded += chunk_length; + } + data_mode_off(); + return bytesDecoded; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::sdi_send_buffer(uint8_t* data, size_t len) +{ + size_t chunk_length; // Length of chunk 32 byte or shorter + + data_mode_on(); + while(len){ // More to do? + + await_data_request(); // Wait for space available + chunk_length=len; + if(len > vs1053_chunk_size){ + chunk_length=vs1053_chunk_size; + } + len-=chunk_length; + spi_VS1053->writeBytes(data, chunk_length); + data+=chunk_length; + } + data_mode_off(); +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::sdi_send_fillers(size_t len){ + + size_t chunk_length; // Length of chunk 32 byte or shorter + + data_mode_on(); + while(len) // More to do? + { + await_data_request(); // Wait for space available + chunk_length=len; + if(len > vs1053_chunk_size){ + chunk_length=vs1053_chunk_size; + } + len-=chunk_length; + while(chunk_length--){ + spi_VS1053->write(m_endFillByte); + } + } + data_mode_off(); +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::wram_write(uint16_t address, uint16_t data){ + + write_register(SCI_WRAMADDR, address); + write_register(SCI_WRAM, data); +} +//--------------------------------------------------------------------------------------------------------------------- +uint16_t Audio::wram_read(uint16_t address){ + + write_register(SCI_WRAMADDR, address); // Start reading from WRAM + return read_register(SCI_WRAM); // Read back result +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::begin(){ + + pinMode(dreq_pin, INPUT); // DREQ is an input + pinMode(cs_pin, OUTPUT); // The SCI and SDI signals + pinMode(dcs_pin, OUTPUT); + DCS_HIGH(); + CS_HIGH(); + delay(100); + + // Init SPI in slow mode (0.2 MHz) + VS1053_SPI = SPISettings(200000, MSBFIRST, SPI_MODE0); +// printDetails("Right after reset/startup"); + delay(20); + // Most VS1053 modules will start up in midi mode. The result is that there is no audio + // when playing MP3. You can modify the board, but there is a more elegant way: + wram_write(0xC017, 3); // GPIO DDR=3 + wram_write(0xC019, 0); // GPIO ODATA=0 + delay(100); +// printDetails("After test loop"); + softReset(); // Do a soft reset + // Switch on the analog parts + write_register(SCI_AUDATA, 44100 + 1); // 44.1kHz + stereo + // The next clocksetting allows SPI clocking at 5 MHz, 4 MHz is safe then. + write_register(SCI_CLOCKF, 6 << 12); // Normal clock settings multiplyer 3.0=12.2 MHz + //SPI Clock to 4 MHz. Now you can set high speed SPI clock. + VS1053_SPI=SPISettings(6700000, MSBFIRST, SPI_MODE0); // SPIDIV 12 -> 80/12=6.66 MHz + write_register(SCI_MODE, _BV (SM_SDINEW) | _BV(SM_LINE1)); + //testComm("Fast SPI, Testing VS1053 read/write registers again... \n"); + delay(10); + await_data_request(); + m_endFillByte=wram_read(0x1E06) & 0xFF; +// sprintf(chbuf, "endFillByte is %X", endFillByte); +// if(audio_info) audio_info(chbuf); +// printDetails("After last clocksetting \n"); + loadUserCode(); // load in VS1053B if you want to play flac + delay(100); +} +//--------------------------------------------------------------------------------------------------------------------- +size_t Audio::bufferFilled(){ + return InBuff.bufferFilled(); +} +//--------------------------------------------------------------------------------------------------------------------- +size_t Audio::bufferFree(){ + return InBuff.freeSpace(); +} +//--------------------------------------------------------------------------------------------------------------------- + +void Audio::setVolume(uint8_t vol){ + + // Set volume. Both left and right. + // Input value is 0..21. 21 is the loudest. + // Clicking reduced by using 0xf8 to 0x00 as limits. + uint16_t value; // Value to send to SCI_VOL + + //if(vol > 21) vol=21; + //uint8_t vol2 = map(vol, 0, 254, 128, 254); + //uint8_t vol2 = log((float)vol)*23+128; + //uint8_t vol2 = log10((float)vol)*50.0+128; + uint8_t lgvol = log10(((float)vol+1)) * 50.54571334 + 128; + //uint8_t vol2 = sqrt((float)vol)*7.5+128; + if(vol != curvol){ + curvol = vol; // #20 + //vol=volumetable[vol]; // Save for later use + value=map(lgvol, 0, 254, 0xF8, 0x00); // 0..100% to one channel + value=(value << 8) | value; + + write_register(SCI_VOL, value); // Volume left and right + } +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::setTone(uint8_t *rtone){ // Set bass/treble (4 nibbles) + + // Set tone characteristics. See documentation for the 4 nibbles. + uint16_t value=0; // Value to send to SCI_BASS + int i; // Loop control + + for(i=0; i < 4; i++) + { + value=(value << 4) | rtone[i]; // Shift next nibble in + } + write_register(SCI_BASS, value); // Volume left and right +} +void Audio::setTone(int8_t gainLowPass, int8_t gainBandPass, int8_t gainHighPass){ + // TODO +} +void Audio::setBalance(int8_t bal){ + // TODO +} +//--------------------------------------------------------------------------------------------------------------------- +uint8_t Audio::getVolume() // Get the currenet volume setting. +{ + return curvol; +} +//---------------------------------------------------------------------------------------------------------------------- +void Audio::startSong() +{ + sdi_send_fillers(2052); +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::stopSong() +{ + uint16_t modereg; // Read from mode register + int i; // Loop control + + m_f_localfile = false; + m_f_webfile = false; + m_f_webstream = false; + + sdi_send_fillers(2052); + delay(10); + write_register(SCI_MODE, _BV (SM_SDINEW) | _BV(SM_CANCEL)); + for(i=0; i < 200; i++) + { + sdi_send_fillers(32); + modereg=read_register(SCI_MODE); // Read status + if((modereg & _BV(SM_CANCEL)) == 0) + { + sdi_send_fillers(2052); + sprintf(chbuf, "Song stopped correctly after %d msec", i * 10); + m_f_running = false; + if(audio_info) audio_info(chbuf); + return; + } + delay(10); + } + if(audio_info) audio_info("Song stopped incorrectly!"); + printDetails("after sond stopped incorrectly"); +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::softReset() +{ + write_register(SCI_MODE, _BV (SM_SDINEW) | _BV(SM_RESET)); + delay(10); + await_data_request(); +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::printDetails(const char* str){ + + if(strlen(str) && audio_info) audio_info(str); + + char decbuf[16][6]; + char hexbuf[16][5]; + char binbuf[16][17]; + uint8_t i; + + const char regName[16][12] = { + "MODE ", "STATUS ", "BASS ", "CLOCKF ", + "DECODE_TIME", "AUDATA ", "WRAM ", "WRAMADDR ", + "HDAT0 ", "HDAT1 ", "AIADDR ", "VOL ", + "AICTRL0 ", "AICTRL1 ", "AICTRL2 ", "AICTRL3 ", + }; + + for(i=0; i <= SCI_AICTRL3; i++){ + sprintf(hexbuf[i], "%04X", read_register(i)); + sprintf(decbuf[i], "%05d", read_register(i)); + + uint16_t tmp = read_register(i); + uint16_t shift = 0x8000; + for(int8_t j = 0; j < 16; j++){ + binbuf[i][j] = (tmp & shift ? '1' : '0'); + shift >>= 1; + } + binbuf[i][16] = '\0'; + } + + if(audio_info) audio_info("REG dec bin hex"); + if(audio_info) audio_info("----------- ------- ---------------- -------"); + + for(i=0; i <= SCI_AICTRL3; i++){ + sprintf(chbuf, "%s %s %s %s", regName[i], decbuf[i], binbuf[i], hexbuf[i]); + if(audio_info) audio_info(chbuf); + } +} +//--------------------------------------------------------------------------------------------------------------------- +const char* Audio::printVersion(){ + boolean flag=true; + uint16_t reg1 = 0, reg2 = 0, reg3 = 0; + reg1 = wram_read(0x1E00); + reg2 = wram_read(0x1E01); + reg3 = wram_read(0x1E02) & 0xFF; + if((reg1 ==0xFFFF)&&(reg2 == 0xFFFF)) {flag = false; log_e("all pins high?, VS1053 seems not connected");} + if((reg1 ==0x0000)&&(reg2 == 0x0000)) {flag = false; log_e("all pins low?, VS1053 not proper connected (no SCK?)");} + if( reg3 == 0xFF) {flag = false; log_e("VS1053 version too high");} + sprintf(chbuf, "chipID = %d%d, version = %d", reg1, reg2, reg3); + if(audio_info) audio_info(chbuf); + if(flag) return chbuf; + return nullptr; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::showstreamtitle(const char* ml) { + // example for ml: + // StreamTitle='Oliver Frank - Mega Hitmix';StreamUrl='www.radio-welle-woerthersee.at'; + // or adw_ad='true';durationMilliseconds='10135';adId='34254';insertionType='preroll'; + + int16_t idx1, idx2; + uint16_t i = 0, hash = 0; + static uint16_t sTit_remember = 0, sUrl_renember = 0; + + idx1 = indexOf(ml, "StreamTitle=", 0); + if(idx1 >= 0){ // Streamtitle found + idx2 = indexOf(ml, ";", idx1); + char *sTit; + if(idx2 >= 0){sTit = strndup(ml + idx1, idx2 + 1); sTit[idx2] = '\0';} + else sTit = strdup(ml); + + while(i < strlen(sTit)){hash += sTit[i] * i+1; i++;} + + if(sTit_remember != hash){ + sTit_remember = hash; + if(audio_info) audio_info(sTit); + uint8_t pos = 12; // remove "StreamTitle=" + if(sTit[pos] == '\'') pos++; // remove leading \' + if(sTit[strlen(sTit) - 1] == '\'') sTit[strlen(sTit) -1] = '\0'; // remove trailing \' + if(audio_showstreamtitle) audio_showstreamtitle(sTit + pos); + } + free(sTit); + } + + idx1 = indexOf(ml, "StreamUrl=", 0); + idx2 = indexOf(ml, ";", idx1); + if(idx1 >= 0 && idx2 > idx1){ // StreamURL found + uint16_t len = idx2 - idx1; + char *sUrl; + sUrl = strndup(ml + idx1, len + 1); sUrl[len] = '\0'; + + while(i < strlen(sUrl)){hash += sUrl[i] * i+1; i++;} + if(sUrl_renember != hash){ + sUrl_renember = hash; + if(audio_info) audio_info(sUrl); + } + free(sUrl); + } + + idx1 = indexOf(ml, "adw_ad=", 0); + if(idx1 >= 0){ // Advertisement found + idx1 = indexOf(ml, "durationMilliseconds=", 0); + idx2 = indexOf(ml, ";", idx1); + if(idx1 >= 0 && idx2 > idx1){ + uint16_t len = idx2 - idx1; + char *sAdv; + sAdv = strndup(ml + idx1, len + 1); sAdv[len] = '\0'; + if(audio_info) audio_info(sAdv); + uint8_t pos = 21; // remove "StreamTitle=" + if(sAdv[pos] == '\'') pos++; // remove leading \' + if(sAdv[strlen(sAdv) - 1] == '\'') sAdv[strlen(sAdv) -1] = '\0'; // remove trailing \' + if(audio_commercial) audio_commercial(sAdv + pos); + free(sAdv); + } + } +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::loop(){ + // - localfile - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_f_localfile) { // Playing file fron SPIFFS or SD? + processLocalFile(); + } + // - webstream - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_f_webstream) { // Playing file from URL? + //if(!m_f_running) return; + if(m_datamode == VS1053_PLAYLISTINIT || m_datamode == VS1053_PLAYLISTHEADER || m_datamode == VS1053_PLAYLISTDATA){ + processPlayListData(); + return; + } + if(m_datamode == VS1053_HEADER){ + processAudioHeaderData(); + return; + } + if(m_datamode == VS1053_DATA){ + processWebStream(); + return; + } + } + return; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::processLocalFile() { + + if(!(audiofile && m_f_running && m_f_localfile)) return; + + int bytesDecoded = 0; + uint32_t bytesCanBeWritten = 0; + uint32_t bytesCanBeRead = 0; + int32_t bytesAddedToBuffer = 0; + static bool f_stream; + + if(m_f_firstCall) { // runs only one time per connection, prepare for start + m_f_firstCall = false; + f_stream = false; + return; + } + bytesCanBeWritten = InBuff.writeSpace(); + //---------------------------------------------------------------------------------------------------- + // some files contain further data after the audio block (e.g. pictures). + // In that case, the end of the audio block is not the end of the file. An 'eof' has to be forced. + if((m_controlCounter == 100) && (m_contentlength > 0)) { // fileheader was read + if(bytesCanBeWritten + getFilePos() >= m_contentlength){ + if(m_contentlength > getFilePos()) bytesCanBeWritten = m_contentlength - getFilePos(); + else bytesCanBeWritten = 0; + } + } + //---------------------------------------------------------------------------------------------------- + + bytesAddedToBuffer = audiofile.read(InBuff.getWritePtr(), bytesCanBeWritten); + if(bytesAddedToBuffer > 0) { + InBuff.bytesWritten(bytesAddedToBuffer); + } + +// if(psramFound() && bytesAddedToBuffer >4096) +// vTaskDelay(2);// PSRAM has a bottleneck in the queue, so wait a little bit + + if(bytesAddedToBuffer == -1) bytesAddedToBuffer = 0; // read error? eof? + bytesCanBeRead = InBuff.bufferFilled(); + if(bytesCanBeRead > InBuff.getMaxBlockSize()) bytesCanBeRead = InBuff.getMaxBlockSize(); + if(bytesCanBeRead == InBuff.getMaxBlockSize()) { // mp3 or aac frame complete? + if(!f_stream) { + f_stream = true; + if(audio_info) audio_info("stream ready"); + } + if(m_controlCounter != 100){ + if(m_codec == CODEC_WAV){ + // int res = read_WAV_Header(InBuff.getReadPtr(), bytesCanBeRead); + m_controlCounter = 100; + } + if(m_codec == CODEC_MP3){ + int res = read_MP3_Header(InBuff.getReadPtr(), bytesCanBeRead); + if(res >= 0) bytesDecoded = res; + else{ // error, skip header + m_controlCounter = 100; + } + } + if(m_codec == CODEC_M4A){ + // int res = read_M4A_Header(InBuff.getReadPtr(), bytesCanBeRead); + // if(res >= 0) bytesDecoded = res; + // else{ // error, skip header + m_controlCounter = 100; + // } + } + if(m_codec == CODEC_AAC){ + // stream only, no header + m_audioDataSize = getFileSize(); + m_controlCounter = 100; + } + + if(m_codec == CODEC_FLAC){ + // int res = read_FLAC_Header(InBuff.getReadPtr(), bytesCanBeRead); + // if(res >= 0) bytesDecoded = res; + // else{ // error, skip header + // stopSong(); + m_controlCounter = 100; + // } + } + + if(m_codec == CODEC_OGG){ + m_controlCounter = 100; + } + } + else { + bytesDecoded = sendBytes(InBuff.getReadPtr(), bytesCanBeRead); + } + if(bytesDecoded > 0) {InBuff.bytesWasRead(bytesDecoded);} + return; + } + if(!bytesAddedToBuffer) { // eof + bytesCanBeRead = InBuff.bufferFilled(); + if(bytesCanBeRead > 200){ + if(bytesCanBeRead > InBuff.getMaxBlockSize()) bytesCanBeRead = InBuff.getMaxBlockSize(); + bytesDecoded = sendBytes(InBuff.getReadPtr(), bytesCanBeRead); // play last chunk(s) + if(bytesDecoded > 0){ + InBuff.bytesWasRead(bytesDecoded); + return; + } + } + InBuff.resetBuffer(); + + // if(m_f_loop && f_stream){ //eof + // sprintf(chbuf, "loop from: %u to: %u", getFilePos(), m_audioDataStart); //TEST loop + // if(audio_info) audio_info(chbuf); + // setFilePos(m_audioDataStart); + // if(m_codec == CODEC_FLAC) FLACDecoderReset(); + // /* + // The current time of the loop mode is not reset, + // which will cause the total audio duration to be exceeded. + // For example: current time ====progress bar====> total audio duration + // 3:43 ====================> 3:33 + // */ + // m_audioCurrentTime = 0; + // return; + // } //TEST loop + + f_stream = false; + m_f_localfile = false; + + char *afn =strdup(audiofile.name()); // store temporary the name + + + stopSong(); + sprintf(chbuf, "End of file \"%s\"", afn); + if(audio_info) audio_info(chbuf); + if(audio_eof_mp3) audio_eof_mp3(afn); + if(afn) free(afn); + } +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::processWebStream(){ + const uint16_t maxFrameSize = InBuff.getMaxBlockSize(); + int32_t availableBytes = 0; // available bytes in stream + static bool f_tmr_1s; + static bool f_stream; // first audio data received + static int bytesDecoded; + static uint32_t byteCounter; // count received data + static uint32_t chunksize; // chunkcount read from stream + static uint32_t tmr_1s; // timer 1 sec + static uint32_t loopCnt; // count loops if clientbuffer is empty + static uint32_t metacount; // counts down bytes between metadata + + + // first call, set some values to default - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_f_firstCall) { // runs only ont time per connection, prepare for start + m_f_firstCall = false; + f_stream = false; + byteCounter = 0; + chunksize = 0; + bytesDecoded = 0; + loopCnt = 0; + tmr_1s = millis(); + m_t0 = millis(); + metacount = m_metaint; + readMetadata(0, true); // reset all static vars + } + + // timer, triggers every second - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if((tmr_1s + 1000) < millis()) { + f_tmr_1s = true; // flag will be set every second for one loop only + tmr_1s = millis(); + } + + if(m_f_ssl == false) availableBytes = client.available(); // available from stream + if(m_f_ssl == true) availableBytes = clientsecure.available(); // available from stream + + // if we have chunked data transfer: get the chunksize- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_f_chunked && !m_chunkcount && availableBytes) { // Expecting a new chunkcount? + int b; + if(!m_f_ssl) b = client.read(); + else b = clientsecure.read(); + + if(b == '\r') return; + if(b == '\n'){ + m_chunkcount = chunksize; + chunksize = 0; + if(m_f_tts){ + m_contentlength = m_chunkcount; // tts has one chunk only + m_f_webfile = true; + m_f_chunked = false; + } + return; + } + // We have received a hexadecimal character. Decode it and add to the result. + b = toupper(b) - '0'; // Be sure we have uppercase + if(b > 9) b = b - 7; // Translate A..F to 10..15 + chunksize = (chunksize << 4) + b; + return; + } + + // if we have metadata: get them - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(!metacount && !m_f_swm && availableBytes){ + int16_t b = 0; + if(!m_f_ssl) b = client.read(); + else b = clientsecure.read(); + if(b >= 0) { + if(m_f_chunked) m_chunkcount--; + if(readMetadata(b)) metacount = m_metaint; + } + return; + } + + // if the buffer is often almost empty issue a warning - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(InBuff.bufferFilled() < maxFrameSize && f_stream){ + static uint8_t cnt_slow = 0; + cnt_slow ++; + if(f_tmr_1s) { + if(cnt_slow > 25 && audio_info) audio_info("slow stream, dropouts are possible"); + f_tmr_1s = false; + cnt_slow = 0; + } + } + + // if the buffer can't filled for several seconds try a new connection - - - - - - - - - - - - - - - - - - - - - - + if(f_stream && !availableBytes){ + loopCnt++; + if(loopCnt > 200000) { // wait several seconds + loopCnt = 0; + if(audio_info) audio_info("Stream lost -> try new connection"); + connecttohost(m_lastHost); + } + } + if(availableBytes) loopCnt = 0; + + + // buffer fill routine - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(true) { // statement has no effect + uint32_t bytesCanBeWritten = InBuff.writeSpace(); + if(!m_f_swm) bytesCanBeWritten = min(metacount, bytesCanBeWritten); + if(m_f_chunked) bytesCanBeWritten = min(m_chunkcount, bytesCanBeWritten); + + int16_t bytesAddedToBuffer = 0; + + if(psramFound()) if(bytesCanBeWritten > 4096) bytesCanBeWritten = 4096; // PSRAM throttle + + if(m_f_webfile){ + // normally there is nothing to do here, if byteCounter == contentLength + // then the file is completely read, but: + // m4a files can have more data (e.g. pictures ..) after the audio Block + // therefore it is bad to read anything else (this can generate noise) + if(byteCounter + bytesCanBeWritten >= m_contentlength) bytesCanBeWritten = m_contentlength - byteCounter; + } + + if(m_f_ssl == false) bytesAddedToBuffer = client.read(InBuff.getWritePtr(), bytesCanBeWritten); + else bytesAddedToBuffer = clientsecure.read(InBuff.getWritePtr(), bytesCanBeWritten); + + if(bytesAddedToBuffer > 0) { + if(m_f_webfile) byteCounter += bytesAddedToBuffer; // Pull request #42 + if(!m_f_swm) metacount -= bytesAddedToBuffer; + if(m_f_chunked) m_chunkcount -= bytesAddedToBuffer; + InBuff.bytesWritten(bytesAddedToBuffer); + } + + if(InBuff.bufferFilled() > maxFrameSize && !f_stream) { // waiting for buffer filled + f_stream = true; // ready to play the audio data + uint16_t filltime = millis() - m_t0; + if(audio_info) audio_info("stream ready"); + sprintf(chbuf, "buffer filled in %d ms", filltime); + if(audio_info) audio_info(chbuf); + } + if(!f_stream) return; + } + + // // if we have a webfile, read the file header first - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_f_webfile && m_controlCounter != 100){ + if(InBuff.bufferFilled() < maxFrameSize) return; + if(m_codec == CODEC_WAV){ + m_controlCounter = 100; + } + if(m_codec == CODEC_MP3){ + int res = read_MP3_Header(InBuff.getReadPtr(), InBuff.bufferFilled()); + if(res >= 0) bytesDecoded = res; + else{m_controlCounter = 100;} // error, skip header + } + if(m_codec == CODEC_M4A){ + m_controlCounter = 100; + } + if(m_codec == CODEC_FLAC){ + m_controlCounter = 100; + } + InBuff.bytesWasRead(bytesDecoded); + return; + } + + // play audio data - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + if((InBuff.bufferFilled() >= maxFrameSize) && (f_stream == true)) { // fill > framesize? + bytesDecoded = sendBytes(InBuff.getReadPtr(), maxFrameSize); + InBuff.bytesWasRead(bytesDecoded); + } + + // have we reached the end of the webfile? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_f_webfile && byteCounter == m_contentlength){ + while(InBuff.bufferFilled() > 0){ + if(InBuff.bufferFilled() == 128){ // post tag? + if(indexOf((const char*)InBuff.getReadPtr(), "TAG", 0) == 0){ + // log_d("%s", InBuff.getReadPtr() + 3); + break; + } + // else log_v("%s", InBuff.getReadPtr()); + } + bytesDecoded = sendBytes(InBuff.getReadPtr(), InBuff.bufferFilled()); + if(bytesDecoded < 0) break; + InBuff.bytesWasRead(bytesDecoded); + } + stopSong(); // Correct close when play known length sound #74 and before callback #112 + + if(m_f_tts){ + sprintf(chbuf, "End of speech: \"%s\"", m_lastHost); + if(audio_info) audio_info(chbuf); + if(audio_eof_speech) audio_eof_speech(m_lastHost); + } + else{ + sprintf(chbuf, "End of webstream: \"%s\"", m_lastHost); + if(audio_info) audio_info(chbuf); + if(audio_eof_stream) audio_eof_stream(m_lastHost); + } + } +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::processPlayListData() { + + static bool f_entry = false; // entryflag for asx playlist + static bool f_title = false; // titleflag for asx playlist + static bool f_ref = false; // refflag for asx playlist + static bool f_begin = false; + static bool f_end = false; + + (void)f_title; // is unused yet + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_datamode == VS1053_PLAYLISTINIT) { // Initialize for receive .m3u file + // We are going to use metadata to read the lines from the .m3u file + // Sometimes this will only contain a single line + f_entry = false; + f_title = false; + f_ref = false; + f_begin = false; + f_end = false; + m_datamode = VS1053_PLAYLISTHEADER; // Handle playlist data + if(audio_info) audio_info("Read from playlist"); + } // end AUDIO_PLAYLISTINIT + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + int av = 0; + if(!m_f_ssl) av = client.available(); + else av = clientsecure.available(); + if(av < 1){ + if(f_end) return; + if(f_begin) {f_end = true;} + else return; + } + + char pl[256]; // playlistline + uint8_t b = 0; + int16_t pos = 0; + + + + while(true){ + if(!m_f_ssl) b = client.read(); + else b = clientsecure.read(); + if(b == 0xff) b = '\n'; // no more to read? send new line + if(b == '\n') {pl[pos] = 0; break;} + if(b < 0x20 || b > 0x7E) continue; + pl[pos] = b; + pos++; + if(pos == 255){pl[pos] = '\0'; log_e("playlistline oberflow"); break;} + } + + // log_i("pl=%s", pl); + + + if(strlen(pl) == 0 && m_datamode == VS1053_PLAYLISTHEADER) { + if(audio_info) audio_info("Switch to PLAYLISTDATA"); + m_datamode = VS1053_PLAYLISTDATA; // Expecting data now + return; + } + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_datamode == VS1053_PLAYLISTHEADER) { // Read header + + sprintf(chbuf, "Playlistheader: %s", pl); // Show playlistheader + if(audio_info) audio_info(chbuf); + + if(indexOf(pl, "Connection:close", 0) >= 0){ // not a playlist + m_datamode = VS1053_HEADER; + } + + int pos = indexOf(pl, "404 Not Found", 0); + if(pos >= 0) { + m_datamode = VS1053_NONE; + if(audio_info) audio_info("Error 404 Not Found"); + stopSong(); + return; + } + + pos = indexOf(pl, "404 File Not Found", 0); + if(pos >= 0) { + m_datamode = VS1053_NONE; + if(audio_info) audio_info("Error 404 File Not Found"); + stopSong(); + return; + } + + pos = indexOf(pl, ":", 0); // lowercase all letters up to the colon + if(pos >= 0) { + for(int i=0; i= 0) { + m_datamode = VS1053_NONE; + if(audio_info) audio_info("Not Found"); + stopSong(); + return; + } + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_playlistFormat == FORMAT_M3U) { + + if(indexOf(pl, "#EXTINF:", 0) >= 0) { // Info? + pos = indexOf(pl, ",", 0); // Comma in this line? + if(pos > 0) { + // Show artist and title if present in metadata + if(audio_info) audio_info(pl + pos + 1); + } + return; + } + if(startsWith(pl, "#")) { // Commentline? + return; + } + + pos = indexOf(pl, "http://:@", 0); // ":@"?? remove that! + if(pos >= 0) { + sprintf(chbuf, "Entry in playlist found: %s", (pl + pos + 9)); + connecttohost(pl + pos + 9); + return; + } + //sprintf(chbuf, "Entry in playlist found: %s", pl); + //if(audio_info) audio_info(chbuf); + pos = indexOf(pl, "http", 0); // Search for "http" + const char* host; + if(pos >= 0) { // Does URL contain "http://"? + host = (pl + pos); + connecttohost(host); + } // Yes, set new host + return; + } //m3u + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_playlistFormat == FORMAT_PLS) { + if(startsWith(pl, "File1")) { + pos = indexOf(pl, "http", 0); // File1=http://streamplus30.leonex.de:14840/; + if(pos >= 0) { // yes, URL contains "http"? + memcpy(m_lastHost, pl + pos, strlen(pl) + 1); // http://streamplus30.leonex.de:14840/; + // Now we have an URL for a stream in host. + f_ref = true; + } + } + if(startsWith(pl, "Title1")) { // Title1=Antenne Tirol + const char* plsStationName = (pl + 7); + if(audio_showstation) audio_showstation(plsStationName); + sprintf(chbuf, "StationName: \"%s\"", plsStationName); + if(audio_info) audio_info(chbuf); + f_title = true; + } + if(startsWith(pl, "Length1")) f_title = true; // if no Title is available + if((f_ref == true) && (strlen(pl) == 0)) f_title = true; + + if(indexOf(pl, "Invalid username", 0) >= 0){ // Unable to access account: Invalid username or password + m_f_running = false; + stopSong(); + m_datamode = VS1053_NONE; + return; + } + + if(f_end) { // we have both StationName and StationURL + log_d("connect to new host %s", m_lastHost); + connecttohost(m_lastHost); // Connect to it + } + return; + } // pls + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_playlistFormat == FORMAT_ASX) { // Advanced Stream Redirector + if(indexOf(pl, "", 0) >= 0) f_entry = true; // found entry tag (returns -1 if not found) + + if(f_entry) { + if(indexOf(pl, "ref href", 0) > 0) { // + pos = indexOf(pl, "http", 0); + if(pos > 0) { + char* plsURL = (pl + pos); // http://87.98.217.63:24112/stream" /> + int pos1 = indexOf(plsURL, "\"", 0); // http://87.98.217.63:24112/stream + if(pos1 > 0) { + plsURL[pos1] = '\0'; + } + memcpy(m_lastHost, plsURL, strlen(plsURL) + 1); // save url in array + log_d("m_lastHost = %s",m_lastHost); + // Now we have an URL for a stream in host. + f_ref = true; + } + } + pos = indexOf(pl, "", 0); + if(pos < 0) pos = indexOf(pl, "<Title>", 0); + if(pos >= 0) { + char* plsStationName = (pl + pos + 7); // remove <Title> + pos = indexOf(plsStationName, "</", 0); + if(pos >= 0){ + *(plsStationName +pos) = 0; // remove + } + if(audio_showstation) audio_showstation(plsStationName); + sprintf(chbuf, "StationName: \"%s\"", plsStationName); + if(audio_info) audio_info(chbuf); + f_title = true; + } + } //entry + if(indexOf(pl, "http", 0) == 0) { //url only in asx + memcpy(m_lastHost, pl, strlen(pl)); // save url in array + m_lastHost[strlen(pl)] = '\0'; + log_d("m_lastHost = %s",m_lastHost); + connecttohost(pl); + } + if(f_end) { //we have both StationName and StationURL + connecttohost(m_lastHost); // Connect to it + } + } //asx + return; + } // end AUDIO_PLAYLISTDATA +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::latinToUTF8(char* buff, size_t bufflen){ + // most stations send strings in UTF-8 but a few sends in latin. To standardize this, all latin strings are + // converted to UTF-8. If UTF-8 is already present, nothing is done and true is returned. + // A conversion to UTF-8 extends the string. Therefore it is necessary to know the buffer size. If the converted + // string does not fit into the buffer, false is returned + // utf8 bytelength: >=0xF0 3 bytes, >=0xE0 2 bytes, >=0xC0 1 byte, e.g. e293ab is ⓫ + + uint16_t pos = 0; + uint8_t ext_bytes = 0; + uint16_t len = strlen(buff); + uint8_t c; + + while(pos < len){ + c = buff[pos]; + if(c >= 0xC2) { // is UTF8 char + pos++; + if(c >= 0xC0 && buff[pos] < 0x80) {ext_bytes++; pos++;} + if(c >= 0xE0 && buff[pos] < 0x80) {ext_bytes++; pos++;} + if(c >= 0xF0 && buff[pos] < 0x80) {ext_bytes++; pos++;} + } + else pos++; + } + if(!ext_bytes) return true; // is UTF-8, do nothing + + pos = 0; + + while(buff[pos] != 0){ + len = strlen(buff); + if(buff[pos] >= 0x80 && buff[pos+1] < 0x80){ // is not UTF8, is latin? + for(int i = len+1; i > pos; i--){ + buff[i+1] = buff[i]; + } + uint8_t c = buff[pos]; + buff[pos++] = 0xc0 | ((c >> 6) & 0x1f); // 2+1+5 bits + buff[pos++] = 0x80 | ((char)c & 0x3f); // 1+1+6 bits + } + pos++; + if(pos > bufflen -3){ + buff[bufflen -1] = '\0'; + return false; // do not overwrite + } + } + return true; +} + +//--------------------------------------------------------------------------------------------------------------------- +void Audio::processAudioHeaderData() { + + int av = 0; + if(!m_f_ssl) av=client.available(); + else av= clientsecure.available(); + if(av <= 0) return; + + char hl[512]; // headerline + uint8_t b = 0; + uint16_t pos = 0; + int16_t idx = 0; + static bool f_icyname = false; + static bool f_icydescription = false; + static bool f_icyurl = false; + + while(true){ + if(!m_f_ssl) b = client.read(); + else b = clientsecure.read(); + if(b == '\n') break; + if(b == '\r') hl[pos] = 0; + if(b < 0x20) continue; + hl[pos] = b; + pos++; + if(pos == 510){ + hl[pos] = '\0'; + log_e("headerline overflow"); + break; + } + } + + if(!pos && m_f_ctseen){ // audio header complete? + m_datamode = VS1053_DATA; // Expecting data now + sprintf(chbuf, "Switch to DATA, metaint is %d", m_metaint); + if(audio_info) audio_info(chbuf); + memcpy(chbuf, m_lastHost, strlen(m_lastHost)+1); + // uint idx = indexOf(chbuf, "?", 0); + // if(idx > 0) chbuf[idx] = 0; + if(audio_lasthost) audio_lasthost(chbuf); + if(!f_icyname){if(audio_showstation) audio_showstation("");} + if(!f_icydescription){if(audio_icydescription) audio_icydescription("");} + if(!f_icyurl){if(audio_icyurl) audio_icyurl("");} + if(m_f_swm){if(audio_showstreamtitle) audio_showstreamtitle("");} + f_icyname = false; + f_icydescription = false; + f_icyurl = false; + delay(50); // #77 + return; + } + if(!pos){ + stopSong(); + if(audio_showstation) audio_showstation(""); + if(audio_icydescription) audio_icydescription(""); + if(audio_icyurl) audio_icyurl(""); + f_icyname = false; + f_icydescription = false; + f_icyurl = false; + log_e("can't see content in audioHeaderData"); + return; + } + + idx = indexOf(hl, ":", 0); // lowercase all letters up to the colon + if(idx >= 0) { + for(int i=0; i< idx; i++) { + hl[i] = toLowerCase(hl[i]); + } + } + + if(indexOf(hl, "HTTP/1.0 404", 0) >= 0) { + m_f_running = false; + stopSong(); + if(audio_info) audio_info("404 Not Found"); + return; + } + + if(indexOf(hl, "HTTP/1.1 404", 0) >= 0) { + m_f_running = false; + stopSong(); + if(audio_info) audio_info("404 Not Found"); + return; + } + + if(indexOf(hl, "ICY 401", 0) >= 0) { + m_f_running = false; + stopSong(); + if(audio_info) audio_info("ICY 401 Service Unavailable"); + return; + } + + if(indexOf(hl, "content-type:", 0) >= 0) { + if(parseContentType(hl)) m_f_ctseen = true; + } + else if(startsWith(hl, "location:")) { + int pos = indexOf(hl, "http", 0); + const char* c_host = (hl + pos); + sprintf(chbuf, "redirect to new host \"%s\"", c_host); + if(audio_info) audio_info(chbuf); + connecttohost(c_host); + } + else if(startsWith(hl, "content-disposition:")) { + int pos1, pos2; // pos3; + // e.g we have this headerline: content-disposition: attachment; filename=stream.asx + // filename is: "stream.asx" + pos1 = indexOf(hl, "filename=", 0); + if(pos1 > 0){ + pos1 += 9; + if(hl[pos1] == '\"') pos1++; // remove '\"' around filename if present + pos2 = strlen(hl); + if(hl[pos2 - 1] == '\"') hl[pos2 - 1] = '\0'; + } + log_d("Filename is %s", hl + pos1); + } + else if(startsWith(hl, "set-cookie:") || + startsWith(hl, "pragma:") || + startsWith(hl, "expires:") || + startsWith(hl, "cache-control:") || + startsWith(hl, "icy-pub:") || + startsWith(hl, "p3p:") || + startsWith(hl, "accept-ranges:") ){ + ; // do nothing + } + else if(startsWith(hl, "connection:")) { + if(indexOf(hl, "close", 0) >= 0) {; /* do nothing */} + } + else if(startsWith(hl, "icy-genre:")) { + ; // do nothing Ambient, Rock, etc + } + else if(startsWith(hl, "icy-br:")) { + const char* c_bitRate = (hl + 7); + int32_t br = atoi(c_bitRate); // Found bitrate tag, read the bitrate in Kbit + m_bitrate = br; + sprintf(chbuf, "%d", m_bitrate*1000); + if(audio_bitrate) audio_bitrate(chbuf); + } + else if(startsWith(hl, "icy-metaint:")) { + const char* c_metaint = (hl + 12); + int32_t i_metaint = atoi(c_metaint); + m_metaint = i_metaint; + if(m_metaint) m_f_swm = false; // Multimediastream + } + else if(startsWith(hl, "icy-name:")) { + char* c_icyname = (hl + 9); // Get station name + idx = 0; + while(c_icyname[idx] == ' '){idx++;} c_icyname += idx; // Remove leading spaces + idx = strlen(c_icyname); + while(c_icyname[idx] == ' '){idx--;} c_icyname[idx + 1] = 0; // Remove trailing spaces + + if(strlen(c_icyname) > 0) { + sprintf(chbuf, "icy-name: %s", c_icyname); + if(audio_info) audio_info(chbuf); + if(audio_showstation) audio_showstation(c_icyname); + f_icyname = true; + } + } + else if(startsWith(hl, "content-length:")) { + const char* c_cl = (hl + 15); + int32_t i_cl = atoi(c_cl); + m_contentlength = i_cl; + m_f_webfile = true; // Stream comes from a fileserver + sprintf(chbuf, "content-length: %i", m_contentlength); + if(audio_info) audio_info(chbuf); + } + else if(startsWith(hl, "icy-description:")) { + const char* c_idesc = (hl + 16); + while(c_idesc[0] == ' ') c_idesc++; + + latinToUTF8(hl, sizeof(hl)); // if already UTF-0 do nothing, otherwise convert to UTF-8 + + if(audio_icydescription) audio_icydescription(c_idesc); + f_icydescription = true; + } + else if((startsWith(hl, "transfer-encoding:"))){ + if(endsWith(hl, "chunked") || endsWith(hl, "Chunked") ) { // Station provides chunked transfer + m_f_chunked = true; + if(audio_info) audio_info("chunked data transfer"); + m_chunkcount = 0; // Expect chunkcount in DATA + } + } + else if(startsWith(hl, "icy-url:")) { + const char* icyurl = (hl + 8); + idx = 0; + while(icyurl[idx] == ' ') {idx ++;} icyurl += idx; // remove leading blanks + sprintf(chbuf, "icy-url: %s", icyurl); + // if(audio_info) audio_info(chbuf); + if(audio_icyurl) audio_icyurl(icyurl); + f_icyurl = true; + } + else if(startsWith(hl, "www-authenticate:")) { + if(audio_info) audio_info("authentification failed, wrong credentials?"); + m_f_running = false; + stopSong(); + } + else { + if(isascii(hl[0]) && hl[0] >= 0x20) { // all other + sprintf(chbuf, "%s", hl); + if(audio_info) audio_info(chbuf); + } + } + return; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::readMetadata(uint8_t b, bool first) { + + static uint16_t pos_ml = 0; // determines the current position in metaline + static uint16_t metalen = 0; + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(first){ + pos_ml = 0; + metalen = 0; + return true; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(!metalen) { // First byte of metadata? + metalen = b * 16 + 1; // New count for metadata including length byte + if(metalen >512){ + if(audio_info) audio_info("Metadata block to long! Skipping all Metadata from now on."); + m_f_swm = true; // expect stream without metadata + } + pos_ml = 0; chbuf[pos_ml] = 0; // Prepare for new line + } + else { + + chbuf[pos_ml] = (char) b; // Put new char in metaline + if(pos_ml < 510) pos_ml ++; + chbuf[pos_ml] = 0; + if(pos_ml == 509) log_e("metaline overflow in AUDIO_METADATA! metaline=%s", chbuf) ; + if(pos_ml == 510) { ; /* last current char in b */} + } + + if(--metalen == 0) { + if(strlen(chbuf)) { // Any info present? + // metaline contains artist and song name. For example: + // "StreamTitle='Don McLean - American Pie';StreamUrl='';" + // Sometimes it is just other info like: + // "StreamTitle='60s 03 05 Magic60s';StreamUrl='';" + // Isolate the StreamTitle, remove leading and trailing quotes if present. + // log_i("ST %s", metaline); + + latinToUTF8(chbuf, sizeof(chbuf)); // convert to UTF-8 if necessary + + int pos = indexOf(chbuf, "song_spot", 0); // remove some irrelevant infos + if(pos > 3) { // e.g. song_spot="T" MediaBaseId="0" itunesTrackId="0" + chbuf[pos] = 0; + } + + if(!m_f_localfile) showstreamtitle(chbuf); // Show artist and title if present in metadata + } + return true ; + } + return false; // end_METADATA +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::parseContentType(const char* ct) { + bool ct_seen = false; + if(indexOf(ct, "audio", 0) >= 0) { // Is ct audio? + ct_seen = true; // Yes, remember seeing this + if(indexOf(ct, "mpeg", 13) >= 0) { + m_codec = CODEC_MP3; + sprintf(chbuf, "%s, format is mp3", ct); + if(audio_info) audio_info(chbuf); //ok is likely mp3 + } + else if(indexOf(ct, "mp3", 13) >= 0) { + m_codec = CODEC_MP3; + sprintf(chbuf, "%s, format is mp3", ct); + if(audio_info) audio_info(chbuf); + } + else if(indexOf(ct, "aac", 13) >= 0) { + m_codec = CODEC_AAC; + sprintf(chbuf, "%s, format is aac", ct); + if(audio_info) audio_info(chbuf); + } + else if(indexOf(ct, "mp4", 13) >= 0) { // audio/mp4a, audio/mp4a-latm + m_codec = CODEC_M4A; + sprintf(chbuf, "%s, format is aac", ct); + if(audio_info) audio_info(chbuf); + } + else if(indexOf(ct, "m4a", 13) >= 0) { // audio/x-m4a + m_codec = CODEC_M4A; + sprintf(chbuf, "%s, format is aac", ct); + if(audio_info) audio_info(chbuf); + } + else if(indexOf(ct, "wav", 13) >= 0) { // audio/x-wav + m_codec = CODEC_WAV; + sprintf(chbuf, "%s, format is wav", ct); + if(audio_info) audio_info(chbuf); + } + else if(indexOf(ct, "ogg", 13) >= 0) { + m_codec = CODEC_OGG; + sprintf(chbuf, "ContentType %s found", ct); + if(audio_info) audio_info(chbuf); + } + else if(indexOf(ct, "flac", 13) >= 0) { // audio/flac, audio/x-flac + m_codec = CODEC_FLAC; + sprintf(chbuf, "%s, format is flac", ct); + if(audio_info) audio_info(chbuf); + } + else { + m_f_running = false; + sprintf(chbuf, "%s, unsupported audio format", ct); + if(audio_info) audio_info(chbuf); + } + } + if(indexOf(ct, "application", 0) >= 0) { // Is ct application? + ct_seen = true; // Yes, remember seeing this + uint8_t pos = indexOf(ct, "application", 0); + if(indexOf(ct, "ogg", 13) >= 0) { + m_codec = CODEC_OGG; + sprintf(chbuf, "ContentType %s found", ct + pos); + if(audio_info) audio_info(chbuf); + } + } + return ct_seen; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::stop_mp3client(){ + int v=read_register(SCI_VOL); + audiofile.close(); + m_f_localfile=false; + m_f_webstream=false; + write_register(SCI_VOL, 0); // Mute while stopping + + client.flush(); // Flush stream client + client.stop(); // Stop stream client + write_register(SCI_VOL, v); +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::setDefaults(){ + // initializationsequence + stopSong(); + initInBuff(); // initialize InputBuffer if not already done + InBuff.resetBuffer(); + client.stop(); + client.flush(); // release memory + clientsecure.stop(); + clientsecure.flush(); + m_f_ctseen=false; // Contents type not seen yet + m_metaint=0; // No metaint yet + m_LFcount=0; // For detection end of header + m_bitrate=0; // Bitrate still unknown + m_f_firstCall = true; // InitSequence for processWebstream and processLokalFile + m_controlCounter = 0; + m_f_firstchunk=true; // First chunk expected + m_f_chunked=false; // Assume not chunked + m_f_ssl=false; + m_f_swm = true; + m_f_webfile = false; + m_f_webstream = false; + m_f_tts = false; // text to speech + m_f_localfile = false; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::connecttohost(String host){ + return connecttohost(host.c_str()); +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::connecttohost(const char* host, const char* user, const char* pwd) { + // user and pwd for authentification only, can be empty + + if(strlen(host) == 0) { + if(audio_info) audio_info("Hostaddress is empty"); + return false; + } + setDefaults(); + + log_d("free heap=%d", ESP.getFreeHeap()); + + sprintf(chbuf, "Connect to new host: \"%s\"", host); + if(audio_info) audio_info(chbuf); + + // authentification + uint8_t auth = strlen(user) + strlen(pwd); + char toEncode[auth + 4]; + toEncode[0] = '\0'; + strcat(toEncode, user); + strcat(toEncode, ":"); + strcat(toEncode, pwd); + char authorization[base64_encode_expected_len(strlen(toEncode)) + 1]; + authorization[0] = '\0'; + b64encode((const char*)toEncode, strlen(toEncode), authorization); + + // initializationsequence + int16_t pos_slash; // position of "/" in hostname + int16_t pos_colon; // position of "/" in hostname + int16_t pos_ampersand; // position of "&" in hostname + uint16_t port = 80; // port number + m_f_webstream = true; + setDatamode(VS1053_HEADER); // Handle header + + if(startsWith(host, "http://")) { + host = host + 7; + m_f_ssl = false; + } + + if(startsWith(host, "https://")) { + host = host +8; + m_f_ssl = true; + port = 443; + } + + // Is it a playlist? + if(endsWith(host, ".m3u")) {m_playlistFormat = FORMAT_M3U; m_datamode = VS1053_PLAYLISTINIT;} + if(endsWith(host, ".pls")) {m_playlistFormat = FORMAT_PLS; m_datamode = VS1053_PLAYLISTINIT;} + if(endsWith(host, ".asx")) {m_playlistFormat = FORMAT_ASX; m_datamode = VS1053_PLAYLISTINIT;} + // if url ...=asx www.fantasyfoxradio.de/infusions/gr_radiostatus_panel/gr_radiostatus_player.php?id=2&p=asx + if(endsWith(host, "=asx")) {m_playlistFormat = FORMAT_ASX; m_datamode = VS1053_PLAYLISTINIT;} + if(endsWith(host, "=pls")) {m_playlistFormat = FORMAT_PLS; m_datamode = VS1053_PLAYLISTINIT;} + + // In the URL there may be an extension, like noisefm.ru:8000/play.m3u&t=.m3u + pos_slash = indexOf(host, "/", 0); + pos_colon = indexOf(host, ":", 0); + pos_ampersand = indexOf(host, "&", 0); + + char *hostwoext = NULL; // "skonto.ls.lv:8002" in "skonto.ls.lv:8002/mp3" + char *extension = NULL; // "/mp3" in "skonto.ls.lv:8002/mp3" + + if(pos_slash > 1) { + uint8_t hostwoextLen = pos_slash; + hostwoext = (char*)malloc(hostwoextLen + 1); + memcpy(hostwoext, host, hostwoextLen); + hostwoext[hostwoextLen] = '\0'; + uint8_t extLen = urlencode_expected_len(host + pos_slash); + extension = (char *)malloc(extLen); + memcpy(extension, host + pos_slash, extLen); + trim(extension); + urlencode(extension, extLen, true); + } + else{ // url has no extension + hostwoext = strdup(host); + extension = strdup("/"); + } + + if((pos_colon >= 0) && ((pos_ampersand == -1) or (pos_ampersand > pos_colon))){ + port = atoi(host+ pos_colon + 1);// Get portnumber as integer + hostwoext[pos_colon] = '\0';// Host without portnumber + } + + sprintf(chbuf, "Connect to \"%s\" on port %d, extension \"%s\"", hostwoext, port, extension); + if(audio_info) audio_info(chbuf); + + char resp[strlen(host) + strlen(authorization) + 100]; + resp[0] = '\0'; + + strcat(resp, "GET "); + strcat(resp, extension); + strcat(resp, " HTTP/1.1\r\n"); + strcat(resp, "Host: "); + strcat(resp, hostwoext); + strcat(resp, "\r\n"); + strcat(resp, "Icy-MetaData:1\r\n"); + strcat(resp, "Authorization: Basic "); + strcat(resp, authorization); + strcat(resp, "\r\n"); + strcat(resp, "Connection: keep-alive\r\n\r\n"); + + const uint32_t TIMEOUT_MS{350}; + uint32_t wtf; + if(m_f_ssl == false) { + uint32_t t = millis(); + if(client.connect(hostwoext, port, TIMEOUT_MS)) { + // client.setNoDelay(true); + client.print(resp); + uint32_t dt = millis() - t; + sprintf(chbuf, "Connected to server in %u ms", dt); + if(audio_info) audio_info(chbuf); + memcpy(m_lastHost, host, strlen(host) + 1); // Remember the current s_host + trim(m_lastHost); + m_f_running = true; + if(hostwoext) free(hostwoext); + if(extension) free(extension); + wtf = millis(); + while(!client.connected()){ + if(millis()-wtf>TIMEOUT_MS * 2){ + sprintf(chbuf, "Request %s failed! with WTF", m_lastHost); + if(audio_info) audio_info(chbuf); + return false; + break; + } + } // wait until the connection is established + return true; + } + } + + const uint32_t TIMEOUT_MS_SSL{3700}; + if(m_f_ssl == true) { + uint32_t t = millis(); + if(clientsecure.connect(hostwoext, port, TIMEOUT_MS_SSL)) { + // clientsecure.setNoDelay(true); + // if(audio_info) audio_info("SSL/TLS Connected to server"); + clientsecure.print(resp); + uint32_t dt = millis() - t; + sprintf(chbuf, "SSL has been established in %u ms, free Heap: %u bytes", dt, ESP.getFreeHeap()); + if(audio_info) audio_info(chbuf); + memcpy(m_lastHost, "https://", 8); + memcpy(m_lastHost + 8, host, strlen(host) + 1); // Remember the current s_host + m_f_running = true; + if(hostwoext) free(hostwoext); + if(extension) free(extension); + wtf = millis(); + while(!clientsecure.connected()){ + if(millis()-wtf>TIMEOUT_MS_SSL){ + sprintf(chbuf, "Request %s failed! with WTF", m_lastHost); + if(audio_info) audio_info(chbuf); + return false; + break; + } + } + return true; + } + } + sprintf(chbuf, "Request %s failed!", host); + if(audio_info) audio_info(chbuf); + if(audio_showstation) audio_showstation(""); + if(audio_showstreamtitle) audio_showstreamtitle(""); + if(audio_icydescription) audio_icydescription(""); + if(audio_icyurl) audio_icyurl(""); + m_lastHost[0] = 0; + if(hostwoext) free(hostwoext); + if(extension) free(extension); + return false; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::loadUserCode(void) { + int i = 0; + + while (i 128 && str[j] < 189 && f_C3_seen == true) { + s = ascii[str[j] - 129]; + if(s != 0) str[j] = s; // found a related ASCII sign + f_C3_seen = false; + } + i++; j++; + } + str[j] = 0; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::connecttoSD(String sdfile){ + return connecttoFS(SD, sdfile.c_str()); +} + +bool Audio::connecttoSD(const char* sdfile){ + return connecttoFS(SD, sdfile); +} + +bool Audio::connecttoFS(fs::FS &fs, const char* path) { + + if(strlen(path)>255) return false; + + char audioName[256]; + + setDefaults(); // free buffers an set defaults + + memcpy(audioName, path, strlen(path)+1); + if(audioName[0] != '/'){ + for(int i = 255; i > 0; i--){ + audioName[i] = audioName[i-1]; + } + audioName[0] = '/'; + } + if(endsWith(audioName, "\n")) audioName[strlen(audioName) -1] = 0; + + sprintf(chbuf, "Reading file: \"%s\"", audioName); + if(audio_info) {vTaskDelay(2); audio_info(chbuf);} + + if(fs.exists(audioName)) { + audiofile = fs.open(audioName); + } else { + UTF8toASCII(audioName); + if(fs.exists(audioName)) { + audiofile = fs.open(audioName); + } + } + + if(!audiofile) { + if(audio_info) {vTaskDelay(2); audio_info("Failed to open file for reading");} + return false; + } + + m_f_localfile = true; + m_file_size = audiofile.size();//TEST loop + + char* afn = strdup(audiofile.name()); // audioFileName + uint8_t dotPos = lastIndexOf(afn, "."); + for(uint8_t i = dotPos + 1; i < strlen(afn); i++){ + afn[i] = toLowerCase(afn[i]); + } + + if(endsWith(afn, ".mp3")){ // MP3 section + m_codec = CODEC_MP3; + m_f_running = true; + return true; + } // end MP3 section + + if(endsWith(afn, ".m4a")){ // M4A section, iTunes + m_codec = CODEC_M4A; + m_f_running = true; + return true; + } // end M4A section + + if(endsWith(afn, ".aac")){ // AAC section, without FileHeader + m_codec = CODEC_AAC; + m_f_running = true; + return true; + } // end AAC section + + if(endsWith(afn, ".wav")){ // WAVE section + m_codec = CODEC_WAV; + m_f_running = true; + return true; + } // end WAVE section + + if(endsWith(afn, ".flac")) { // FLAC section + m_codec = CODEC_FLAC; + m_f_running = true; + return true; + } // end FLAC section + + if(endsWith(afn, ".ogg")) { // FLAC section + m_codec = CODEC_OGG; + m_f_running = true; + return true; + } // end FLAC section + + sprintf(chbuf, "The %s format is not supported", afn + dotPos); + if(audio_info) audio_info(chbuf); + audiofile.close(); + if(afn) free(afn); + return false; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::connecttospeech(const char* speech, const char* lang){ + + setDefaults(); + char host[] = "translate.google.com.vn"; + char path[] = "/translate_tts"; + + uint16_t speechLen = strlen(speech); + uint16_t speechBuffLen = speechLen + 300; + memcpy(m_lastHost, speech, 256); + char* speechBuff = (char*)malloc(speechBuffLen); + if(!speechBuff) {log_e("out of memory"); return false;} + memcpy(speechBuff, speech, speechLen); + speechBuff[speechLen] = '\0'; + urlencode(speechBuff, speechBuffLen); + + char resp[strlen(speechBuff) + 200] = ""; + strcat(resp, "GET "); + strcat(resp, path); + strcat(resp, "?ie=UTF-8&tl="); + strcat(resp, lang); + strcat(resp, "&client=tw-ob&q="); + strcat(resp, speechBuff); + strcat(resp, " HTTP/1.1\r\n"); + strcat(resp, "Host: "); + strcat(resp, host); + strcat(resp, "\r\n"); + strcat(resp, "User-Agent: Mozilla/5.0 \r\n"); + strcat(resp, "Accept-Encoding: identity\r\n"); + strcat(resp, "Accept: text/html\r\n"); + strcat(resp, "Connection: close\r\n\r\n"); + + free(speechBuff); + + if(!clientsecure.connect(host, 443)) { + log_e("Connection failed"); + return false; + } + clientsecure.print(resp); + sprintf(chbuf, "SSL has been established, free Heap: %u bytes", ESP.getFreeHeap()); + if(audio_info) audio_info(chbuf); + + m_f_webstream = true; + m_f_running = true; + m_f_ssl = true; + m_f_tts = true; + setDatamode(VS1053_HEADER); + + return true; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::unicode2utf8(char* buff, uint32_t len){ + // converts unicode in UTF-8, buff contains the string to be converted up to len + // range U+1 ... U+FFFF + uint8_t* tmpbuff = (uint8_t*)malloc(len * 2); + if(!tmpbuff) {log_e("out of memory"); return;} + bool bitorder = false; + uint16_t j = 0; + uint16_t k = 0; + uint16_t m = 0; + uint8_t uni_h = 0; + uint8_t uni_l = 0; + + while(m < len - 1) { + if((buff[m] == 0xFE) && (buff[m + 1] == 0xFF)) { + bitorder = true; + j = m + 2; + } // LSB/MSB + if((buff[m] == 0xFF) && (buff[m + 1] == 0xFE)) { + bitorder = false; + j = m + 2; + } // MSB/LSB + m++; + } // seek for last bitorder + m = 0; + if(j > 0) { + for(k = j; k < len; k += 2) { + if(bitorder == true) { + uni_h = (uint8_t)buff[k]; + uni_l = (uint8_t)buff[k + 1]; + } + else { + uni_l = (uint8_t)buff[k]; + uni_h = (uint8_t)buff[k + 1]; + } + + uint16_t uni_hl = ((uni_h << 8) | uni_l); + + if (uni_hl < 0X80){ + tmpbuff[m] = uni_l; + m++; + } + else if (uni_hl < 0X800) { + tmpbuff[m]= ((uni_hl >> 6) | 0XC0); + m++; + tmpbuff[m] =((uni_hl & 0X3F) | 0X80); + m++; + } + else { + tmpbuff[m] = ((uni_hl >> 12) | 0XE0); + m++; + tmpbuff[m] = (((uni_hl >> 6) & 0X3F) | 0X80); + m++; + tmpbuff[m] = ((uni_hl & 0X3F) | 0X80); + m++; + } + } + } + buff[m] = 0; + memcpy(buff, tmpbuff, m); + free(tmpbuff); +} +//--------------------------------------------------------------------------------------------------------------------- +int Audio::read_MP3_Header(uint8_t *data, size_t len) { + + static size_t headerSize; + static size_t id3Size; + static uint8_t ID3version; + static int ehsz = 0; + static char frameid[5]; + static size_t framesize = 0; + static bool compressed = false; + static bool APIC_seen = false; + static size_t APIC_size = 0; + static uint32_t APIC_pos = 0; + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 0){ /* read ID3 tag and ID3 header size */ + if(m_f_localfile){ + m_contentlength = getFileSize(); + ID3version = 0; + sprintf(chbuf, "Content-Length: %u", m_contentlength); + if(audio_info) audio_info(chbuf); + } + m_controlCounter ++; + APIC_seen = false; + headerSize = 0; + ehsz = 0; + if(specialIndexOf(data, "ID3", 4) != 0) { // ID3 not found + if(audio_info) audio_info("file has no mp3 tag, skip metadata"); + m_audioDataSize = m_contentlength; + sprintf(chbuf, "Audio-Length: %u", m_audioDataSize); + if(audio_info) audio_info(chbuf); + return -1; // error, no ID3 signature found + } + ID3version = *(data + 3); + switch(ID3version){ + case 2: + m_f_unsync = (*(data + 5) & 0x80); + m_f_exthdr = false; + break; + case 3: + case 4: + m_f_unsync = (*(data + 5) & 0x80); // bit7 + m_f_exthdr = (*(data + 5) & 0x40); // bit6 extended header + break; + }; + id3Size = bigEndian(data + 6, 4, 7); // ID3v2 size 4 * %0xxxxxxx (shift left seven times!!) + id3Size += 10; + + // Every read from now may be unsync'd + sprintf(chbuf, "ID3 framesSize: %i", id3Size); + if(audio_info) audio_info(chbuf); + + sprintf(chbuf, "ID3 version: 2.%i", ID3version); + if(audio_info) audio_info(chbuf); + + if(ID3version == 2){ + m_controlCounter = 10; + } + headerSize = id3Size; + headerSize -= 10; + + return 10; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 1){ // compute extended header size if exists + m_controlCounter ++; + if(m_f_exthdr) { + if(audio_info) audio_info("ID3 extended header"); + ehsz = bigEndian(data, 4); + headerSize -= 4; + ehsz -= 4; + return 4; + } + else{ + if(audio_info) audio_info("ID3 normal frames"); + return 0; + } + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 2){ // skip extended header if exists + if(ehsz > 256) { + ehsz -=256; + headerSize -= 256; + return 256;} // Throw it away + else { + m_controlCounter ++; + headerSize -= ehsz; + return ehsz;} // Throw it away + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 3){ // read a ID3 frame, get the tag + if(headerSize == 0){ + m_controlCounter = 99; + return 0; + } + m_controlCounter ++; + frameid[0] = *(data + 0); + frameid[1] = *(data + 1); + frameid[2] = *(data + 2); + frameid[3] = *(data + 3); + frameid[4] = 0; + headerSize -= 4; + if(frameid[0] == 0 && frameid[1] == 0 && frameid[2] == 0 && frameid[3] == 0) { + // We're in padding + m_controlCounter = 98; // all ID3 metadata processed + } + return 4; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 4){ // get the frame size + m_controlCounter = 6; + + if(ID3version == 4){ + framesize = bigEndian(data, 4, 7); // << 7 + } + else { + framesize = bigEndian(data, 4); // << 8 + } + headerSize -= 4; + uint8_t flag = *(data + 4); // skip 1st flag + (void) flag; + headerSize--; + compressed = (*(data + 5)) & 0x80; // Frame is compressed using [#ZLIB zlib] with 4 bytes for 'decompressed + // size' appended to the frame header. + headerSize--; + uint32_t decompsize = 0; + if(compressed){ + log_d("iscompressed"); + decompsize = bigEndian(data + 6, 4); + headerSize -= 4; + (void) decompsize; + log_d("decompsize=%u", decompsize); + return 6 + 4; + } + return 6; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 5){ // If the frame is larger than 256 bytes, skip the rest + if(framesize > 256){ + framesize -= 256; + headerSize -= 256; + return 256; + } + else { + m_controlCounter = 3; // check next frame + headerSize -= framesize; + return framesize; + } + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 6){ // Read the value + m_controlCounter = 5; // only read 256 bytes + char value[256]; + char ch = *(data + 0); + bool isUnicode = (ch==1) ? true : false; + + if(startsWith(frameid, "APIC")) { // a image embedded in file, passing it to external function + // log_d("framesize=%i", framesize); + isUnicode = false; + if(m_f_localfile){ + APIC_seen = true; + APIC_pos = id3Size - headerSize; + APIC_size = framesize; + } + return 0; + } + + size_t fs = framesize; + if(fs >255) fs = 255; + for(int i=0; i 1) { + unicode2utf8(value, fs); // convert unicode to utf-8 U+0020...U+07FF + } + if(!isUnicode){ + uint16_t j = 0, k = 0; + j = 0; + k = 0; + while(j < fs) { + if(value[j] == 0x0A) value[j] = 0x20; // replace LF by space + if(value[j] > 0x1F) { + value[k] = value[j]; + k++; + } + j++; + } //remove non printables + if(k>0) value[k] = 0; else value[0] = 0; // new termination + } + showID3Tag(frameid, value); + return fs; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + // -- section V2.2 only , greater Vers above ---- + if(m_controlCounter == 10){ // frames in V2.2, 3bytes identifier, 3bytes size descriptor + frameid[0] = *(data + 0); + frameid[1] = *(data + 1); + frameid[2] = *(data + 2); + frameid[3] = 0; + headerSize -= 3; + size_t len = bigEndian(data + 3, 3); + headerSize -= 3; + headerSize -= len; + char value[256]; + size_t tmp = len; + if(tmp > 254) tmp = 254; + memcpy(value, (data + 7), tmp); + value[tmp+1] = 0; + chbuf[0] = 0; + + showID3Tag(frameid, value); + if(len == 0) m_controlCounter = 98; + + return 3 + 3 + len; + } + // -- end section V2.2 ----------- + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 98){ // skip all ID3 metadata (mostly spaces) + if(headerSize > 256) { + headerSize -=256; + return 256; + } // Throw it away + else { + m_controlCounter = 99; + return headerSize; + } // Throw it away + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 99){ // exist another ID3tag? + m_audioDataStart += id3Size; + vTaskDelay(30); + if((*(data + 0) == 'I') && (*(data + 1) == 'D') && (*(data + 2) == '3')) { + m_controlCounter = 0; + return 0; + } + else { + m_controlCounter = 100; // ok + m_audioDataSize = m_contentlength - m_audioDataStart; + sprintf(chbuf, "Audio-Length: %u", m_audioDataSize); + if(audio_info) audio_info(chbuf); + if(APIC_seen && audio_id3image) audio_id3image(audiofile, APIC_pos, APIC_size); + return 0; + } + } + return 0; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::showID3Tag(const char* tag, const char* value){ + + chbuf[0] = 0; + // V2.2 + if(!strcmp(tag, "CNT")) sprintf(chbuf, "Play counter: %s", value); + // if(!strcmp(tag, "COM")) sprintf(chbuf, "Comments: %s", value); + if(!strcmp(tag, "CRA")) sprintf(chbuf, "Audio encryption: %s", value); + if(!strcmp(tag, "CRM")) sprintf(chbuf, "Encrypted meta frame: %s", value); + if(!strcmp(tag, "ETC")) sprintf(chbuf, "Event timing codes: %s", value); + if(!strcmp(tag, "EQU")) sprintf(chbuf, "Equalization: %s", value); + if(!strcmp(tag, "IPL")) sprintf(chbuf, "Involved people list: %s", value); + if(!strcmp(tag, "PIC")) sprintf(chbuf, "Attached picture: %s", value); + if(!strcmp(tag, "SLT")) sprintf(chbuf, "Synchronized lyric/text: %s", value); + // if(!strcmp(tag, "TAL")) sprintf(chbuf, "Album/Movie/Show title: %s", value); + if(!strcmp(tag, "TBP")) sprintf(chbuf, "BPM (Beats Per Minute): %s", value); + if(!strcmp(tag, "TCM")) sprintf(chbuf, "Composer: %s", value); + if(!strcmp(tag, "TCO")) sprintf(chbuf, "Content type: %s", value); + if(!strcmp(tag, "TCR")) sprintf(chbuf, "Copyright message: %s", value); + if(!strcmp(tag, "TDA")) sprintf(chbuf, "Date: %s", value); + if(!strcmp(tag, "TDY")) sprintf(chbuf, "Playlist delay: %s", value); + if(!strcmp(tag, "TEN")) sprintf(chbuf, "Encoded by: %s", value); + if(!strcmp(tag, "TFT")) sprintf(chbuf, "File type: %s", value); + if(!strcmp(tag, "TIM")) sprintf(chbuf, "Time: %s", value); + if(!strcmp(tag, "TKE")) sprintf(chbuf, "Initial key: %s", value); + if(!strcmp(tag, "TLA")) sprintf(chbuf, "Language(s): %s", value); + if(!strcmp(tag, "TLE")) sprintf(chbuf, "Length: %s", value); + if(!strcmp(tag, "TMT")) sprintf(chbuf, "Media type: %s", value); + if(!strcmp(tag, "TOA")) sprintf(chbuf, "Original artist(s)/performer(s): %s", value); + if(!strcmp(tag, "TOF")) sprintf(chbuf, "Original filename: %s", value); + if(!strcmp(tag, "TOL")) sprintf(chbuf, "Original Lyricist(s)/text writer(s): %s", value); + if(!strcmp(tag, "TOR")) sprintf(chbuf, "Original release year: %s", value); + if(!strcmp(tag, "TOT")) sprintf(chbuf, "Original album/Movie/Show title: %s", value); + if(!strcmp(tag, "TP1")) sprintf(chbuf, "Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group: %s", value); + if(!strcmp(tag, "TP2")) sprintf(chbuf, "Band/Orchestra/Accompaniment: %s", value); + if(!strcmp(tag, "TP3")) sprintf(chbuf, "Conductor/Performer refinement: %s", value); + if(!strcmp(tag, "TP4")) sprintf(chbuf, "Interpreted, remixed, or otherwise modified by: %s", value); + if(!strcmp(tag, "TPA")) sprintf(chbuf, "Part of a set: %s", value); + if(!strcmp(tag, "TPB")) sprintf(chbuf, "Publisher: %s", value); + if(!strcmp(tag, "TRC")) sprintf(chbuf, "ISRC (International Standard Recording Code): %s", value); + if(!strcmp(tag, "TRD")) sprintf(chbuf, "Recording dates: %s", value); + if(!strcmp(tag, "TRK")) sprintf(chbuf, "Track number/Position in set: %s", value); + if(!strcmp(tag, "TSI")) sprintf(chbuf, "Size: %s", value); + if(!strcmp(tag, "TSS")) sprintf(chbuf, "Software/hardware and settings used for encoding: %s", value); + if(!strcmp(tag, "TT1")) sprintf(chbuf, "Content group description: %s", value); + if(!strcmp(tag, "TT2")) sprintf(chbuf, "Title/Songname/Content description: %s", value); + if(!strcmp(tag, "TT3")) sprintf(chbuf, "Subtitle/Description refinement: %s", value); + if(!strcmp(tag, "TXT")) sprintf(chbuf, "Lyricist/text writer: %s", value); + if(!strcmp(tag, "TXX")) sprintf(chbuf, "User defined text information frame: %s", value); + if(!strcmp(tag, "TYE")) sprintf(chbuf, "Year: %s", value); + if(!strcmp(tag, "UFI")) sprintf(chbuf, "Unique file identifier: %s", value); + if(!strcmp(tag, "ULT")) sprintf(chbuf, "Unsychronized lyric/text transcription: %s", value); + if(!strcmp(tag, "WAF")) sprintf(chbuf, "Official audio file webpage: %s", value); + if(!strcmp(tag, "WAR")) sprintf(chbuf, "Official artist/performer webpage: %s", value); + if(!strcmp(tag, "WAS")) sprintf(chbuf, "Official audio source webpage: %s", value); + if(!strcmp(tag, "WCM")) sprintf(chbuf, "Commercial information: %s", value); + if(!strcmp(tag, "WCP")) sprintf(chbuf, "Copyright/Legal information: %s", value); + if(!strcmp(tag, "WPB")) sprintf(chbuf, "Publishers official webpage: %s", value); + if(!strcmp(tag, "WXX")) sprintf(chbuf, "User defined URL link frame: %s", value); + + // V2.3 V2.4 tags + // if(!strcmp(tag, "COMM")) sprintf(chbuf, "Comment: %s", value); + if(!strcmp(tag, "OWNE")) sprintf(chbuf, "Ownership: %s", value); + // if(!strcmp(tag, "PRIV")) sprintf(chbuf, "Private: %s", value); + if(!strcmp(tag, "SYLT")) sprintf(chbuf, "SynLyrics: %s", value); + if(!strcmp(tag, "TALB")) sprintf(chbuf, "Album: %s", value); + if(!strcmp(tag, "TBPM")) sprintf(chbuf, "BeatsPerMinute: %s", value); + if(!strcmp(tag, "TCMP")) sprintf(chbuf, "Compilation: %s", value); + if(!strcmp(tag, "TCOM")) sprintf(chbuf, "Composer: %s", value); + if(!strcmp(tag, "TCON")) sprintf(chbuf, "ContentType: %s", value); + if(!strcmp(tag, "TCOP")) sprintf(chbuf, "Copyright: %s", value); + if(!strcmp(tag, "TDAT")) sprintf(chbuf, "Date: %s", value); + if(!strcmp(tag, "TEXT")) sprintf(chbuf, "Lyricist: %s", value); + if(!strcmp(tag, "TIME")) sprintf(chbuf, "Time: %s", value); + if(!strcmp(tag, "TIT1")) sprintf(chbuf, "Grouping: %s", value); + if(!strcmp(tag, "TIT2")) sprintf(chbuf, "Title: %s", value); + if(!strcmp(tag, "TIT3")) sprintf(chbuf, "Subtitle: %s", value); + if(!strcmp(tag, "TLAN")) sprintf(chbuf, "Language: %s", value); + if(!strcmp(tag, "TLEN")) sprintf(chbuf, "Length (ms): %s", value); + if(!strcmp(tag, "TMED")) sprintf(chbuf, "Media: %s", value); + if(!strcmp(tag, "TOAL")) sprintf(chbuf, "OriginalAlbum: %s", value); + if(!strcmp(tag, "TOPE")) sprintf(chbuf, "OriginalArtist: %s", value); + if(!strcmp(tag, "TORY")) sprintf(chbuf, "OriginalReleaseYear: %s", value); + if(!strcmp(tag, "TPE1")) sprintf(chbuf, "Artist: %s", value); + if(!strcmp(tag, "TPE2")) sprintf(chbuf, "Band: %s", value); + if(!strcmp(tag, "TPE3")) sprintf(chbuf, "Conductor: %s", value); + if(!strcmp(tag, "TPE4")) sprintf(chbuf, "InterpretedBy: %s", value); + if(!strcmp(tag, "TPOS")) sprintf(chbuf, "PartOfSet: %s", value); + if(!strcmp(tag, "TPUB")) sprintf(chbuf, "Publisher: %s", value); + if(!strcmp(tag, "TRCK")) sprintf(chbuf, "Track: %s", value); + if(!strcmp(tag, "TSSE")) sprintf(chbuf, "SettingsForEncoding: %s", value); + if(!strcmp(tag, "TRDA")) sprintf(chbuf, "RecordingDates: %s", value); + if(!strcmp(tag, "TXXX")) sprintf(chbuf, "UserDefinedText: %s", value); + if(!strcmp(tag, "TYER")) sprintf(chbuf, "Year: %s", value); + if(!strcmp(tag, "USER")) sprintf(chbuf, "TermsOfUse: %s", value); + if(!strcmp(tag, "USLT")) sprintf(chbuf, "Lyrics: %s", value); + if(!strcmp(tag, "WOAR")) sprintf(chbuf, "OfficialArtistWebpage: %s", value); + if(!strcmp(tag, "XDOR")) sprintf(chbuf, "OriginalReleaseTime: %s", value); + + latinToUTF8(chbuf, sizeof(chbuf)); + if(chbuf[0] != 0) if(audio_id3data) audio_id3data(chbuf); +} +//--------------------------------------------------------------------------------------------------------------------- +uint32_t Audio::getFileSize(){ + if (!audiofile) return 0; + return audiofile.size(); +} +//--------------------------------------------------------------------------------------------------------------------- +uint32_t Audio::getFilePos(){ + if (!audiofile) return 0; + return audiofile.position(); +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::setFilePos(uint32_t pos){ + if (!audiofile) return false; + return audiofile.seek(pos); +} +//--------------------------------------------------------------------------------------------------------------------- +uint32_t Audio::getAudioDataStartPos() { + if(!audiofile) return 0; + return m_audioDataStart; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::urlencode(char* buff, uint16_t buffLen, bool spacesOnly){ + uint16_t len = strlen(buff); + uint8_t* tmpbuff = (uint8_t*)malloc(buffLen); + if(!tmpbuff) {log_e("out of memory"); return;} + char c; + char code0; + char code1; + uint16_t j = 0; + for(int i = 0; i < len; i++) { + c = buff[i]; + if(isalnum(c)) tmpbuff[j++] = c; + else if(spacesOnly){ + if(c == ' '){ + tmpbuff[j++] = '%'; + tmpbuff[j++] = '2'; + tmpbuff[j++] = '0'; + } + else{ + tmpbuff[j++] = c; + } + } + else { + code1 = (c & 0xf) + '0'; + if((c & 0xf) > 9) code1 = (c & 0xf) - 10 + 'A'; + c = (c >> 4) & 0xf; + code0 = c + '0'; + if(c > 9) code0 = c - 10 + 'A'; + tmpbuff[j++] = '%'; + tmpbuff[j++] = code0; + tmpbuff[j++] = code1; + } + if(j == buffLen - 1){ + log_e("out of memory"); + break; + } + } + memcpy(buff, tmpbuff, j); + buff[j] ='\0'; + free(tmpbuff); +} +#endif diff --git a/yoRadio/src/audioVS1053/audioVS1053Ex.h b/yoRadio/src/audioVS1053/audioVS1053Ex.h new file mode 100644 index 0000000..4859a92 --- /dev/null +++ b/yoRadio/src/audioVS1053/audioVS1053Ex.h @@ -0,0 +1,359 @@ +/* + * vs1053_ext.h + * + * Created on: Jul 09.2017 + * Updated on: Feb 11 2022 + * Author: Wolle + */ + +#ifndef _vs1053_ext +#define _vs1053_ext + +#define AUDIOBUFFER_MULTIPLIER 13 + +#include "Arduino.h" +#include "libb64/cencode.h" +#include "SPI.h" +#include "SD.h" +#include "SD_MMC.h" +#include "SPIFFS.h" +#include "FS.h" +#include "FFat.h" +#include "WiFiClient.h" +#include "WiFiClientSecure.h" + +#include "vs1053b-patches-flac.h" + +extern __attribute__((weak)) void audio_info(const char*); +extern __attribute__((weak)) void audio_showstreamtitle(const char*); +extern __attribute__((weak)) void audio_showstation(const char*); +extern __attribute__((weak)) void audio_showstreaminfo(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*); +extern __attribute__((weak)) void audio_eof_speech(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_stream(const char*); // The webstream comes to an end + +//---------------------------------------------------------------------------------------------------------------------- + +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 * 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 = 4096; // reserved buffspace, >= one mp3 frame + size_t m_resBuffSizePSRAM = 4096; + 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 + +private: + WiFiClient client; + WiFiClientSecure clientsecure; + File audiofile; + + +private: + enum : int { VS1053_NONE, VS1053_HEADER , VS1053_DATA, VS1053_METADATA, VS1053_PLAYLISTINIT, + VS1053_PLAYLISTHEADER, VS1053_PLAYLISTDATA, VS1053_SWM, VS1053_OGG}; + enum : int { FORMAT_NONE = 0, FORMAT_M3U = 1, FORMAT_PLS = 2, FORMAT_ASX = 3}; + + enum : int { CODEC_NONE, CODEC_WAV, CODEC_MP3, CODEC_AAC, CODEC_M4A, CODEC_FLAC, CODEC_OGG, + CODEC_OGG_FLAC, CODEC_OGG_OPUS}; + +private: + uint8_t cs_pin ; // Pin where CS line is connected + uint8_t dcs_pin ; // Pin where DCS line is connected + uint8_t dreq_pin ; // Pin where DREQ line is connected + uint8_t curvol ; // Current volume setting 0..100% + + const uint8_t vs1053_chunk_size = 32 ; + // SCI Register + const uint8_t SCI_MODE = 0x0 ; + const uint8_t SCI_STATUS = 0x1 ; + const uint8_t SCI_BASS = 0x2 ; + const uint8_t SCI_CLOCKF = 0x3 ; + const uint8_t SCI_DECODE_TIME = 0x4 ; + const uint8_t SCI_AUDATA = 0x5 ; + const uint8_t SCI_WRAM = 0x6 ; + const uint8_t SCI_WRAMADDR = 0x7 ; + const uint8_t SCI_HDAT0 = 0x8 ; + const uint8_t SCI_HDAT1 = 0x9 ; + const uint8_t SCI_AIADDR = 0xA ; + const uint8_t SCI_VOL = 0xB ; + const uint8_t SCI_AICTRL0 = 0xC ; + const uint8_t SCI_AICTRL1 = 0xD ; + const uint8_t SCI_AICTRL2 = 0xE ; + const uint8_t SCI_AICTRL3 = 0xF ; + // SCI_MODE bits + const uint8_t SM_SDINEW = 11 ; // Bitnumber in SCI_MODE always on + const uint8_t SM_RESET = 2 ; // Bitnumber in SCI_MODE soft reset + const uint8_t SM_CANCEL = 3 ; // Bitnumber in SCI_MODE cancel song + const uint8_t SM_TESTS = 5 ; // Bitnumber in SCI_MODE for tests + const uint8_t SM_LINE1 = 14 ; // Bitnumber in SCI_MODE for Line input + + SPIClass* spi_VS1053 = NULL; + SPISettings VS1053_SPI; // SPI settings for this slave + + char chbuf[512]; + char m_lastHost[256]; // Store the last URL to a webstream + uint8_t m_codec = CODEC_NONE; // + uint8_t m_rev=0; // Revision + uint8_t m_playlistFormat = 0; // M3U, PLS, ASX + size_t m_file_size = 0; // size of the file + size_t m_audioDataSize = 0; // + uint32_t m_audioDataStart = 0; // in bytes + int m_id3Size=0; // length id3 tag + bool m_f_ssl=false; + uint8_t m_endFillByte ; // Byte to send when stopping song + uint16_t m_datamode=0; // Statemaschine + bool m_f_chunked = false ; // Station provides chunked transfer + bool m_f_ctseen=false; // First line of header seen or not + bool m_f_firstchunk=true; // First chunk as input + bool m_f_swm = true; // Stream without metadata + bool m_f_tts = false; // text to speech + bool m_f_webfile = false; + bool m_f_firstCall = false; // InitSequence for processWebstream and processLokalFile + int m_LFcount; // Detection of end of header + uint32_t m_chunkcount = 0 ; // Counter for chunked transfer + uint32_t m_contentlength = 0; + uint32_t m_metaint = 0; // Number of databytes between metadata + uint32_t m_t0 = 0; // store millis(), is needed for a small delay + uint16_t m_bitrate = 0; // Bitrate in kb/sec + int16_t m_btp=0; // Bytes to play + int m_metacount=0; // Number of bytes in metadata + int m_controlCounter = 0; // Status within readID3data() and readWaveHeader() + bool m_firstmetabyte=false; // True if first metabyte (counter) + bool m_f_running = false; + bool m_f_localfile = false ; // Play from local mp3-file + bool m_f_webstream = false ; // Play from URL + bool m_f_ogg=false; // Set if oggstream + bool m_f_stream_ready=false; // Set after connecttohost and first streamdata are available + bool m_f_unsync = false; + bool m_f_exthdr = false; // ID3 extended header + + const char volumetable[22]={ 0,50,60,65,70,75,80,82,84,86, + 88,90,91,92,93,94,95,96,97,98,99,100}; //22 elements +protected: + inline void DCS_HIGH() {(dcs_pin&0x20) ? GPIO.out1_w1ts.data = 1 << (dcs_pin - 32) : GPIO.out_w1ts = 1 << dcs_pin;} + inline void DCS_LOW() {(dcs_pin&0x20) ? GPIO.out1_w1tc.data = 1 << (dcs_pin - 32) : GPIO.out_w1tc = 1 << dcs_pin;} + inline void CS_HIGH() {( cs_pin&0x20) ? GPIO.out1_w1ts.data = 1 << ( cs_pin - 32) : GPIO.out_w1ts = 1 << cs_pin;} + inline void CS_LOW() {( cs_pin&0x20) ? GPIO.out1_w1tc.data = 1 << ( cs_pin - 32) : GPIO.out_w1tc = 1 << cs_pin;} + inline void await_data_request() {while(!digitalRead(dreq_pin)) NOP();} // Very short delay + inline bool data_request() {return(digitalRead(dreq_pin) == HIGH);} + + void initInBuff(); + void control_mode_on(); + void control_mode_off(); + void data_mode_on(); + void data_mode_off(); + uint16_t read_register ( uint8_t _reg ) ; + void write_register ( uint8_t _reg, uint16_t _value ); + void sdi_send_buffer ( uint8_t* data, size_t len ) ; + size_t sendBytes(uint8_t* data, size_t len); + void sdi_send_fillers ( size_t length ) ; + void wram_write ( uint16_t address, uint16_t data ) ; + uint16_t wram_read ( uint16_t address ) ; + void showstreamtitle(const char* ml); + void startSong() ; // Prepare to start playing. Call this each + // time a new song starts. + void stopSong() ; // Finish playing a song. Call this after + // the last playChunk call. + void urlencode(char* buff, uint16_t buffLen, bool spacesOnly = false); + int read_MP3_Header(uint8_t *data, size_t len); + void showID3Tag(const char* tag, const char* value); + void processLocalFile(); + void processWebStream(); + void processPlayListData(); + bool parseContentType(const char* ct); + bool latinToUTF8(char* buff, size_t bufflen); + void processAudioHeaderData(); + bool readMetadata(uint8_t b, bool first = false); + void UTF8toASCII(char* str); + void unicode2utf8(char* buff, uint32_t len); + void setDefaults(); + void loadUserCode(); + + + +public: + // Constructor. Only sets pin values. Doesn't touch the chip. Be sure to call begin()! + Audio ( uint8_t _cs_pin, uint8_t _dcs_pin, uint8_t _dreq_pin, uint8_t spi = VSPI, uint8_t mosi = 23, uint8_t miso = 19, uint8_t sclk = 18); + ~Audio(); + + void begin() ; // Begin operation. Sets pins correctly and prepares SPI bus. + void stop_mp3client(); + void setVolume(uint8_t vol); // Set the player volume.Level from 0-21, higher is louder. + void setTone(uint8_t* rtone); // Set the player baas/treble, 4 nibbles for treble gain/freq and bass gain/freq + uint8_t getVolume(); // Get the current volume setting, higher is louder. + void printDetails(const char* str); // Print configuration details to serial output. + const char* printVersion(); // Print ID and version of vs1053 chip + void softReset() ; // Do a soft reset + void loop(); + bool connecttohost(String host); + bool connecttohost(const char* host, const char* user = "", const char* pwd = ""); + bool connecttoSD(String sdfile); + bool connecttoSD(const char* sdfile); + bool connecttoFS(fs::FS &fs, const char* path); + bool connecttospeech(const char* speech, const char* lang); + uint32_t getFileSize(); + uint32_t getFilePos(); + uint32_t getAudioDataStartPos(); + bool setFilePos(uint32_t pos); + size_t bufferFilled(); + size_t bufferFree(); + bool isRunning() {/*Serial.printf("m_f_running=%d\n", m_f_running); */return m_f_running;} + void setBalance(int8_t bal = 0); + void setTone(int8_t gainLowPass, int8_t gainBandPass, int8_t gainHighPass); + + // implement several function with respect to the index of string + bool startsWith (const char* base, const char* str) { return (strstr(base, str) - base) == 0;} + bool endsWith (const char* base, const char* str) { + int blen = strlen(base); + int slen = strlen(str); + return (blen >= slen) && (0 == strcmp(base + blen - slen, str)); + } + int indexOf (const char* base, const char* str, int startIndex) { + int result; + int baselen = strlen(base); + if (strlen(str) > baselen || startIndex > baselen) result = -1; + else { + char* pos = strstr(base + startIndex, str); + if (pos == NULL) result = -1; + else result = pos - base; + } + return result; + } + int lastIndexOf(const char* base, const char* str) { + int res = -1, result = -1; + int lenBase = strlen(base); + int lenStr = strlen(str); + if(lenStr > lenBase) {return -1;} // str should not longer than base + for(int i=0; i<(lenBase - lenStr); i++){ + res = indexOf(base, str, i); + if(res > result) result = res; + } + return result; + } + 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; + } + void trim(char* s){ + uint8_t l = 0; + while(isspace(*(s + l))) l++; + for(uint16_t i = 0; i< strlen(s) - l; i++) *(s + i) = *(s + i + l); // ltrim + char* back = s + strlen(s); + while(isspace(*--back)); + *(back + 1) = '\0'; // rtrim + } + + + inline uint8_t getDatamode(){return m_datamode;} + inline void setDatamode(uint8_t dm){m_datamode=dm;} + inline uint32_t streamavail() {if(m_f_ssl==false) return client.available(); else return clientsecure.available();} +}; + +#endif diff --git a/yoRadio/src/audioVS1053/vs1053b-patches-flac.h b/yoRadio/src/audioVS1053/vs1053b-patches-flac.h new file mode 100644 index 0000000..cc9052e --- /dev/null +++ b/yoRadio/src/audioVS1053/vs1053b-patches-flac.h @@ -0,0 +1,1104 @@ +/* User application code loading tables for VS10xx */ +/* http://www.vlsi.fi/fileadmin/software/VS10XX/vs1053b-patches290.zip*/ + +#pragma once +#include + + +const uint16_t flac_plugin[] = { /* Compressed plugin */ + + 0x0007,0x0001, /*copy 1*/ + 0x8050, + 0x0006,0x001e, /*copy 30*/ + 0x2a00,0xc000,0x3e12,0xb817,0x3e14,0xf812,0x3e01,0xb811, + 0x0007,0x9717,0x0020,0xffd2,0x0030,0x11d1,0x3111,0x8024, + 0x3704,0xc024,0x3b81,0x8024,0x3101,0x8024,0x3b81,0x8024, + 0x3f04,0xc024,0x2808,0x4800,0x36f1,0x9811, + 0x0007,0x0001, /*copy 1*/ + 0x8060, + 0x0006,0x0540, /*copy 1344*/ + 0xf400,0x4095,0x0000,0x02c2,0x6124,0x0024,0x0000,0x0024, + 0x2800,0x1ac5,0x4192,0x4542,0x0000,0x0041,0x2000,0x0015, + 0x0030,0x0317,0x2000,0x0000,0x3f00,0x4024,0x2000,0x0000, + 0x0000,0x0000,0x3e12,0x3800,0x3e00,0xb804,0x0030,0x0015, + 0x0007,0x8257,0x3700,0x984c,0xf224,0x1444,0xf224,0x0024, + 0x0008,0x0002,0x2910,0x0181,0x0000,0x1bc8,0xb428,0x1402, + 0x0000,0x8004,0x2910,0x0195,0x0000,0x1bc8,0xb428,0x0024, + 0x0006,0x0095,0x2800,0x2945,0x3e13,0x780e,0x3e11,0x7803, + 0x3e13,0xf806,0x3e11,0xf801,0x3510,0xb808,0x003f,0xe004, + 0xfec4,0x3800,0x48be,0x17c3,0xfec6,0x41c2,0x48be,0x4497, + 0x4090,0x1c46,0xf06c,0x0024,0x2400,0x2580,0x6090,0x41c3, + 0x6628,0x1c47,0x0000,0x0024,0x2800,0x2449,0xf07e,0x0024, + 0xf400,0x4182,0x673a,0x1c46,0x0000,0x0024,0x2800,0x2589, + 0xf06c,0x0024,0xf400,0x41c3,0x0000,0x0024,0x4224,0x3442, + 0x2903,0xd9c0,0x4336,0x37c3,0x0000,0x1805,0x2903,0xd9c0, + 0x4508,0x40c2,0x450a,0x9808,0x0000,0x0207,0xa478,0x1bc0, + 0xc45a,0x1807,0x0030,0x03d5,0x3d01,0x5bc1,0x36f3,0xd806, + 0x3601,0x5803,0x36f3,0x0024,0x36f3,0x580e,0x0007,0x8257, + 0x0000,0x6004,0x3730,0x8024,0xb244,0x1c04,0xd428,0x3c02, + 0x0006,0xc717,0x2800,0x2d05,0x4284,0x0024,0x3613,0x3c02, + 0x0006,0xc357,0x2901,0x6ac0,0x3e11,0x5c05,0x4284,0x1bc5, + 0x0007,0x8257,0x2800,0x3285,0x0002,0x0001,0x3701,0x0024, + 0x0006,0xc357,0xb412,0x9c02,0x002e,0xe001,0x2800,0x3005, + 0x6212,0x0024,0x0000,0x0024,0x2800,0x3295,0x0000,0x0024, + 0x0030,0x0117,0x3f00,0x0024,0x3613,0x0024,0x3e10,0x3813, + 0x3e14,0x8024,0x3e04,0x8024,0x2900,0x4b40,0x0006,0x02d3, + 0x36e3,0x0024,0x3009,0x1bd3,0x0007,0x8257,0x3700,0x8024, + 0xf224,0x0024,0x0000,0x0024,0x2800,0x3491,0x3600,0x9844, + 0x2900,0x3a40,0x0000,0x3508,0x2911,0xf140,0x0000,0x0024, + 0x0030,0x0057,0x3700,0x0024,0xf200,0x4595,0x0fff,0xfe02, + 0xa024,0x164c,0x8000,0x17cc,0x3f00,0x0024,0x3500,0x0024, + 0x0021,0x6d82,0xd024,0x44c0,0x0006,0xa402,0x2800,0x3955, + 0xd024,0x0024,0x0000,0x0000,0x2800,0x3955,0x000b,0x6d57, + 0x3009,0x3c00,0x36f0,0x8024,0x36f2,0x1800,0x2000,0x0000, + 0x0000,0x0024,0x3e14,0x7810,0x3e13,0xb80d,0x3e13,0xf80a, + 0x3e10,0xb803,0x3e11,0x3805,0x3e11,0xb807,0x3e14,0xf801, + 0x3e15,0x3815,0x0001,0x000a,0x0006,0xc4d7,0xbf8e,0x9c42, + 0x3e01,0x9c03,0x0006,0xa017,0x0023,0xffd1,0x0007,0x8250, + 0x0fff,0xfd85,0x3001,0x0024,0xa45a,0x4494,0x0000,0x0093, + 0x2800,0x4091,0xf25a,0x104c,0x34f3,0x0024,0x2800,0x4091, + 0x0000,0x0024,0x3413,0x084c,0x0000,0x0095,0x3281,0xf806, + 0x4091,0x4d64,0x2400,0x42c0,0x4efa,0x9c10,0xf1eb,0x6061, + 0xfe55,0x2f66,0x5653,0x4d64,0x48b2,0xa201,0x4efa,0xa201, + 0x36f3,0x3c10,0x36f5,0x1815,0x36f4,0xd801,0x36f1,0x9807, + 0x36f1,0x1805,0x36f0,0x9803,0x36f3,0xd80a,0x36f3,0x980d, + 0x2000,0x0000,0x36f4,0x5810,0x36f3,0x0024,0x3009,0x3848, + 0x3e14,0x3811,0x3e00,0x0024,0x0000,0x4000,0x0001,0x0010, + 0x2915,0x94c0,0x0001,0xcc11,0x36f0,0x0024,0x2927,0x9e40, + 0x3604,0x1811,0x3613,0x0024,0x3e14,0x3811,0x3e00,0x0024, + 0x0000,0x4000,0x0001,0x0010,0x2915,0x94c0,0x0001,0xcc11, + 0x36f0,0x0024,0x36f4,0x1811,0x3009,0x1808,0x2000,0x0000, + 0x0000,0x190d,0x3613,0x0024,0x3e22,0xb815,0x3e05,0xb814, + 0x3615,0x0024,0x0000,0x800a,0x3e13,0x7801,0x3e10,0xb803, + 0x3e11,0x3805,0x3e11,0xb807,0x3e14,0x3811,0x3e14,0xb813, + 0x3e03,0xf80e,0xb488,0x44d5,0x3543,0x134c,0x34e5,0xc024, + 0x3524,0x8024,0x35a4,0xc024,0x3710,0x8a0c,0x3540,0x4a0c, + 0x3d44,0x8024,0x3a10,0x8024,0x3590,0x0024,0x4010,0x15c1, + 0x6010,0x3400,0x3710,0x8024,0x2800,0x5704,0x3af0,0x8024, + 0x3df0,0x0024,0x3591,0x4024,0x3530,0x4024,0x4192,0x4050, + 0x6100,0x1482,0x4020,0x1753,0xbf8e,0x1582,0x4294,0x4011, + 0xbd86,0x408e,0x2400,0x550e,0xfe6d,0x2819,0x520e,0x0a00, + 0x5207,0x2819,0x4fbe,0x0024,0xad56,0x904c,0xaf5e,0x1010, + 0xf7d4,0x0024,0xf7fc,0x2042,0x6498,0x2046,0x3cf4,0x0024, + 0x3400,0x170c,0x4090,0x1492,0x35a4,0xc024,0x2800,0x4f95, + 0x3c00,0x0024,0x4480,0x914c,0x36f3,0xd80e,0x36f4,0x9813, + 0x36f4,0x1811,0x36f1,0x9807,0x36f1,0x1805,0x36f0,0x9803, + 0x36f3,0x5801,0x3405,0x9014,0x36e3,0x0024,0x2000,0x0000, + 0x36f2,0x9815,0x2814,0x9c91,0x0000,0x004d,0x2814,0x9940, + 0x003f,0x0013,0x3e12,0xb817,0x3e12,0x7808,0x3e11,0xb811, + 0x3e15,0x7810,0x3e18,0xb823,0x3e18,0x3821,0x3e10,0x3801, + 0x48b2,0x0024,0x3e10,0x3801,0x3e11,0x3802,0x3009,0x3814, + 0x0030,0x0717,0x3f05,0xc024,0x0030,0x0351,0x3100,0x0024, + 0x4080,0x0024,0x0030,0x10d1,0x2800,0x6745,0x0001,0x800a, + 0x0006,0x6514,0x3111,0x8024,0x6894,0x13c1,0x6618,0x0024, + 0xfe44,0x1000,0x4cb2,0x0406,0x3c10,0x0024,0x3c50,0x4024, + 0x34f0,0x4024,0x661c,0x1040,0xfe64,0x0024,0x4cb2,0x0024, + 0x3cf0,0x4024,0xbc82,0x3080,0x0030,0x0351,0x3100,0x8024, + 0xfea8,0x0024,0x5ca2,0x0024,0x0000,0x0182,0xac22,0x0024, + 0xf7c8,0x0024,0x48b2,0x0024,0xac22,0x0024,0x2800,0x6ac0, + 0xf7cc,0x1002,0x0030,0x0394,0x3400,0x4024,0x3100,0x184c, + 0x0006,0xc051,0x291e,0x8080,0x0006,0x6410,0x4088,0x1001, + 0x0030,0x1111,0x3100,0x184c,0x0006,0xc051,0x291e,0x8080, + 0x0006,0x6550,0x0006,0x6694,0x408c,0x1002,0xf224,0x0024, + 0x0006,0xa017,0x2800,0x6ed5,0x0000,0x0024,0x2808,0x3f41, + 0x0006,0x6410,0x3050,0x0024,0x3000,0x4024,0x6014,0x0024, + 0x0000,0x0024,0x2800,0x6e19,0x0000,0x0024,0xf400,0x4040, + 0x38b0,0x0024,0x2808,0x3f40,0x3800,0x0024,0x2800,0x70c1, + 0xf224,0x0024,0x0000,0x0024,0x2808,0x3f45,0x4684,0x4106, + 0xf12c,0x0024,0xf148,0x0024,0x846c,0x0024,0x2808,0x3f40, + 0xf400,0x4184,0x3e12,0xb817,0x3e12,0x3815,0x3e05,0xb814, + 0x3625,0x0024,0x0000,0x800a,0x3e10,0x3801,0x3e10,0xb803, + 0x3e11,0x3805,0x3e11,0xb807,0x3e14,0x3811,0x0006,0xa090, + 0x2912,0x0d00,0x3e14,0xc024,0x4088,0x8000,0x4080,0x0024, + 0x0007,0x90d1,0x2800,0x7705,0x0000,0x0024,0x0007,0x9051, + 0x3100,0x4024,0x4100,0x0024,0x3900,0x0024,0x0007,0x90d1, + 0x0004,0x0000,0x31f0,0x4024,0x6014,0x0400,0x0000,0x0024, + 0x2800,0x7b51,0x4080,0x0024,0x0000,0x0000,0x2800,0x7ac5, + 0x0000,0x0024,0x0007,0x9053,0x3300,0x0024,0x4080,0x0024, + 0x0000,0x0000,0x2800,0x7b58,0x0000,0x0024,0x0007,0x9051, + 0x3900,0x0024,0x3200,0x504c,0x6410,0x0024,0x3cf0,0x0000, + 0x4080,0x0024,0x0006,0xc691,0x2800,0x9405,0x3009,0x0400, + 0x0007,0x9051,0x0000,0x1001,0x3100,0x0024,0x6012,0x0024, + 0x0006,0xc6d0,0x2800,0x8849,0x003f,0xe000,0x0006,0xc693, + 0x3900,0x0c00,0x3009,0x0001,0x6014,0x0024,0x0007,0x1ad0, + 0x2800,0x8855,0x3009,0x0000,0x4080,0x0024,0x0000,0x0301, + 0x2800,0x8245,0x4090,0x0024,0x0000,0x0024,0x2800,0x8355, + 0x0000,0x0024,0x3009,0x0000,0xc012,0x0024,0x2800,0x8840, + 0x3009,0x2001,0x3009,0x0000,0x6012,0x0024,0x0000,0x0341, + 0x2800,0x8555,0x0000,0x0024,0x6190,0x0024,0x2800,0x8840, + 0x3009,0x2000,0x6012,0x0024,0x0000,0x0381,0x2800,0x8715, + 0x0000,0x0024,0x6190,0x0024,0x2800,0x8840,0x3009,0x2000, + 0x6012,0x0024,0x0000,0x00c0,0x2800,0x8855,0x0000,0x0024, + 0x3009,0x2000,0x0006,0xa090,0x3009,0x0000,0x4080,0x0024, + 0x0000,0x0081,0x2800,0x8d15,0x0007,0x8c13,0x3300,0x104c, + 0xb010,0x0024,0x0002,0x8001,0x2800,0x8f85,0x34f0,0x0024, + 0x2800,0x8d00,0x0000,0x0024,0x0006,0xc351,0x3009,0x0000, + 0x6090,0x0024,0x3009,0x2000,0x2900,0x0b80,0x3009,0x0405, + 0x0006,0xc690,0x0006,0xc6d1,0x3009,0x0000,0x3009,0x0401, + 0x6014,0x0024,0x0006,0xa093,0x2800,0x8b91,0xb880,0x0024, + 0x2800,0x9cc0,0x3009,0x2c00,0x4040,0x0024,0x6012,0x0024, + 0x0006,0xc6d0,0x2800,0x9cd8,0x0000,0x0024,0x0006,0xc693, + 0x3009,0x0c00,0x3009,0x0001,0x6014,0x0024,0x0006,0xc350, + 0x2800,0x9cc1,0x0000,0x0024,0x6090,0x0024,0x3009,0x2c00, + 0x3009,0x0005,0x2900,0x0b80,0x0000,0x9cc8,0x3009,0x0400, + 0x4080,0x0024,0x0003,0x8000,0x2800,0x9cc5,0x0000,0x0024, + 0x6400,0x0024,0x0000,0x0081,0x2800,0x9cc9,0x0000,0x0024, + 0x0007,0x8c13,0x3300,0x0024,0xb010,0x0024,0x0006,0xc650, + 0x2800,0x9cd5,0x0000,0x0024,0x0001,0x0002,0x3413,0x0000, + 0x3009,0x0401,0x4010,0x8406,0x0000,0x0281,0xa010,0x13c1, + 0x4122,0x0024,0x0000,0x03c2,0x6122,0x8002,0x462c,0x0024, + 0x469c,0x0024,0xfee2,0x0024,0x48be,0x0024,0x6066,0x8400, + 0x0006,0xc350,0x2800,0x9cc1,0x0000,0x0024,0x4090,0x0024, + 0x3009,0x2400,0x2900,0x0b80,0x3009,0x0005,0x0007,0x1b50, + 0x2912,0x0d00,0x3613,0x0024,0x3a00,0x0380,0x4080,0x0024, + 0x0000,0x00c1,0x2800,0xa585,0x3009,0x0000,0xb010,0x008c, + 0x4192,0x0024,0x6012,0x0024,0x0006,0xf051,0x2800,0xa398, + 0x3009,0x0400,0x0007,0x1fd1,0x30e3,0x0400,0x4080,0x0024, + 0x0000,0x0301,0x2800,0xa585,0x3009,0x0000,0xb010,0x0024, + 0x0000,0x0101,0x6012,0x0024,0x0006,0xf051,0x2800,0xa595, + 0x0000,0x0024,0x3023,0x0400,0xf200,0x184c,0xb880,0xa400, + 0x3009,0x2000,0x3009,0x0441,0x3e10,0x4402,0x2909,0xa9c0, + 0x3e10,0x8024,0x36e3,0x0024,0x36f4,0xc024,0x36f4,0x1811, + 0x36f1,0x9807,0x36f1,0x1805,0x36f0,0x9803,0x36f0,0x1801, + 0x3405,0x9014,0x36f3,0x0024,0x36f2,0x1815,0x2000,0x0000, + 0x36f2,0x9817,0x3613,0x0024,0x3e12,0xb817,0x3e12,0x3815, + 0x3e05,0xb814,0x3615,0x0024,0x0000,0x800a,0x3e10,0xb803, + 0x0012,0x5103,0x3e11,0x3805,0x3e11,0xb807,0x3e14,0x380d, + 0x0030,0x0250,0x3e13,0xf80e,0xbe8b,0x83e0,0x290c,0x4840, + 0x3613,0x0024,0x290c,0x4840,0x4086,0x984c,0x0000,0x00ce, + 0x2400,0xaf8e,0x3009,0x1bc0,0x0000,0x01c3,0xae3a,0x184c, + 0x0000,0x0043,0x3009,0x3842,0x290c,0x4840,0x3009,0x3840, + 0x4084,0x9bc0,0xfe26,0x9bc2,0xceba,0x0024,0x4e8e,0x0024, + 0x4e9a,0x0024,0x4f8e,0x0024,0x0000,0x0102,0x2800,0xb4c5, + 0x0030,0x0010,0x0000,0x0206,0x3613,0x0024,0x290c,0x4840, + 0x3009,0x3840,0x3000,0xdbc0,0xb366,0x0024,0x0000,0x0024, + 0x2800,0xb4d5,0x4e8e,0x0024,0x4e9a,0x0024,0x4f8e,0x0024, + 0x0030,0x0010,0x2800,0xb195,0x0000,0x0206,0x36f3,0xd80e, + 0x36f4,0x180d,0x36f1,0x9807,0x36f1,0x1805,0x36f0,0x9803, + 0x3405,0x9014,0x36f3,0x0024,0x36f2,0x1815,0x2000,0x0000, + 0x36f2,0x9817,0x3e10,0xb812,0x3e11,0xb810,0x3e12,0x0024, + 0x0006,0x9f92,0x0025,0xffd0,0x3e04,0x4bd1,0x3181,0xf847, + 0xb68c,0x4440,0x3009,0x0802,0x6024,0x3806,0x0006,0x8a10, + 0x2903,0xa905,0x0000,0xb948,0x0000,0x0800,0x6101,0x1602, + 0xaf2e,0x0024,0x4214,0x1be3,0xaf0e,0x1811,0x0fff,0xfc00, + 0xb200,0x9bc7,0x0000,0x03c0,0x2800,0xbd85,0xb204,0xa002, + 0x2903,0x6b00,0x3613,0x2002,0x4680,0x1bc8,0x36f1,0x9810, + 0x2000,0x0000,0x36f0,0x9812,0x2a08,0x1b8e,0x2803,0x8c80, + 0x0000,0xbe97,0x0006,0xd397,0x2000,0x0000,0x3f00,0x0024, + 0x0007,0x0001, /*copy 1*/ + 0x8300, + 0x0006,0x191e, /*copy 6430*/ + 0x0030,0x0055,0xb080,0x1402,0x0fdf,0xffc1,0x0007,0x9257, + 0xb212,0x3c00,0x3d00,0x4024,0x0006,0x0097,0x3f10,0x0024, + 0x3f00,0x0024,0x0030,0x0297,0x3f00,0x0024,0x0007,0x9017, + 0x3f00,0x0024,0x0007,0x81d7,0x3f10,0x0024,0xc090,0x3c00, + 0x0006,0x0297,0xb080,0x3c00,0x0000,0x0401,0x000a,0x1055, + 0x0006,0x0017,0x3f10,0x3401,0x000a,0x2795,0x3f00,0x3401, + 0x0001,0x6a97,0xf400,0x55c0,0x0000,0x0817,0xb080,0x57c0, + 0x0014,0x958f,0x0000,0x5b4e,0x0030,0x0017,0x3700,0x0024, + 0x0004,0x0001,0xb012,0x0024,0x0000,0x004d,0x280f,0xe115, + 0x0006,0x2016,0x0006,0x01d7,0x3f00,0x0024,0x0000,0x190d, + 0x000f,0xf94f,0x0000,0xcd0e,0x280f,0xe100,0x0006,0x2016, + 0x0000,0x0080,0x0005,0x4f92,0x2909,0xf840,0x3613,0x2800, + 0x0006,0x0197,0x0006,0xa115,0xb080,0x0024,0x3f00,0x3400, + 0x0007,0x8a57,0x3700,0x0024,0x4080,0x0024,0x0000,0x0040, + 0x2800,0xced5,0x0006,0xa2d7,0x3009,0x3c00,0x0006,0xa157, + 0x3009,0x1c00,0x0006,0x01d7,0x0000,0x190d,0x000a,0x708f, + 0x0000,0xd7ce,0x290b,0x1a80,0x3f00,0x184c,0x0030,0x0017, + 0x4080,0x1c01,0x0000,0x0200,0x2800,0xcb15,0xb102,0x0024, + 0x0000,0xcd08,0x2800,0xcb15,0x0000,0xd3ce,0x0011,0x210f, + 0x0000,0x190d,0x280f,0xcb00,0x3613,0x0024,0x0006,0xa115, + 0x0006,0x01d7,0x37f0,0x1401,0x6100,0x1c01,0x4012,0x0024, + 0x0000,0x8000,0x6010,0x0024,0x34f3,0x0400,0x2800,0xd698, + 0x0000,0x0024,0x0000,0x8001,0x6010,0x3c01,0x0000,0x000d, + 0x2811,0x8259,0x0000,0x0024,0x2a11,0x2100,0x0030,0x0257, + 0x3700,0x0024,0x4080,0x0024,0x0000,0x0024,0x2800,0xd9d5, + 0x0006,0x0197,0x0006,0xa115,0x3f00,0x3400,0x003f,0xc000, + 0xb600,0x41c1,0x0012,0x5103,0x000c,0xc002,0xdcd6,0x0024, + 0x0019,0xd4c2,0x2800,0xa845,0x0001,0x1188,0x0013,0xd9c3, + 0x6fd6,0x0024,0x0000,0x190d,0x2800,0xdf95,0x0014,0x1b01, + 0x0020,0x480f,0x0000,0xde4e,0x0000,0x190d,0x2820,0x41c0, + 0x0001,0x1188,0x0039,0x324f,0x0001,0x3e8e,0x2820,0x4a18, + 0xb882,0x0024,0x2a20,0x48c0,0x003f,0xfd00,0xb700,0x0024, + 0x003f,0xf901,0x6010,0x0024,0x0000,0x0024,0x280a,0xc505, + 0x0000,0x190d,0x0014,0x1b01,0x0015,0x59c0,0x6fc2,0x0024, + 0x0019,0x9301,0x2800,0xe9d5,0x0018,0x50c0,0x290c,0x4840, + 0x3613,0x0024,0x290c,0x4840,0x4086,0x184c,0x0000,0x18c2, + 0x6234,0x0024,0x0000,0x1d02,0x2800,0xe5d5,0x6234,0x0024, + 0x0030,0x0317,0x2800,0xeb40,0x3f00,0x0024,0x0000,0x1d82, + 0x2800,0xe855,0x6234,0x0024,0x2912,0x0d00,0x4084,0x184c, + 0xf200,0x0024,0x6200,0x0024,0x0006,0x0017,0x2800,0xe540, + 0xb080,0x3c40,0x0000,0x0202,0x2800,0xeb55,0xa024,0x0024, + 0xc020,0x0024,0x2800,0xe540,0x0030,0x02d7,0x6fc2,0x0024, + 0x0000,0x0024,0x2800,0xeb55,0x0000,0x0024,0x2803,0x2240, + 0x000a,0xcac8,0x000a,0x8c8f,0x0000,0xec8e,0x000c,0x0981, + 0x280a,0x71c0,0x002c,0x9d40,0x000a,0x708f,0x0000,0xd7ce, + 0x280a,0xc0d5,0x0012,0x5182,0x6fd6,0x0024,0x003f,0xfd81, + 0x280a,0x8e45,0xb710,0x0024,0xb710,0x0024,0x003f,0xfc01, + 0x6012,0x0024,0x0000,0x0101,0x2801,0x0855,0xffd2,0x0024, + 0x48b2,0x0024,0x4190,0x0024,0x0000,0x190d,0x2801,0x0855, + 0x0030,0x0250,0xb880,0x104c,0x3cf0,0x0024,0x0010,0x5500, + 0xb880,0x23c0,0xb882,0x2000,0x0007,0x8590,0x2914,0xbec0, + 0x0000,0x0440,0x0007,0x8b50,0xb880,0x0024,0x2920,0x0100, + 0x3800,0x0024,0x2920,0x0000,0x0006,0x8a91,0x0000,0x0800, + 0xb880,0xa440,0x003f,0xfd81,0xb710,0xa7c0,0x003f,0xfc01, + 0x6012,0x0024,0x0000,0x0101,0x2801,0x1195,0x0000,0x0024, + 0xffe2,0x0024,0x48b2,0x0024,0x4190,0x0024,0x0000,0x0024, + 0x2801,0x1195,0x0000,0x0024,0x2912,0x2d80,0x0000,0x0780, + 0x4080,0x0024,0x0006,0x8a90,0x2801,0x1195,0x0000,0x01c2, + 0xb886,0x8040,0x3613,0x03c1,0xbcd2,0x0024,0x0030,0x0011, + 0x2800,0xfe15,0x003f,0xff42,0xb886,0x8040,0x3009,0x03c1, + 0x0000,0x0020,0xac22,0x0024,0x0000,0x0102,0x6cd2,0x0024, + 0x3e10,0x0024,0x2909,0x8c80,0x3e00,0x4024,0x36f3,0x0024, + 0x3e11,0x8024,0x3e01,0xc024,0x2901,0x3540,0x0000,0x0201, + 0xf400,0x4512,0x2900,0x0c80,0x3213,0x1b8c,0x3100,0x0024, + 0xb010,0x0024,0x0000,0x0024,0x2801,0x1195,0x0000,0x0024, + 0x291a,0x8a40,0x0000,0x0100,0x2920,0x0200,0x3633,0x0024, + 0x2920,0x0280,0x0000,0x0401,0x408e,0x0024,0x2920,0x0280, + 0x0000,0x0401,0x003f,0xfd81,0xb710,0x4006,0x003f,0xfc01, + 0x6012,0x0024,0x0000,0x0101,0x2801,0x1195,0x0000,0x0024, + 0xffe2,0x0024,0x48b2,0x0024,0x4190,0x0024,0x0000,0x0024, + 0x2801,0x1195,0x0000,0x0024,0x2912,0x2d80,0x0000,0x0780, + 0x4080,0x0024,0x0000,0x01c2,0x2800,0xfa05,0x0006,0x8a90, + 0x2a01,0x1180,0x2920,0x0100,0x0000,0x0401,0x0000,0x0180, + 0x2920,0x0200,0x3613,0x0024,0x2920,0x0280,0x3613,0x0024, + 0x0000,0x0401,0x2920,0x0280,0x4084,0x984c,0x0019,0x9d01, + 0x6212,0x0024,0x001e,0x5c01,0x2801,0x0cd5,0x6012,0x0024, + 0x0000,0x0024,0x2801,0x0ec5,0x0000,0x0024,0x001b,0x5bc1, + 0x6212,0x0024,0x001b,0xdd81,0x2801,0x1295,0x6012,0x0024, + 0x0000,0x0024,0x2801,0x1295,0x0000,0x0024,0x0000,0x004d, + 0x000a,0xbf4f,0x280a,0xb880,0x0001,0x0fce,0x0020,0xfb4f, + 0x0000,0x190d,0x0001,0x16ce,0x2920,0xf440,0x3009,0x2bc1, + 0x291a,0x8a40,0x36e3,0x0024,0x0000,0x190d,0x000a,0x708f, + 0x280a,0xcac0,0x0000,0xd7ce,0x0030,0x0017,0x3700,0x4024, + 0x0000,0x0200,0xb102,0x0024,0x0000,0x00c0,0x2801,0x15c5, + 0x0005,0x4f92,0x2909,0xf840,0x3613,0x2800,0x0006,0x0197, + 0x0006,0xa115,0xb080,0x0024,0x3f00,0x3400,0x0000,0x190d, + 0x000a,0x708f,0x280a,0xc0c0,0x0000,0xd7ce,0x0000,0x004d, + 0x0020,0xfe0f,0x2820,0xfb40,0x0001,0x17ce,0x2801,0x1995, + 0x3009,0x1000,0x6012,0x93cc,0x0000,0x0024,0x2801,0x3445, + 0x0000,0x0024,0x3413,0x0024,0x34b0,0x0024,0x4080,0x0024, + 0x0000,0x0200,0x2801,0x1c95,0xb882,0x0024,0x3453,0x0024, + 0x3009,0x13c0,0x4080,0x0024,0x0000,0x0200,0x2801,0x3445, + 0x0000,0x0024,0xb882,0x130c,0x0000,0x004d,0x0021,0x058f, + 0x2821,0x0340,0x0001,0x1d8e,0x2801,0x2dd5,0x6012,0x0024, + 0x0000,0x0024,0x2801,0x2dd5,0x0000,0x0024,0x34c3,0x184c, + 0x3e13,0xb80f,0xf400,0x4500,0x0026,0x9dcf,0x0001,0x218e, + 0x0000,0xfa0d,0x2926,0x8e80,0x3e10,0x110c,0x36f3,0x0024, + 0x2801,0x2dc0,0x36f3,0x980f,0x001c,0xdd00,0x001c,0xd901, + 0x6ec2,0x0024,0x001c,0xdd00,0x2801,0x2495,0x0018,0xdbc1, + 0x3413,0x184c,0xf400,0x4500,0x2926,0xc640,0x3e00,0x13cc, + 0x2801,0x2b80,0x36f3,0x0024,0x6ec2,0x0024,0x003f,0xc000, + 0x2801,0x2715,0x002a,0x4001,0x3413,0x184c,0xf400,0x4500, + 0x2926,0xafc0,0x3e00,0x13cc,0x2801,0x2b80,0x36f3,0x0024, + 0xb400,0x0024,0xd100,0x0024,0x0000,0x0024,0x2801,0x2b85, + 0x0000,0x0024,0x3613,0x0024,0x3e11,0x4024,0x2926,0x8540, + 0x3e01,0x0024,0x4080,0x1b8c,0x0000,0x0024,0x2801,0x2b85, + 0x0000,0x0024,0x3413,0x184c,0xf400,0x4500,0x2926,0x8e80, + 0x3e10,0x13cc,0x36f3,0x0024,0x3110,0x8024,0x31f0,0xc024, + 0x0000,0x4000,0x0000,0x0021,0x6d06,0x0024,0x3110,0x8024, + 0x2826,0xa8c4,0x31f0,0xc024,0x2a26,0xad00,0x34c3,0x184c, + 0x3410,0x8024,0x3430,0xc024,0x0000,0x4000,0x0000,0x0021, + 0x6d06,0x0024,0x0000,0x0024,0x2801,0x3454,0x4d06,0x0024, + 0x0000,0x0200,0x2922,0x1885,0x0001,0x32c8,0x0000,0x0200, + 0x3e10,0x8024,0x2921,0xca80,0x3e00,0xc024,0x291a,0x8a40, + 0x0000,0x0024,0x2922,0x1880,0x36f3,0x0024,0x0000,0x004d, + 0x0021,0x0ecf,0x2821,0x0bc0,0x0001,0x33ce,0x2801,0x16c0, + 0x3c30,0x4024,0x0000,0x190d,0x0000,0x458e,0x2821,0x0f80, + 0x0027,0x9e0f,0x0020,0xcd4f,0x2820,0xc780,0x0001,0x360e, + 0x0006,0xf017,0x0000,0x0015,0xb070,0xbc15,0x0000,0x458e, + 0x0027,0x9e0f,0x2820,0xcd80,0x0000,0x190d,0x3613,0x0024, + 0x3e10,0xb803,0x3e14,0x3811,0x3e11,0x3805,0x3e00,0x3801, + 0x0007,0xc390,0x0006,0xa011,0x3010,0x0444,0x3050,0x4405, + 0x6458,0x0302,0xff94,0x4081,0x0003,0xffc5,0x48b6,0x0024, + 0xff82,0x0024,0x42b2,0x0042,0xb458,0x0003,0x4cd6,0x9801, + 0xf248,0x1bc0,0xb58a,0x0024,0x6de6,0x1804,0x0006,0x0010, + 0x3810,0x9bc5,0x3800,0xc024,0x36f4,0x1811,0x36f0,0x9803, + 0x283e,0x2d80,0x0fff,0xffc3,0x2801,0x4c40,0x0000,0x0024, + 0x3413,0x0024,0x2801,0x4045,0xf400,0x4517,0x2801,0x4440, + 0x6894,0x13cc,0x37b0,0x184c,0x6090,0x1d51,0x0000,0x0910, + 0x3f00,0x060c,0x3100,0x4024,0x6016,0xb812,0x000c,0x8012, + 0x2801,0x42d1,0xb884,0x0024,0x6894,0x3002,0x0000,0x028d, + 0x003a,0x5e0f,0x0001,0x544e,0x2939,0xb0c0,0x3e10,0x93cc, + 0x4084,0x9bd2,0x4282,0x0024,0x0000,0x0040,0x2801,0x4645, + 0x4292,0x130c,0x3443,0x0024,0x2801,0x4785,0x000c,0x8390, + 0x2a01,0x4b00,0x3444,0x0024,0x3073,0x0024,0xc090,0x014c, + 0x2801,0x4b00,0x3800,0x0024,0x000c,0x4113,0xb880,0x2380, + 0x3304,0x4024,0x3800,0x05cc,0xcc92,0x05cc,0x3910,0x0024, + 0x3910,0x4024,0x000c,0x8110,0x3910,0x0024,0x39f0,0x4024, + 0x3810,0x0024,0x38d0,0x4024,0x3810,0x0024,0x38f0,0x4024, + 0x34c3,0x0024,0x3444,0x0024,0x3073,0x0024,0x3063,0x0024, + 0x3000,0x0024,0x4080,0x0024,0x0000,0x0024,0x2839,0x53d5, + 0x4284,0x0024,0x3613,0x0024,0x2801,0x4e45,0x6898,0xb804, + 0x0000,0x0084,0x293b,0x1cc0,0x3613,0x0024,0x000c,0x8117, + 0x3711,0x0024,0x37d1,0x4024,0x4e8a,0x0024,0x0000,0x0015, + 0x2801,0x5105,0xce9a,0x0024,0x3f11,0x0024,0x3f01,0x4024, + 0x000c,0x8197,0x408a,0x9bc4,0x3f15,0x4024,0x2801,0x5345, + 0x4284,0x3c15,0x6590,0x0024,0x0000,0x0024,0x2839,0x53d5, + 0x4284,0x0024,0x0000,0x0024,0x2801,0x3f18,0x458a,0x0024, + 0x2a39,0x53c0,0x003e,0x2d4f,0x283a,0x5ed5,0x0001,0x37ce, + 0x000c,0x4653,0x0000,0x0246,0xffac,0x0c01,0x48be,0x0024, + 0x4162,0x4546,0x6642,0x4055,0x3501,0x8024,0x0000,0x0087, + 0x667c,0x4057,0x000c,0x41d5,0x283a,0x62d5,0x3501,0x8024, + 0x667c,0x1c47,0x3701,0x8024,0x283a,0x62d5,0xc67c,0x0024, + 0x0000,0x0024,0x283a,0x62c5,0x0000,0x0024,0x2a3a,0x5ec0, + 0x3009,0x3851,0x3e14,0xf812,0x3e12,0xb817,0x3e11,0x8024, + 0x0006,0x0293,0x3301,0x8024,0x468c,0x3804,0x0006,0xa057, + 0x2801,0x6044,0x0006,0x0011,0x469c,0x0024,0x3be1,0x8024, + 0x2801,0x6055,0x0006,0xc392,0x3311,0x0024,0x33f1,0x2844, + 0x3009,0x2bc4,0x0030,0x04d2,0x3311,0x0024,0x3a11,0x0024, + 0x3201,0x8024,0x003f,0xfc04,0xb64c,0x0fc4,0xc648,0x0024, + 0x3a01,0x0024,0x3111,0x1fd3,0x6498,0x07c6,0x868c,0x2444, + 0x0023,0xffd2,0x3901,0x8e06,0x0030,0x0551,0x3911,0x8e06, + 0x3961,0x9c44,0xf400,0x44c6,0xd46c,0x1bc4,0x36f1,0xbc13, + 0x2801,0x69d5,0x36f2,0x9817,0x002b,0xffd2,0x3383,0x188c, + 0x3e01,0x8c06,0x0006,0xa097,0x3009,0x1c12,0x3213,0x0024, + 0x468c,0xbc12,0x002b,0xffd2,0xf400,0x4197,0x2801,0x66c4, + 0x3713,0x0024,0x2801,0x6705,0x37e3,0x0024,0x3009,0x2c17, + 0x3383,0x0024,0x3009,0x0c06,0x468c,0x4197,0x0006,0xa052, + 0x2801,0x6904,0x3713,0x2813,0x2801,0x6945,0x37e3,0x0024, + 0x3009,0x2c17,0x36f1,0x8024,0x36f2,0x9817,0x36f4,0xd812, + 0x2100,0x0000,0x3904,0x5bd1,0x2a01,0x5a0e,0x3e11,0x7804, + 0x0030,0x0257,0x3701,0x0024,0x0013,0x4d05,0xd45b,0xe0e1, + 0x0007,0xc795,0x2801,0x7155,0x0fff,0xff45,0x3511,0x184c, + 0x4488,0xb808,0x0006,0x8a97,0x2801,0x7105,0x3009,0x1c40, + 0x3511,0x1fc1,0x0000,0x0020,0xac52,0x1405,0x6ce2,0x0024, + 0x0000,0x0024,0x2801,0x7101,0x68c2,0x0024,0x291a,0x8a40, + 0x3e10,0x0024,0x2921,0xca80,0x3e00,0x4024,0x36f3,0x0024, + 0x3009,0x1bc8,0x36f0,0x1801,0x3601,0x5804,0x3e13,0x780f, + 0x3e13,0xb808,0x0008,0x9b0f,0x0001,0x740e,0x2908,0x9300, + 0x0000,0x004d,0x36f3,0x9808,0x2000,0x0000,0x36f3,0x580f, + 0x0007,0x81d7,0x3711,0x8024,0x3711,0xc024,0x3700,0x0024, + 0x0000,0x2001,0xb012,0x0024,0x0034,0x0000,0x2801,0x7985, + 0x0000,0x01c1,0x3700,0x0024,0x0002,0x0001,0xb012,0x0024, + 0x002e,0xe001,0x2801,0x7885,0x6512,0x0024,0x0034,0x0000, + 0x2801,0x7995,0x0000,0x01c1,0x0030,0x0117,0x3f00,0x0024, + 0x0014,0xc000,0x0000,0x01c1,0x4fce,0x0024,0xffea,0x0024, + 0x48b6,0x0024,0x4384,0x4097,0xb886,0x45c6,0xfede,0x0024, + 0x4db6,0x0024,0x466c,0x0024,0x0006,0xc610,0x8dd6,0x8007, + 0x0000,0x00c6,0xff6e,0x0024,0x48b2,0x0024,0x0034,0x2406, + 0xffee,0x0024,0x2914,0xaa80,0x40b2,0x0024,0xf1c6,0x0024, + 0xf1d6,0x0024,0x0000,0x0201,0x8d86,0x0024,0x61de,0x0024, + 0x0006,0xc612,0x2801,0x8001,0x0006,0xc713,0x4c86,0x0024, + 0x2912,0x1180,0x0006,0xc351,0x0006,0x0210,0x2912,0x0d00, + 0x3810,0x984c,0xf200,0x2043,0x2808,0xa000,0x3800,0x0024, + 0x3613,0x0024,0x3e12,0xb817,0x3e12,0x3815,0x3e05,0xb814, + 0x3635,0x0024,0x0000,0x800a,0x3e10,0x7802,0x3e14,0x0024, + 0x2900,0xb740,0x0000,0x0201,0x0000,0x0601,0x3413,0x184c, + 0x2903,0x7680,0x3cf0,0x0024,0x3413,0x184c,0x3400,0x3040, + 0x3009,0x33c1,0x0000,0x1fc1,0xb010,0x0024,0x6014,0x9040, + 0x0006,0x8010,0x2801,0x88d5,0x0000,0x0024,0x34e3,0x1bcc, + 0x6890,0x0024,0x2801,0x8a80,0xb880,0x2000,0x3e10,0x1381, + 0x2903,0xb8c0,0x3e00,0x4024,0x003f,0xfe41,0x36e3,0x104c, + 0x34f0,0x0024,0xa010,0x0024,0x36f4,0x0024,0x36f0,0x5802, + 0x3405,0x9014,0x36f3,0x0024,0x36f2,0x1815,0x2000,0x0000, + 0x36f2,0x9817,0x3613,0x0024,0x3e12,0xb817,0x3e12,0x3815, + 0x3e05,0xb814,0x3615,0x0024,0x0000,0x800a,0x3e10,0xb804, + 0x3e01,0x534c,0xbe8a,0x10c0,0x4080,0x0024,0x0000,0x0024, + 0x2801,0x93c5,0x0000,0x0024,0x2903,0x7680,0x4082,0x184c, + 0x4c8a,0x134c,0x0000,0x0001,0x6890,0x10c2,0x4294,0x0024, + 0xac22,0x0024,0xbec2,0x0024,0x0000,0x0024,0x2801,0x93c5, + 0x0000,0x0024,0x6890,0x134c,0xb882,0x10c2,0xac22,0x0024, + 0x4c92,0x0024,0xdc92,0x0024,0xceca,0x0024,0x4e82,0x1bc5, + 0x36f0,0x9804,0x3405,0x9014,0x36f3,0x0024,0x36f2,0x1815, + 0x2000,0x0000,0x36f2,0x9817,0x3613,0x0024,0x3e12,0xb817, + 0x3e12,0x3815,0x3e05,0xb814,0x3645,0x0024,0x0000,0x800a, + 0x3e10,0x3801,0x3e10,0xb803,0x3e11,0x3805,0x3e11,0xb807, + 0x3e14,0x104c,0x2900,0xb740,0x0000,0x0081,0x4080,0x3040, + 0x0000,0x0101,0x2801,0x9ac5,0x0000,0x0024,0x4090,0x0024, + 0x0006,0x8050,0x2801,0xaed5,0x0000,0x0024,0x2900,0xb740, + 0x3613,0x0024,0xb880,0x3000,0x2801,0xac80,0x3009,0x3380, + 0x2900,0xb740,0x4122,0x10cc,0x3cf0,0x0024,0x3001,0x0024, + 0x3400,0x0024,0x6800,0x0024,0xa408,0x9040,0x4080,0x0024, + 0x0000,0x07c1,0x2801,0xa055,0x6894,0x1380,0x6894,0x130c, + 0x3460,0x0024,0x6408,0x4481,0x4102,0x1380,0xf400,0x4052, + 0x0000,0x07c1,0x34f0,0xc024,0x6234,0x0024,0x6824,0x0024, + 0xa122,0x0024,0x6014,0x0024,0x0000,0x0141,0x2801,0xa755, + 0x0000,0x0024,0x2900,0xb740,0x3613,0x0024,0x2801,0xa5c0, + 0xb88a,0x4002,0x2901,0x8c40,0x3e00,0x8024,0x4c8e,0xa801, + 0x0000,0x0201,0x3a10,0x1bcc,0x3000,0x0024,0xb010,0x0024, + 0x0000,0x0024,0x2801,0xab55,0x659a,0x0024,0x6540,0x184c, + 0x0030,0x0010,0x2801,0xa348,0x0000,0x0024,0x2801,0xab40, + 0x36f3,0x0024,0x2801,0xaa00,0xb88a,0x0024,0x2903,0x3ec0, + 0x34d0,0x4024,0x4c8f,0xa0a1,0x0000,0x0201,0x3000,0x084c, + 0xb010,0x0024,0x0000,0x0024,0x2801,0xab55,0x659a,0x0024, + 0x6540,0x10cc,0x0030,0x0010,0x2801,0xa7c8,0x0000,0x0024, + 0x34d3,0x0024,0x3423,0x0024,0xf400,0x4510,0x3009,0x1380, + 0x6090,0x0024,0x3009,0x2000,0x6892,0x108c,0x34f0,0x9000, + 0xa122,0x984c,0x6016,0x13c1,0x0000,0x0102,0x2801,0x9c08, + 0x0006,0x8150,0x2801,0xaf40,0x3009,0x1bcc,0x6890,0x938c, + 0x3800,0x0024,0x36f4,0x0024,0x36f1,0x9807,0x36f1,0x1805, + 0x36f0,0x9803,0x36f0,0x1801,0x3405,0x9014,0x36f3,0x0024, + 0x36f2,0x1815,0x2000,0x0000,0x36f2,0x9817,0x3613,0x0024, + 0x3e12,0xb817,0x3e12,0x3815,0x3e05,0xb814,0x3615,0x0024, + 0x0000,0x800a,0x3e10,0x3801,0x3e10,0xb804,0x3e11,0xb807, + 0x3e14,0x3811,0x3e04,0x934c,0x3430,0x0024,0x4080,0x0024, + 0x0000,0x0206,0x2801,0xb845,0x0006,0x8151,0x3101,0x130c, + 0xff0c,0x1102,0x6408,0x0024,0x4204,0x0024,0xb882,0x4092, + 0x1005,0xfe02,0x48be,0x0024,0x4264,0x0024,0x2903,0xc540, + 0xf400,0x4090,0x36f4,0x8024,0x36f4,0x1811,0x36f1,0x9807, + 0x36f0,0x9804,0x36f0,0x1801,0x3405,0x9014,0x36f3,0x0024, + 0x36f2,0x1815,0x2000,0x0000,0x36f2,0x9817,0x3613,0x0024, + 0x3e12,0xb817,0x3e12,0x3815,0x3e05,0xb814,0x3675,0x0024, + 0x3643,0x0024,0x0000,0x800a,0x3e10,0x3801,0x0000,0x0181, + 0x3e10,0xb803,0x3e11,0x3806,0x3e11,0xf810,0x3e14,0x7812, + 0x3e13,0xf80e,0x2903,0x47c0,0x3e03,0x4024,0x2900,0xb740, + 0x4088,0x184c,0x3413,0x184c,0x2900,0xb740,0x6892,0x3040, + 0x4080,0x3040,0x0000,0x0000,0x2801,0xc5c5,0x0000,0x0024, + 0x6890,0x0024,0x2903,0x47c0,0x3cd0,0x0024,0x4080,0x0024, + 0x0000,0x0024,0x2801,0xc615,0x0000,0x0024,0x3433,0x0024, + 0xf400,0x4510,0x34d0,0x0024,0x6090,0x0024,0x2903,0x47c0, + 0x3800,0x0024,0x4080,0x10cc,0xf400,0x4510,0x2801,0xc385, + 0x34d0,0x0024,0x2801,0xc600,0x0000,0x0024,0x3cd0,0x0024, + 0x3433,0x0024,0x34a0,0x0024,0xf400,0x4510,0x3430,0x4024, + 0x6100,0x0024,0x0000,0x0341,0x3840,0x0024,0x3000,0x0024, + 0x6012,0x0024,0x0006,0x0581,0x2801,0xe381,0x4012,0x0024, + 0xf400,0x4057,0x3702,0x0024,0x2000,0x0000,0x0000,0x0024, + 0x34d3,0x184c,0x3430,0x8024,0x2901,0x8c40,0x3e00,0x8024, + 0x36f3,0x11cc,0xb888,0x104c,0x3c10,0x0024,0x3c90,0x4024, + 0x2801,0xcf40,0x34e3,0x0024,0x3411,0x8024,0x3491,0xc024, + 0x4f82,0x128c,0x3400,0x4024,0x4142,0x0024,0xf400,0x4050, + 0x3800,0x0024,0x3440,0x4024,0x4142,0x0024,0x6498,0x4050, + 0x3009,0x2007,0x0006,0x8150,0x3000,0x11cc,0x6402,0x104c, + 0x0000,0x0024,0x2801,0xcc88,0x0000,0x0024,0x3493,0x0024, + 0x2801,0xff40,0x34f3,0x0024,0x2801,0xd6c0,0xb888,0x0024, + 0x3430,0x8024,0x2901,0x8c40,0x3e00,0x8024,0x4c8e,0x130c, + 0x3400,0x5bcc,0x4142,0x0024,0xf400,0x4050,0x3800,0x0024, + 0x3440,0x4024,0x4142,0x0024,0xf400,0x4050,0x0000,0x0201, + 0x3009,0x2007,0x0030,0x0010,0x3000,0x0024,0xb010,0x0024, + 0x0000,0x0024,0x2801,0xff55,0x6498,0x0024,0x0006,0x8150, + 0x3000,0x134c,0x6402,0x984c,0x0000,0x0024,0x2801,0xd208, + 0x0000,0x0024,0x2801,0xff40,0x3433,0x1bcc,0x0000,0x0201, + 0xb888,0x104c,0x3430,0x184c,0x6010,0x0024,0x6402,0x3000, + 0x0000,0x0201,0x2801,0xdf58,0x0030,0x0010,0x4090,0x124c, + 0x2401,0xde40,0x0000,0x0024,0x3430,0x8024,0x2901,0x8c40, + 0x3e00,0x8024,0x4c8e,0x130c,0x3400,0x4024,0x4142,0x0024, + 0xf400,0x4050,0x3800,0x0024,0x3410,0x4024,0x4142,0x0024, + 0x6498,0x4050,0x3009,0x2007,0x0030,0x0010,0x0000,0x0201, + 0x3473,0x0024,0x3490,0x0024,0x3e00,0x13cc,0x2901,0x9580, + 0x3444,0x8024,0x3000,0x1bcc,0xb010,0x0024,0x0000,0x0024, + 0x2801,0xff55,0x0000,0x0024,0x34c3,0x184c,0x3470,0x0024, + 0x3e10,0x104c,0x34c0,0x4024,0x2901,0xb1c0,0x3e00,0x4024, + 0x2801,0xff40,0x36e3,0x0024,0x0000,0x0801,0x3413,0x0024, + 0x34f0,0x0024,0x6012,0x0024,0x0000,0x07c1,0x2801,0xfe88, + 0x0000,0x0024,0x6010,0x114c,0xb888,0x32c0,0x6402,0x0024, + 0x0000,0x0101,0x2801,0xeb18,0x0000,0x0024,0x4090,0x134c, + 0x2401,0xea40,0x3009,0x184c,0x3430,0x8024,0x2901,0x8c40, + 0x3e00,0x8024,0x4c8e,0x130c,0x3400,0x4024,0x4142,0x0024, + 0xf400,0x4050,0x3800,0x0024,0x3410,0x4024,0x4142,0x0024, + 0x6498,0x4050,0x3009,0x2007,0x0000,0x0101,0x3433,0x1bcc, + 0x2900,0xb740,0x3613,0x0024,0x0000,0x0141,0x6090,0x118c, + 0x2900,0xb740,0x3ca0,0x184c,0x3473,0x184c,0xb888,0x3380, + 0x3400,0x0024,0x6402,0x0024,0x0000,0x0201,0x2801,0xf1d8, + 0x0000,0x0024,0x4090,0x104c,0x2401,0xf100,0x0000,0x0024, + 0x34a0,0x8024,0x2901,0x8c40,0x3e00,0x8024,0x0006,0x8002, + 0x4244,0x118c,0x4244,0x0024,0x6498,0x4095,0x3009,0x3440, + 0x3009,0x37c1,0x0000,0x0201,0x34f3,0x0024,0x0030,0x0010, + 0x3490,0x0024,0x3e00,0x138c,0x2901,0x9580,0x3444,0x8024, + 0x3000,0x1bcc,0xb010,0x0024,0x0000,0x0024,0x2801,0xff55, + 0x4112,0x0024,0x3463,0x0024,0x34a0,0x0024,0x6012,0x0024, + 0x0006,0x8111,0x2801,0xfb19,0x0000,0x0024,0x3100,0x11cc, + 0x3490,0x4024,0x4010,0x0024,0x0000,0x0a01,0x6012,0x0024, + 0x0006,0x8151,0x2801,0xfb18,0x0000,0x0024,0x3613,0x114c, + 0x3101,0x3804,0x3490,0x8024,0x6428,0x138c,0x3470,0x8024, + 0x3423,0x0024,0x3420,0xc024,0x4234,0x1241,0x4380,0x4092, + 0x2903,0xc540,0x0006,0x8010,0x2801,0xff40,0x3009,0x1bcc, + 0x0006,0x8151,0x3613,0x114c,0x3101,0x3804,0x3490,0x8024, + 0x6428,0x138c,0x3470,0x8024,0x3423,0x0024,0x3420,0xc024, + 0x4234,0x1241,0x4380,0x4092,0x2903,0xcf00,0x0006,0x8010, + 0x2801,0xff40,0x3009,0x1bcc,0x0006,0x8050,0x6890,0x0024, + 0x3800,0x0024,0x3433,0x0024,0x34d0,0x0024,0x4080,0x0024, + 0x0006,0x8150,0x2802,0x04c5,0x0000,0x0024,0x3000,0x11cc, + 0xb888,0x10cc,0x6402,0x3240,0x3493,0x0024,0x3444,0x8024, + 0x2802,0x04d8,0x4090,0x0024,0x2402,0x0480,0x0000,0x0024, + 0x6499,0x2620,0xb78e,0x4001,0x0000,0x0000,0x3433,0x0024, + 0xcfce,0x1340,0xaf0e,0x0024,0x3a11,0xa807,0x36f3,0x4024, + 0x36f3,0xd80e,0x36f4,0x5812,0x36f1,0xd810,0x36f1,0x1806, + 0x36f0,0x9803,0x36f0,0x1801,0x3405,0x9014,0x36f3,0x0024, + 0x36f2,0x1815,0x2000,0x0000,0x36f2,0x9817,0x3613,0x0024, + 0x3e12,0xb817,0x3e12,0x3815,0x3e05,0xb814,0x3615,0x0024, + 0x0000,0x800a,0x3e10,0x7802,0x3e10,0xf804,0x0000,0x3fc3, + 0x3e11,0x7806,0x3e11,0xf810,0xbc82,0x12cc,0x3404,0x0024, + 0x3023,0x0024,0x3810,0x0024,0x38f0,0x4024,0x3454,0x0024, + 0x3810,0x0024,0x38f0,0x4024,0x2900,0xb740,0x0000,0x0201, + 0x0006,0x9301,0x4088,0x134c,0x3400,0x8024,0xd204,0x0024, + 0xb234,0x0024,0x4122,0x0024,0xf400,0x4055,0x3500,0x0024, + 0x3c30,0x0024,0x0000,0x2000,0xb400,0x0024,0x0000,0x3001, + 0x2802,0x1255,0x0000,0x3800,0x0000,0x0041,0xfe42,0x12cc, + 0x48b2,0x1090,0x3810,0x0024,0x38f0,0x4024,0x2802,0x3340, + 0x3430,0x0024,0xb400,0x0024,0x6012,0x0024,0x0000,0x3801, + 0x2802,0x1595,0x0000,0x3c00,0x0000,0x07c0,0x0000,0x0041, + 0xb400,0x12cc,0xfe02,0x1150,0x48b2,0x0024,0x689a,0x2040, + 0x2802,0x3200,0x38f0,0x4024,0xb400,0x0024,0x6012,0x0024, + 0x0000,0x3c01,0x2802,0x1915,0x0000,0x3e00,0x0000,0x03c0, + 0x0000,0x0085,0x4592,0x12cc,0xb400,0x1150,0xfe02,0x0024, + 0x48b2,0x0024,0x3810,0x0024,0x2802,0x3200,0x38f0,0x4024, + 0xb400,0x0024,0x6012,0x0024,0x0000,0x3e01,0x2802,0x1c95, + 0x0000,0x3f00,0x0000,0x01c0,0xf20a,0x12cc,0xb400,0x1150, + 0xf252,0x0024,0xfe02,0x0024,0x48b2,0x0024,0x3810,0x0024, + 0x2802,0x3200,0x38f0,0x4024,0xb400,0x130c,0x6012,0x0024, + 0x0000,0x3f01,0x2802,0x2015,0x4390,0x0024,0x0000,0x0041, + 0x0000,0x0105,0x4590,0x13cc,0xb400,0x1150,0xfe02,0x0024, + 0x48b2,0x0024,0x3810,0x0024,0x2802,0x3200,0x38f0,0x4024, + 0xb400,0x0024,0x6012,0x1100,0x0000,0x01c1,0x2802,0x2395, + 0x0000,0x0024,0x0000,0x0041,0x0000,0x0145,0x6890,0x12cc, + 0xb400,0x1150,0xfe02,0x0024,0x48b2,0x0024,0x3810,0x0024, + 0x2802,0x3200,0x38f0,0x4024,0x6012,0x0024,0x0000,0x3f81, + 0x2802,0x2615,0xb430,0x0024,0x6012,0x0024,0x0000,0x0024, + 0x2802,0x2615,0x0000,0x0024,0x2802,0x3200,0x0000,0x0185, + 0x2802,0x3340,0xc890,0x0024,0x0000,0x3fc3,0x0000,0x0201, + 0x34d3,0x0024,0x2900,0xb740,0x3433,0x184c,0x0006,0x9301, + 0x4088,0x134c,0x3400,0x8024,0xd204,0x0024,0xb234,0x0024, + 0x4122,0x0024,0xf400,0x4055,0x0000,0x2001,0x3500,0x0024, + 0x3c30,0x0024,0x0000,0x3000,0xb400,0x0024,0x6012,0x0024, + 0x0000,0x0182,0x2802,0x2c45,0x0000,0x0024,0x2802,0x3340, + 0xc890,0x0024,0x459a,0x12cc,0x3404,0x0024,0x3023,0x0024, + 0x3010,0x0024,0x30d0,0x4024,0xac22,0x0046,0x003f,0xf982, + 0x3011,0xc024,0x0000,0x0023,0xaf2e,0x0024,0x0000,0x0182, + 0xccf2,0x0024,0x0000,0x0fc6,0x0000,0x0047,0xb46c,0x2040, + 0xfe6e,0x23c1,0x3454,0x0024,0x3010,0x0024,0x30f0,0x4024, + 0xac22,0x0024,0xccb2,0x0024,0x3810,0x0024,0x38f0,0x4024, + 0x458a,0x134c,0x0000,0x0201,0x2802,0x2755,0x0000,0x3fc3, + 0x3430,0x0024,0x36f1,0xd810,0x36f1,0x5806,0x36f0,0xd804, + 0x36f0,0x5802,0x3405,0x9014,0x36f3,0x0024,0x36f2,0x1815, + 0x2000,0x0000,0x36f2,0x9817,0x3613,0x0024,0x3e12,0xb817, + 0x3e12,0x3815,0x3e05,0xb814,0x3675,0x0024,0x3633,0x0024, + 0x0000,0x800a,0x3e10,0x3801,0x3e10,0xb803,0x3e11,0x3805, + 0x3e11,0xb807,0x3e14,0x3811,0x3e14,0xb813,0x3e13,0xf80e, + 0x3e03,0x4024,0x2903,0xac40,0x0000,0x0381,0x000f,0xff81, + 0x6012,0x184c,0x0000,0x0201,0x2802,0x3bc5,0x0000,0x0024, + 0x2900,0xb740,0x0003,0x1f08,0x3613,0x0024,0x0000,0x0401, + 0x0006,0x8a10,0x2900,0xbf40,0xb880,0x1bcc,0xb880,0x11cc, + 0x3413,0x184c,0x3c90,0x0024,0x2900,0xb740,0x34f3,0x0024, + 0x3473,0x184c,0x3c00,0x0000,0x4080,0x0024,0x0006,0x9301, + 0x2802,0x4145,0x003f,0xfe04,0x3490,0x8024,0xa244,0x0024, + 0x2903,0x8f40,0xb880,0x0024,0x2900,0xbf40,0x003f,0xfe04, + 0x3473,0x184c,0x0006,0x8091,0x3413,0x0024,0x34f0,0x8024, + 0x3400,0xc024,0xa346,0x0024,0xd234,0x0024,0x0000,0x3fc3, + 0xb234,0x0024,0x4122,0x1042,0xf400,0x4055,0x0006,0x9301, + 0x3500,0x0024,0xd024,0x3000,0xb234,0x0024,0x4122,0x0024, + 0x6892,0x4055,0x3500,0x0024,0x3cf0,0x0024,0x34a0,0x0024, + 0xf100,0x0024,0xb010,0x0024,0x3c60,0x0024,0x34b0,0x0024, + 0xb010,0x0024,0x0000,0x0201,0x2900,0xb740,0x3ce0,0x0024, + 0x0006,0x9301,0x3473,0x184c,0x3c10,0x0024,0x34f0,0x8024, + 0x3410,0xc024,0xd234,0x0024,0x0000,0x3fc3,0xb234,0x0024, + 0x4122,0x0024,0xf400,0x4055,0x003f,0xff01,0x3500,0x0024, + 0x3cf0,0x0024,0x34c0,0x0024,0xa010,0x0024,0x0000,0x03c1, + 0x3c40,0x0024,0x34d0,0x0024,0xb010,0x0024,0x0000,0x0201, + 0x2900,0xb740,0x3cc0,0x0024,0x0006,0x9301,0x3473,0x0024, + 0x3c10,0x0024,0x34f0,0x8024,0x3410,0xc024,0xd234,0x0024, + 0x0000,0x3fc3,0xb234,0x0024,0x4122,0x0024,0xf400,0x4055, + 0x003f,0xff01,0x3500,0x0024,0x3cf0,0x0024,0x3400,0x0024, + 0xa010,0x0024,0x0000,0x01c1,0x3900,0x0024,0x34e0,0x0024, + 0xf100,0x0024,0xb010,0x0024,0x6892,0x3080,0x34f0,0x0024, + 0xb010,0x0024,0x3cb0,0x0024,0x3450,0x0024,0x34a0,0x4024, + 0xc010,0x0024,0x0000,0x0181,0x2802,0x55c5,0x3100,0x0024, + 0x6890,0x07cc,0x2803,0x1f00,0x3900,0x0024,0x6012,0x0024, + 0x0000,0x0201,0x2802,0x5758,0x0000,0x0024,0x2802,0x5a40, + 0x6090,0x044c,0x6012,0x0024,0x0000,0x0281,0x2802,0x5988, + 0x6012,0x0024,0x0000,0x0080,0x2802,0x5999,0x0000,0x0024, + 0x2802,0x5a40,0x3113,0x0024,0x6890,0x07cc,0x2803,0x1f00, + 0x3900,0x0024,0x0000,0x0201,0x3900,0x114c,0x34b0,0x0024, + 0x6012,0x0024,0x0006,0x08c1,0x2802,0x6481,0x4012,0x0024, + 0xf400,0x4057,0x3702,0x0024,0x2000,0x0000,0x0000,0x0024, + 0x2802,0x6480,0x0000,0x0024,0x0000,0x0200,0x0006,0x8110, + 0x2802,0x6480,0x3800,0x0024,0x0000,0x0300,0x0006,0x8110, + 0x2802,0x6480,0x3800,0x0024,0x0006,0x8050,0x6890,0x0024, + 0x2803,0x1f00,0x3800,0x0024,0x0000,0x0400,0x0006,0x8110, + 0x2802,0x6480,0x3800,0x0024,0x0000,0x0500,0x0006,0x8110, + 0x2802,0x6480,0x3800,0x0024,0x0000,0x0600,0x0006,0x8110, + 0x2802,0x6480,0x3800,0x0024,0x0006,0x8050,0x6890,0x0024, + 0x2803,0x1f00,0x3800,0x0024,0x3423,0x184c,0x3460,0x0024, + 0x4080,0x0024,0x0006,0x8200,0x2802,0x69c5,0x3e10,0x0024, + 0x0000,0x01c0,0x3e10,0x0024,0x3490,0x0024,0x2902,0x07c0, + 0x3e00,0x13cc,0x36d3,0x11cc,0x3413,0x0024,0x4080,0x3240, + 0x34f3,0x0024,0x2802,0x6d98,0x0000,0x0024,0x0006,0x8010, + 0x6890,0x0024,0x2803,0x1f00,0x3800,0x0024,0x0000,0x0180, + 0x3e10,0x0024,0x3490,0x0024,0x2902,0x07c0,0x3e00,0x13cc, + 0x36d3,0x11cc,0x3413,0x0024,0x4080,0x3240,0x34f3,0x0024, + 0x2802,0x6d98,0x0000,0x0024,0x0006,0x8010,0x6890,0x0024, + 0x2803,0x1f00,0x3800,0x0024,0x0000,0x0201,0x3433,0x0024, + 0x34d0,0x0024,0x6012,0x0024,0x0006,0x0ac1,0x2802,0x7f81, + 0x4012,0x0024,0xf400,0x4057,0x3702,0x0024,0x2000,0x0000, + 0x0000,0x0024,0x0006,0x8050,0x6890,0x0024,0x2803,0x1f00, + 0x3800,0x0024,0x0000,0x3000,0x2802,0x8140,0x0006,0x8150, + 0x0000,0x9000,0x0006,0x8150,0x3433,0x0024,0x34d0,0x4024, + 0x4192,0x0024,0x4192,0x0024,0x2802,0x8140,0xa010,0x0024, + 0x0000,0x0201,0x0006,0x8150,0x2900,0xb740,0x3613,0x0024, + 0x0006,0x9301,0x3473,0x0024,0x3c10,0x0024,0x34f0,0x8024, + 0x3410,0xc024,0xd234,0x0024,0x0000,0x3fc3,0xb234,0x0024, + 0x4122,0x0024,0xf400,0x4055,0x3500,0x0024,0x3cf0,0x0024, + 0x3490,0x0024,0x2802,0x8140,0x6090,0x0024,0x003f,0xfe04, + 0x0000,0x0401,0x0006,0x8150,0x2900,0xb740,0x3613,0x0024, + 0x0006,0x9301,0x3473,0x0024,0x3c10,0x0024,0x34f0,0x8024, + 0x3400,0xc024,0xa346,0x0024,0xd234,0x0024,0x0000,0x3fc3, + 0xb234,0x0024,0x4122,0x1042,0xf400,0x4055,0x0006,0x9301, + 0x3500,0x0024,0xd024,0x3000,0xb234,0x0024,0x4122,0x0024, + 0xf400,0x4055,0x3500,0x0024,0x3cf0,0x0024,0x3490,0x0024, + 0x2802,0x8140,0x6090,0x0024,0x0000,0x4000,0x0000,0x0202, + 0x0006,0x8150,0x3433,0x0024,0x34d0,0x4024,0x6122,0x0024, + 0xa010,0x0024,0x0004,0x8001,0x3800,0x110c,0x0006,0x8150, + 0x3000,0x0024,0x6012,0x1300,0x0000,0x0401,0x2802,0x8409, + 0x0000,0x0024,0x6890,0x82cc,0x2803,0x1f00,0x3800,0x0024, + 0x6012,0x0024,0x0006,0x0cc1,0x2802,0xab81,0x4012,0x0024, + 0xf400,0x4057,0x3702,0x0024,0x2000,0x0000,0x0000,0x0024, + 0x2802,0xab80,0x0000,0x0024,0x0016,0x2200,0x0006,0x8190, + 0x6892,0x2040,0x2802,0xab80,0x38f0,0x4024,0x002c,0x4400, + 0x0000,0x0081,0x0006,0x8190,0x3810,0x0024,0x2802,0xab80, + 0x38f0,0x4024,0x003b,0x8000,0x0000,0x0081,0x0006,0x8190, + 0x3810,0x0024,0x2802,0xab80,0x38f0,0x4024,0x0007,0xd000, + 0x0006,0x8190,0xb882,0x2040,0x2802,0xab80,0x38f0,0x4024, + 0x000f,0xa000,0x0006,0x8190,0xb882,0x2040,0x2802,0xab80, + 0x38f0,0x4024,0x0015,0x8880,0x0006,0x8190,0xb882,0x2040, + 0x2802,0xab80,0x38f0,0x4024,0x0017,0x7000,0x0006,0x8190, + 0xb882,0x2040,0x2802,0xab80,0x38f0,0x4024,0x001f,0x4000, + 0x0006,0x8190,0xb882,0x2040,0x2802,0xab80,0x38f0,0x4024, + 0x002b,0x1100,0x0006,0x8190,0xb882,0x2040,0x2802,0xab80, + 0x38f0,0x4024,0x002e,0xe000,0x0006,0x8190,0xb882,0x2040, + 0x2802,0xab80,0x38f0,0x4024,0x001d,0xc000,0x0006,0x8190, + 0x6892,0x2040,0x2802,0xab80,0x38f0,0x4024,0x0006,0x8190, + 0x0000,0x0201,0x0000,0xfa04,0x2900,0xb740,0x3613,0x0024, + 0x0006,0x9301,0xb88a,0x11cc,0x3c10,0x0024,0x34f0,0x8024, + 0x3410,0xc024,0xd234,0x0024,0x0000,0x3fc3,0xb234,0x0024, + 0x4122,0x0024,0xf400,0x4055,0x3500,0x0024,0x3cf0,0x0024, + 0x3490,0x0024,0xfe50,0x4005,0x48b2,0x0024,0xfeca,0x0024, + 0x40b2,0x0024,0x3810,0x0024,0x2802,0xab80,0x38f0,0x4024, + 0x003f,0xfe04,0x0000,0x0401,0x0006,0x8190,0x2900,0xb740, + 0x3613,0x0024,0x0006,0x9301,0x3473,0x0024,0x3c10,0x0024, + 0x34f0,0x8024,0x3400,0xc024,0xa346,0x0024,0xd234,0x0024, + 0x0000,0x3fc3,0xb234,0x0024,0x4122,0x1042,0xf400,0x4055, + 0x0006,0x9301,0x3500,0x0024,0xd024,0x3000,0xb234,0x0024, + 0x4122,0x0024,0xf400,0x4055,0x0000,0x0041,0x3500,0x0024, + 0x3cf0,0x0024,0x3490,0x0024,0xfe02,0x0024,0x48b2,0x0024, + 0x3810,0x0024,0x2802,0xab80,0x38f0,0x4024,0x003f,0xfe04, + 0x0000,0x0401,0x0006,0x8190,0x2900,0xb740,0x3613,0x0024, + 0x0006,0x9301,0x3473,0x0024,0x3c10,0x0024,0x34f0,0x8024, + 0x3400,0xc024,0xa346,0x0024,0xd234,0x0024,0x0000,0x3fc3, + 0xb234,0x0024,0x4122,0x1042,0xf400,0x4055,0x0006,0x9301, + 0x3500,0x0024,0xd024,0x3000,0xb234,0x0024,0x4122,0x0024, + 0xf400,0x4055,0x3500,0x0024,0x3cf0,0x0024,0x0000,0x0280, + 0x3490,0x4024,0xfe02,0x0024,0x48b2,0x0024,0x3810,0x0024, + 0x2802,0xab80,0x38f0,0x4024,0x0006,0x8010,0x6890,0x0024, + 0x2803,0x1f00,0x3800,0x0024,0x0000,0x0201,0x2900,0xb740, + 0x3613,0x11cc,0x3c10,0x0024,0x3490,0x4024,0x6014,0x13cc, + 0x0000,0x0081,0x2802,0xaec5,0x0006,0x80d0,0x0006,0x8010, + 0x6890,0x0024,0x2803,0x1f00,0x3800,0x0024,0x3010,0x0024, + 0x6012,0x0024,0x0000,0x0241,0x2802,0xce49,0x0006,0x8112, + 0x0008,0x0001,0x3009,0x184c,0x3e10,0x4024,0x3000,0x8024, + 0x2901,0xbac0,0x3e00,0x8024,0x36f3,0x004c,0x3000,0x3844, + 0x0008,0x0010,0xb884,0x3840,0x0000,0x0400,0x3e00,0x8024, + 0x3201,0x0024,0x2903,0x5680,0x6408,0x4091,0x0001,0x0000, + 0x000b,0x8011,0x0004,0x0010,0x36e3,0x0024,0x2915,0x8300, + 0x3009,0x1bc4,0x000b,0x8000,0x3613,0x0024,0x3e10,0x0024, + 0x3200,0xc024,0x2901,0xbac0,0x3e00,0xc024,0x36f3,0x084c, + 0x32f0,0xf844,0x3e10,0xc024,0x3e00,0x8024,0x2b01,0x0091, + 0x0000,0x0400,0xf204,0x0804,0x2903,0x5680,0x6408,0x0024, + 0x000b,0x8011,0x0008,0x0010,0x0000,0x0084,0x36d3,0x0024, + 0x2915,0x8300,0x0003,0x8000,0x0005,0x0010,0x0001,0x0000, + 0x2915,0x8300,0x000f,0x0011,0x1006,0x0ac0,0x32f3,0x11cc, + 0x3200,0xd08c,0xff34,0x0024,0x48b6,0x0024,0x4020,0x0024, + 0x3c90,0x0024,0x2802,0xca80,0x34e3,0x0024,0x0006,0x8112, + 0x3613,0x0024,0x3e10,0x0024,0x3000,0x4024,0x2901,0xbac0, + 0x3e00,0x4024,0x36f3,0x004c,0x3000,0x7844,0xb884,0x3841, + 0x2b01,0x0091,0x0000,0x0400,0x3e00,0x8024,0x3201,0x0024, + 0x2903,0x5680,0x6408,0x0024,0x0003,0x8000,0x000b,0x8011, + 0x0008,0x0010,0x36e3,0x11cc,0x3423,0x0024,0x3494,0xc024, + 0x2903,0x7ac0,0x3301,0x138c,0x0001,0x0000,0x000f,0x0011, + 0x0004,0x0010,0x2903,0x8000,0x3301,0x0024,0xf400,0x4510, + 0x000b,0x8011,0x3073,0x0024,0x3023,0x0024,0x3000,0x0024, + 0x6090,0x0024,0x3800,0x0024,0x0003,0x8000,0x3004,0xc024, + 0x0008,0x0010,0x2903,0x8000,0x3301,0x0024,0x0001,0x0000, + 0x000f,0x0011,0x0005,0x0010,0x2903,0x8000,0x3301,0x0024, + 0xf400,0x4510,0x3073,0x1bc4,0x6498,0x008c,0x3000,0x0024, + 0x6090,0x0024,0x3800,0x0024,0x0006,0x80d0,0x3000,0x0024, + 0x6402,0x0024,0x0006,0x8110,0x2802,0xbdc8,0x000b,0x8000, + 0x000b,0x8010,0x0001,0x0000,0x2903,0xe0c0,0x0004,0x0011, + 0x0005,0x0011,0x000b,0x8010,0x0001,0x0000,0x291f,0xc6c0, + 0x0002,0xdf88,0x30e1,0x184c,0x3000,0x0024,0x6012,0x0024, + 0x0008,0x0001,0x2802,0xd015,0x0000,0x0024,0x6498,0x0024, + 0x3e10,0x4024,0x0000,0x0081,0x2901,0xbac0,0x3e01,0x0024, + 0x36e3,0x004c,0x3000,0x0024,0x6012,0x0024,0x000b,0x8011, + 0x2802,0xdc55,0x0006,0x8112,0x0000,0x0201,0x0004,0x0010, + 0x2915,0x8300,0x0001,0x0000,0x000b,0x8011,0x0005,0x0010, + 0x291f,0xc6c0,0x0001,0x0000,0x0006,0x8110,0x30e1,0x0024, + 0x3000,0x0024,0x6012,0x0024,0x0000,0x0281,0x2802,0xd745, + 0x6012,0x0024,0x000b,0x8001,0x2802,0xd7d5,0x3613,0x0024, + 0x36f3,0x0024,0x000b,0x8001,0x6498,0x184c,0x0006,0x8112, + 0x0003,0x8000,0x3e10,0x4024,0x2901,0xbac0,0x3e01,0x0024, + 0x36f3,0x0024,0x3009,0x3844,0x3e10,0x0024,0x0000,0x0400, + 0x3000,0x8024,0x0008,0x0010,0x3e00,0x8024,0x3201,0x0024, + 0x2903,0x5680,0x6408,0x4051,0x36e3,0x0024,0x2802,0xdf80, + 0x3009,0x1bc4,0x0000,0x0400,0x0000,0x0011,0x3613,0x008c, + 0x30d0,0x7844,0x3e10,0x4024,0x3000,0x8024,0x0008,0x0010, + 0x3e00,0x8024,0x3201,0x0024,0x2903,0x5680,0x6408,0x0024, + 0x36e3,0x0024,0x3009,0x1bc4,0x0006,0x8a10,0x0000,0x01c1, + 0x3009,0x0000,0xb010,0x0024,0x0000,0x0024,0x2802,0xe385, + 0x6192,0x0024,0x2900,0xb740,0x6102,0x184c,0x4088,0x0024, + 0x0000,0x0024,0x2802,0xe385,0x0000,0x0024,0x0006,0x8051, + 0x6890,0x0024,0x3900,0x0024,0x3009,0x0000,0x4080,0x0024, + 0x0000,0x0024,0x2903,0x8e85,0x0002,0xe848,0x0006,0x9f92, + 0x0000,0x4003,0x3009,0x0811,0x3100,0x8024,0xffa6,0x0024, + 0x48b6,0x0024,0x2903,0x8e80,0x4384,0x0024,0x2903,0x8f40, + 0x3613,0x0024,0x2900,0xbf40,0x0000,0x0024,0x2903,0x8e80, + 0x0000,0x0024,0x0000,0x0401,0x3473,0x184c,0x2900,0xb740, + 0x3c10,0x0024,0x3c90,0x0024,0x290b,0x1400,0x34f3,0x0024, + 0x4080,0x0024,0x0000,0x0024,0x2803,0x1b95,0x0000,0x0024, + 0x3473,0x0024,0x3410,0x0024,0x34a0,0x4024,0x6014,0x1380, + 0x0000,0x0024,0x2802,0xf045,0x4080,0x0024,0x0006,0x8011, + 0x6890,0x0024,0xb882,0x2400,0x0004,0x8000,0x2914,0xbec0, + 0x0008,0x0010,0x0000,0x0400,0x3143,0x108c,0x6890,0x27c0, + 0x3920,0x0024,0x0004,0x8000,0x3900,0x0024,0x34e0,0x0024, + 0x4080,0x0024,0x0006,0x8150,0x2802,0xf445,0x0000,0x3200, + 0x0000,0x0142,0x0006,0x8210,0x3613,0x0024,0x3e00,0x7800, + 0x3011,0x8024,0x30d1,0xc024,0xfef4,0x4087,0x48b6,0x0040, + 0xfeee,0x03c1,0x2914,0xa580,0x42b6,0x0024,0x2802,0xf840, + 0x0007,0x89d0,0x0000,0x0142,0x3613,0x0024,0x3e00,0x7800, + 0x3031,0x8024,0x3010,0x0024,0x30d0,0x4024,0xfe9c,0x4181, + 0x48be,0x0024,0xfe82,0x0040,0x46be,0x03c1,0xfef4,0x4087, + 0x48b6,0x0024,0xfeee,0x0024,0x2914,0xa580,0x42b6,0x0024, + 0x0007,0x89d0,0x0006,0x8191,0x4c8a,0x9800,0xfed0,0x4005, + 0x48b2,0x0024,0xfeca,0x0024,0x40b2,0x0024,0x3810,0x0024, + 0x38f0,0x4024,0x3111,0x8024,0x468a,0x0707,0x2908,0xbe80, + 0x3101,0x0024,0x3123,0x11cc,0x3100,0x108c,0x3009,0x3000, + 0x0004,0x8000,0x3009,0x1241,0x6014,0x138c,0x000b,0x8011, + 0x2802,0xfe81,0x0000,0x0024,0x3473,0x0024,0x3423,0x0024, + 0x3009,0x3240,0x34e3,0x0024,0x2803,0x19c0,0x0008,0x0012, + 0x0000,0x0081,0x2803,0x0009,0x0006,0x80d0,0xf400,0x4004, + 0x3000,0x0024,0x6012,0x0024,0x0000,0x0005,0x2803,0x0589, + 0x0000,0x0024,0x6540,0x0024,0x0000,0x0024,0x2803,0x15d8, + 0x4490,0x0024,0x2403,0x04c0,0x0000,0x0024,0x0006,0x8301, + 0x4554,0x0800,0x4122,0x0024,0x659a,0x4055,0x0006,0x8341, + 0x3d00,0x0840,0x4122,0x0024,0xf400,0x4055,0x3d00,0x0024, + 0x2803,0x15c0,0x0000,0x0024,0x4090,0x0024,0xf400,0x4480, + 0x2803,0x0ad5,0x000b,0x8001,0x6540,0x0024,0x0000,0x0024, + 0x2803,0x15d8,0x4490,0x0024,0x2403,0x0a00,0x0000,0x0024, + 0x0006,0x8301,0x4554,0x0800,0x4122,0x0024,0x659a,0x4055, + 0x0006,0x8341,0x4122,0x3400,0xf400,0x4055,0x3210,0x0024, + 0x3d00,0x0024,0x2803,0x15c0,0x0000,0x0024,0x6014,0x0024, + 0x0001,0x0000,0x2803,0x1215,0x0003,0x8001,0x0008,0x0012, + 0x0008,0x0010,0x0006,0x8153,0x3613,0x0024,0x3009,0x3811, + 0x2903,0xe0c0,0x0004,0x0011,0x0008,0x0010,0x0001,0x0000, + 0x291f,0xc6c0,0x0005,0x0011,0x000f,0x0011,0x0008,0x0010, + 0x33d0,0x184c,0x6010,0xb844,0x3e10,0x0024,0x0000,0x0400, + 0x3320,0x4024,0x3e00,0x4024,0x3301,0x0024,0x2903,0x5680, + 0x6408,0x0024,0x36e3,0x0024,0x3009,0x1bc4,0x3009,0x1bd1, + 0x6540,0x0024,0x0000,0x0024,0x2803,0x15d8,0x4490,0x0024, + 0x2403,0x1580,0x0000,0x0024,0x0006,0x8301,0x4554,0x0840, + 0x4122,0x0024,0x659a,0x4055,0x0006,0x8341,0x4122,0x3400, + 0xf400,0x4055,0x3110,0x0024,0x3d00,0x0024,0xf400,0x4510, + 0x0030,0x0013,0x3073,0x184c,0x3e11,0x008c,0x3009,0x0001, + 0x6140,0x0024,0x0000,0x0201,0x3009,0x2000,0x0006,0x8300, + 0x290c,0x7300,0x3e10,0x0024,0x3300,0x1b8c,0xb010,0x0024, + 0x0000,0x0024,0x2803,0x1b95,0x0000,0x0024,0x3473,0x0024, + 0x3423,0x0024,0x3009,0x1240,0x4080,0x138c,0x0000,0x0804, + 0x2802,0xff15,0x6402,0x0024,0x0006,0xd312,0x0006,0xd310, + 0x0006,0x8191,0x3010,0x984c,0x30f0,0xc024,0x0000,0x0021, + 0xf2d6,0x07c6,0x290a,0xf5c0,0x4682,0x0400,0x6894,0x0840, + 0xb886,0x0bc1,0xbcd6,0x0024,0x3a10,0x8024,0x3af0,0xc024, + 0x36f3,0x4024,0x36f3,0xd80e,0x36f4,0x9813,0x36f4,0x1811, + 0x36f1,0x9807,0x36f1,0x1805,0x36f0,0x9803,0x36f0,0x1801, + 0x3405,0x9014,0x36f3,0x0024,0x36f2,0x1815,0x2000,0x0000, + 0x36f2,0x9817,0x3613,0x0024,0x3e12,0xb817,0x3e12,0x3815, + 0x3e05,0xb814,0x3615,0x0024,0x0000,0x800a,0x3e10,0x3801, + 0x0020,0x0001,0x3e14,0x3811,0x0030,0x0050,0x0030,0x0251, + 0x3e04,0xb813,0x3000,0x0024,0xc012,0x0024,0x0019,0x9300, + 0x3800,0x4024,0x2903,0x8c40,0x3900,0x0024,0x2903,0xa040, + 0x0000,0x0300,0xb882,0x0024,0x2914,0xbec0,0x0006,0x8010, + 0x0000,0x1540,0x0007,0x8190,0x2901,0x8200,0x3800,0x0024, + 0x4080,0x0024,0x0000,0x0024,0x2803,0x2f55,0x0000,0x0024, + 0x0006,0x8012,0x3200,0x0024,0x4080,0x0024,0x0030,0x0010, + 0x2803,0x2f55,0x0000,0x0201,0x3000,0x0024,0xb010,0x0024, + 0x0000,0x0024,0x2803,0x2f55,0x0000,0x0024,0x2901,0x8200, + 0x0000,0x0024,0x4080,0x0024,0x0006,0x8010,0x2803,0x2f55, + 0x3000,0x0024,0x4080,0x0024,0x0000,0x0201,0x2803,0x2b85, + 0x0030,0x0010,0x0030,0x0050,0xf292,0x0000,0xb012,0x0024, + 0x3800,0x4024,0x0030,0x0010,0x0000,0x0201,0x3000,0x0024, + 0xb010,0x0024,0x0000,0x0024,0x2900,0xbed5,0x0003,0x3908, + 0x0006,0x8011,0x3100,0x0024,0x4080,0x0024,0x0000,0x0024, + 0x2803,0x3745,0x0000,0x0024,0x0007,0x8a52,0x3200,0x0024, + 0x4080,0x0024,0x0000,0x0024,0x2803,0x3749,0x0000,0x0024, + 0xf292,0x0800,0x6012,0x0024,0x0000,0x0000,0x2803,0x3705, + 0x0000,0x0024,0x3200,0x0024,0x4090,0x0024,0xb880,0x2800, + 0x3900,0x0024,0x3100,0x0024,0x4080,0x0024,0x0000,0x0024, + 0x2902,0x3585,0x0003,0x3048,0x2900,0xbec0,0x0000,0x0024, + 0x0000,0x0010,0x0006,0x9f51,0x0006,0x9f92,0x0030,0x0493, + 0x0000,0x0201,0x6890,0xa410,0x3b00,0x2810,0x0006,0x8a10, + 0x3009,0x0000,0x6012,0x0024,0x0006,0x9fd0,0x2803,0x3c88, + 0xb880,0x0024,0x6890,0x0024,0x3009,0x2000,0x36f4,0x9813, + 0x36f4,0x1811,0x36f0,0x1801,0x3405,0x9014,0x36f3,0x0024, + 0x36f2,0x1815,0x2000,0x0000,0x36f2,0x9817,0x3613,0x0024, + 0x3e10,0xb810,0x3e11,0x3805,0x3e02,0x0024,0x0030,0x0010, + 0xce9a,0x0002,0x0000,0x0200,0x2903,0x47c0,0xb024,0x0024, + 0xc020,0x0024,0x0000,0x0200,0x2803,0x4085,0x6e9a,0x0002, + 0x4182,0x0024,0x0000,0x0400,0x2803,0x4645,0xae1a,0x0024, + 0x6104,0x984c,0x0000,0x0024,0x2900,0xb749,0x0003,0x4608, + 0x6103,0xe4e5,0x2900,0xb740,0x408a,0x188c,0x2900,0xb740, + 0x408a,0x4141,0x4583,0x6465,0x2803,0x4640,0xceca,0x1bcc, + 0xc408,0x0024,0xf2e2,0x1bc8,0x36f1,0x1805,0x2000,0x0011, + 0x36f0,0x9810,0x2000,0x0000,0xdc92,0x0024,0x0006,0x8a17, + 0x3613,0x1c00,0x6093,0xe1e3,0x0000,0x03c3,0x0006,0x9f95, + 0xb132,0x9415,0x3500,0xfc01,0x2803,0x55d5,0xa306,0x0024, + 0x0006,0xd397,0x003f,0xc001,0x3500,0x184c,0xb011,0xe4e5, + 0xb182,0x1c04,0xd400,0x184c,0x0000,0x0205,0xac52,0x3802, + 0x0006,0xd3c2,0x4212,0x0024,0xf400,0x4057,0xb182,0x1c04, + 0xd400,0x0024,0xac52,0x1404,0xd142,0x0024,0x0000,0x3fc4, + 0xb142,0x0024,0x4122,0x1bc2,0xf400,0x4057,0x3700,0x4024, + 0xd101,0x6465,0x0006,0xd397,0x3f00,0x3814,0x0025,0xffd4, + 0x0006,0xd317,0x3710,0x160c,0x0006,0x9f94,0x37f0,0x73d5, + 0x6c92,0x3808,0x3f10,0x0024,0x3ff0,0x4024,0x3009,0x1040, + 0x3009,0x13c1,0x6010,0x0024,0x0000,0x0024,0x2903,0xa905, + 0x0003,0x51c8,0x2803,0x5414,0x0006,0x0001,0x4010,0x0024, + 0x0005,0xf601,0x6010,0x0024,0x0000,0x0040,0x2803,0x5594, + 0x0030,0x0497,0x3f00,0x0024,0x36f2,0x1814,0x4330,0x9803, + 0x2000,0x0000,0x8880,0x1bc1,0x3613,0x0024,0x3e22,0xb806, + 0x3e05,0xb814,0x3615,0x0024,0x0000,0x800a,0x3e10,0x3801, + 0x3e10,0xb803,0x3e11,0x7807,0x6848,0x930c,0x3411,0x780d, + 0x459a,0x10c0,0x0000,0x0201,0x6012,0x384e,0x0000,0x0241, + 0x2803,0x5d15,0x6012,0x380f,0x2403,0x5c45,0x0000,0x0024, + 0x3000,0x0001,0x3101,0x8407,0x6cfe,0x0024,0xac42,0x0024, + 0xaf4e,0x2040,0x3911,0x8024,0x2803,0x68c0,0x0000,0x0024, + 0x0000,0x0281,0x2803,0x6055,0x6012,0x4455,0x2403,0x5f85, + 0x0000,0x0024,0x3000,0x0001,0x3101,0x8407,0x4cf2,0x0024, + 0xac42,0x0024,0xaf4e,0x2040,0x3911,0x8024,0x2803,0x68c0, + 0x0000,0x0024,0x0000,0x0024,0x2803,0x6495,0x4080,0x0024, + 0x3110,0x0401,0xf20f,0x0203,0x2403,0x63c5,0x8dd6,0x0024, + 0x4dce,0x0024,0xf1fe,0x0024,0xaf4e,0x0024,0x6dc6,0x2046, + 0xf1df,0x0203,0xaf4f,0x1011,0xf20e,0x07cc,0x8dd6,0x2486, + 0x2803,0x68c0,0x0000,0x0024,0x0000,0x0024,0x2803,0x6715, + 0x0000,0x0024,0x0fff,0xffd1,0x2403,0x6645,0x3010,0x0001, + 0xac4f,0x0801,0x3821,0x8024,0x2803,0x68c0,0x0000,0x0024, + 0x0fff,0xffd1,0x2403,0x6885,0x3010,0x0001,0x3501,0x9407, + 0xac47,0x0801,0xaf4e,0x2082,0x3d11,0x8024,0x36f3,0xc024, + 0x36f3,0x980d,0x36f1,0x5807,0x36f0,0x9803,0x36f0,0x1801, + 0x3405,0x9014,0x36e3,0x0024,0x2000,0x0000,0x36f2,0x9806, + 0x0006,0x9f97,0x3e00,0x5c15,0x0006,0xd397,0x003f,0xc001, + 0x3500,0x3840,0xb011,0xe4e5,0xb182,0x1c04,0xd400,0x184c, + 0x0000,0x0205,0xac52,0x3802,0x0006,0xd3c2,0x4212,0x0024, + 0xb182,0x4057,0x3701,0x0024,0xd400,0x0024,0xac52,0x1404, + 0xd142,0x0024,0x0000,0x3fc4,0xb142,0x0024,0x4122,0x1bc2, + 0xf400,0x4057,0x3700,0x4024,0xd101,0x6465,0x0006,0xd397, + 0x3f00,0x3814,0x0025,0xffd4,0x0006,0xd317,0x3710,0x160c, + 0x0006,0x9f94,0x37f0,0x73d5,0x6c92,0x0024,0x3f10,0x1040, + 0x3ff0,0x53c1,0x6010,0x0024,0x0000,0x0024,0x2803,0x7494, + 0x0006,0x0001,0x4010,0x0024,0x0005,0xf601,0x6010,0x9bd4, + 0x0000,0x0040,0x2803,0x7614,0x0030,0x0497,0x3f00,0x0024, + 0x2000,0x0000,0x36f0,0x5800,0x0000,0x0400,0x6102,0x0024, + 0x3e11,0x3805,0x2803,0x7989,0x3e02,0x0024,0x2900,0xb740, + 0x408a,0x188c,0x2900,0xb740,0x408a,0x4141,0x4582,0x1bc8, + 0x2000,0x0000,0x36f1,0x1805,0x2900,0xb740,0x4102,0x184c, + 0xb182,0x1bc8,0x2000,0x0000,0x36f1,0x1805,0x3613,0x0024, + 0x3e12,0xb815,0x3e11,0xb807,0x3e13,0xf80e,0x3e03,0x4024, + 0x680c,0x0024,0x0000,0x0024,0x2803,0x7ed8,0x409c,0x0024, + 0x2403,0x7e86,0x0000,0x000a,0x3111,0xc024,0xfe4e,0x0007, + 0x47be,0x0024,0xf6fe,0x0024,0x3811,0xc024,0x36f3,0x4024, + 0x36f3,0xd80e,0x36f1,0x9807,0x2000,0x0000,0x36f2,0x9815, + 0x3613,0x0024,0x3e12,0xb815,0x3e11,0xb807,0x3e13,0xf80e, + 0x3e03,0x4024,0x680c,0x0024,0x0000,0x0024,0x2803,0x8418, + 0x409c,0x0024,0x2403,0x83c6,0x0000,0x000a,0x3111,0xc024, + 0xfe4e,0x8007,0x47be,0x0024,0xf6fe,0x0024,0x3009,0x2047, + 0x36f3,0x4024,0x36f3,0xd80e,0x36f1,0x9807,0x2000,0x0000, + 0x36f2,0x9815,0x2a03,0x858e,0x3e12,0xb817,0x3e10,0x3802, + 0x0000,0x800a,0x0006,0x9f97,0x3009,0x1fc2,0x3e04,0x5c00, + 0x6020,0xb810,0x0030,0x0451,0x2803,0x8854,0x0006,0x0002, + 0x4020,0x0024,0x0005,0xfb02,0x6024,0x0024,0x0025,0xffd0, + 0x2803,0x8a91,0x3100,0x1c11,0xb284,0x0024,0x0030,0x0490, + 0x3800,0x8024,0x0025,0xffd0,0x3980,0x1810,0x36f4,0x7c11, + 0x36f0,0x1802,0x0030,0x0717,0x3602,0x8024,0x2100,0x0000, + 0x3f05,0xdbd7,0x0003,0x8557,0x3613,0x0024,0x3e00,0x3801, + 0xf400,0x55c0,0x0000,0x0897,0xf400,0x57c0,0x0000,0x0024, + 0x2000,0x0000,0x36f0,0x1801,0x0006,0xd397,0x2000,0x0000, + 0x3700,0x0024,0xb183,0xe1e3,0x0000,0x0203,0xac32,0x40d5, + 0xd122,0x0024,0x0000,0x3fc3,0xb132,0x0024,0x0006,0xd3c3, + 0x4316,0x0024,0xf400,0x40d5,0x3500,0x5803,0x2000,0x0000, + 0xd010,0x1bc1,0x3613,0x0024,0x3e22,0xb815,0x3e05,0xb814, + 0x3615,0x0024,0x0000,0x800a,0x3e10,0x3801,0x3e10,0xb803, + 0xb884,0xb805,0xb888,0x3844,0x3e11,0xb80d,0x3e03,0xf80e, + 0x0000,0x03ce,0xf400,0x4083,0x2403,0x9ace,0xf400,0x4105, + 0x0000,0x0206,0xa562,0x0024,0x455a,0x0024,0x0020,0x0006, + 0xd312,0x0024,0xb16c,0x0024,0x0020,0x0006,0x2803,0x9945, + 0xd342,0x0024,0x0000,0x01c6,0xd342,0x0024,0xd56a,0x0024, + 0x0020,0x0006,0x4448,0x0024,0xb16c,0x0024,0x0020,0x0146, + 0x2803,0x9ac5,0x0000,0x0024,0xd468,0x0024,0x4336,0x0024, + 0x0000,0x4000,0x0006,0xd3c1,0x0006,0x9306,0x4122,0x0024, + 0x462c,0x4055,0x4092,0x3404,0xb512,0x4195,0x6294,0x3401, + 0x6200,0x0024,0x0000,0x03ce,0x2803,0x9551,0xb888,0x0024, + 0x36f3,0xd80e,0x36f1,0x980d,0x36f1,0x1805,0x36f0,0x9803, + 0x36f0,0x1801,0x3405,0x9014,0x36e3,0x0024,0x2000,0x0000, + 0x36f2,0x9815,0x3613,0x0024,0x3e12,0xb817,0x3e12,0x3815, + 0x3e05,0xb814,0x3615,0x0024,0x0000,0x800a,0x3e10,0x3801, + 0xb880,0xb810,0x0006,0x9fd0,0x3e10,0x8001,0x4182,0x3811, + 0x0006,0xd311,0x2803,0xa405,0x0006,0x8a10,0x0000,0x0200, + 0xbc82,0xa000,0x3910,0x0024,0x2903,0x9240,0x39f0,0x4024, + 0x0006,0x9f90,0x0006,0x9f51,0x3009,0x0000,0x3009,0x0401, + 0x6014,0x0024,0x0000,0x0024,0x2903,0xa905,0x0003,0xa508, + 0x36f4,0x4024,0x36f0,0x9810,0x36f0,0x1801,0x3405,0x9014, + 0x36f3,0x0024,0x36f2,0x1815,0x2000,0x0000,0x36f2,0x9817, + 0x3613,0x0024,0x3e12,0xb817,0x3e12,0x3815,0x3e05,0xb814, + 0x290a,0xd900,0x3605,0x0024,0x2910,0x0180,0x3613,0x0024, + 0x3405,0x9014,0x36f3,0x0024,0x36f2,0x1815,0x2000,0x0000, + 0x36f2,0x9817,0x3613,0x0024,0x3e12,0xb817,0x3e12,0x3815, + 0x3e05,0xb814,0x3615,0x0024,0x0000,0x800a,0x3e10,0xb803, + 0x0006,0x0002,0x3e11,0x3805,0x3e11,0xb807,0x3e14,0x3811, + 0x0006,0x9f90,0x3e04,0xb813,0x3009,0x0012,0x3213,0x0024, + 0xf400,0x4480,0x6026,0x0024,0x0000,0x0024,0x2803,0xb195, + 0x0000,0x0024,0x0000,0x0012,0xf400,0x4480,0x0006,0x9f50, + 0x3009,0x0002,0x6026,0x0024,0x0000,0x0024,0x2903,0xa905, + 0x0003,0xb188,0x0006,0x9f93,0x3201,0x0c11,0xb58a,0x0406, + 0x0006,0x8a11,0x468e,0x8400,0xb68c,0x9813,0xcfee,0x1bd2, + 0x0000,0x0804,0xaf0e,0x9811,0x4f86,0x1bd0,0x0000,0x0021, + 0x6418,0x9807,0x6848,0x1bc6,0xad46,0x9805,0xf400,0x4080, + 0x36f1,0x0024,0x36f0,0x9803,0x3405,0x9014,0x36f3,0x0024, + 0x36f2,0x1815,0x2000,0x0000,0x36f2,0x9817,0x3613,0x0024, + 0x3e12,0xb817,0x3e12,0x3815,0x3e05,0xb814,0x3615,0x0024, + 0x0000,0x800a,0x3e10,0x3801,0x3e10,0xb803,0x3e11,0x3805, + 0x2803,0xbfc0,0x3e04,0x3811,0x0000,0x0401,0x2900,0xb740, + 0x3613,0x0024,0x0000,0x0080,0xb882,0x130c,0xf400,0x4510, + 0x3010,0x910c,0x30f0,0xc024,0x6dc2,0x0024,0x3810,0x0024, + 0x38f0,0x4024,0x0000,0x0201,0x3100,0x0024,0xb010,0x0024, + 0x0000,0x0024,0x2803,0xc315,0x0000,0x0024,0x6894,0x130c, + 0xb886,0x1040,0x3430,0x4024,0x6dca,0x0024,0x0030,0x0011, + 0x2803,0xbb91,0x0000,0x0024,0xbcd2,0x0024,0x0000,0x0201, + 0x2803,0xc305,0x0000,0x0024,0x2900,0xb740,0x3613,0x0024, + 0x36f4,0x1811,0x36f1,0x1805,0x36f0,0x9803,0x36f0,0x1801, + 0x3405,0x9014,0x36f3,0x0024,0x36f2,0x1815,0x2000,0x0000, + 0x36f2,0x9817,0x3613,0x0024,0x3e12,0xb815,0x0000,0x800a, + 0x3e14,0x7813,0x3e10,0xb803,0x3e11,0x3805,0x3e11,0xb807, + 0x3e13,0xf80e,0x6812,0x0024,0x3e03,0x7810,0x0fff,0xffd3, + 0x0000,0x0091,0xbd86,0x9850,0x3e10,0x3804,0x3e00,0x7812, + 0xbe8a,0x8bcc,0x409e,0x8086,0x2403,0xca47,0xfe49,0x2821, + 0x526a,0x8801,0x5c87,0x280e,0x4eba,0x9812,0x4286,0x40e1, + 0xb284,0x1bc1,0x4de6,0x0024,0xad17,0x2627,0x4fde,0x9804, + 0x4498,0x1bc0,0x0000,0x0024,0x2803,0xc855,0x3a11,0xa807, + 0x36f3,0x4024,0x36f3,0xd80e,0x36f1,0x9807,0x36f1,0x1805, + 0x36f0,0x9803,0x36f4,0x5813,0x2000,0x0000,0x36f2,0x9815, + 0x3613,0x0024,0x3e12,0xb815,0x0000,0x800a,0x3e10,0xb803, + 0x3e11,0x3805,0x3e11,0xb807,0x3e13,0xf80e,0x6812,0x0024, + 0x3e03,0x7810,0x3009,0x1850,0x3e10,0x3804,0x3e10,0x7812, + 0x32f3,0x0024,0xbd86,0x0024,0x4091,0xe2e3,0x3009,0x0046, + 0x2403,0xd5c0,0x3009,0x0047,0x32f0,0x0801,0xfe1f,0x6465, + 0x5e8a,0x0024,0x44ba,0x0024,0xfee2,0x0024,0x5d8a,0x1800, + 0x4482,0x4160,0x48ba,0x8046,0x4dc6,0x1822,0x4de6,0x8047, + 0x36f3,0x0024,0x36f0,0x5812,0xad17,0x2627,0x4fde,0x9804, + 0x4498,0x1bc0,0x0000,0x0024,0x2803,0xd155,0x3a11,0xa807, + 0x36f3,0x4024,0x36f3,0xd80e,0x36f1,0x9807,0x36f1,0x1805, + 0x36f0,0x9803,0x2000,0x0000,0x36f2,0x9815,0xb386,0x40d7, + 0x4284,0x184c,0x0000,0x05c0,0x2803,0xdb55,0xf5d8,0x3804, + 0x0000,0x0984,0x6400,0xb84a,0x3e13,0xf80d,0xa204,0x380e, + 0x0000,0x800a,0x0000,0x00ce,0x2403,0xde8e,0xffa4,0x0024, + 0x48b6,0x0024,0x0000,0x0024,0x2803,0xde84,0x4000,0x40c2, + 0x4224,0x0024,0x6090,0x0024,0xffa4,0x0024,0x0fff,0xfe83, + 0xfe86,0x1bce,0x36f3,0xd80d,0x48b6,0x0024,0x0fff,0xff03, + 0xa230,0x45c3,0x2000,0x0000,0x36f1,0x180a,0x4080,0x184c, + 0x3e13,0x780f,0x2803,0xe2c5,0x4090,0xb80e,0x2403,0xe240, + 0x3e04,0x0440,0x3810,0x0440,0x3604,0x0024,0x3009,0x1bce, + 0x3603,0x5bcf,0x2000,0x0000,0x0000,0x0024, + 0x0007,0x0001, /*copy 1*/ + 0x802e, + 0x0006,0x0002, /*copy 2*/ + 0x2801,0x6ac0, + 0x0007,0x0001, /*copy 1*/ + 0x8030, + 0x0006,0x0002, /*copy 2*/ + 0x2800,0x1b40, + 0x0007,0x0001, /*copy 1*/ + 0x8028, + 0x0006,0x0002, /*copy 2*/ + 0x2a00,0x144e, + 0x0007,0x0001, /*copy 1*/ + 0x8032, + 0x0006,0x0002, /*copy 2*/ + 0x2800,0x7140, + 0x0007,0x0001, /*copy 1*/ + 0x3580, + 0x0006, 0x8038, 0x0000, /*Rle(56)*/ + 0x0007,0x0001, /*copy 1*/ + 0xfab3, + 0x0006,0x01a4, /*copy 420*/ + 0x0001,0x0001,0x0001,0x0001,0x0000,0xffff,0xfffe,0xfffb, + 0xfff9,0xfff5,0xfff2,0xffed,0xffe8,0xffe3,0xffde,0xffd8, + 0xffd3,0xffce,0xffca,0xffc7,0xffc4,0xffc4,0xffc5,0xffc7, + 0xffcc,0xffd3,0xffdc,0xffe6,0xfff3,0x0001,0x0010,0x001f, + 0x002f,0x003f,0x004e,0x005b,0x0066,0x006f,0x0074,0x0075, + 0x0072,0x006b,0x005f,0x004f,0x003c,0x0024,0x0009,0xffed, + 0xffcf,0xffb0,0xff93,0xff77,0xff5f,0xff4c,0xff3d,0xff35, + 0xff34,0xff3b,0xff4a,0xff60,0xff7e,0xffa2,0xffcd,0xfffc, + 0x002e,0x0061,0x0094,0x00c4,0x00f0,0x0114,0x0131,0x0144, + 0x014b,0x0146,0x0134,0x0116,0x00eb,0x00b5,0x0075,0x002c, + 0xffde,0xff8e,0xff3d,0xfeef,0xfea8,0xfe6a,0xfe39,0xfe16, + 0xfe05,0xfe06,0xfe1b,0xfe43,0xfe7f,0xfecd,0xff2a,0xff95, + 0x0009,0x0082,0x00fd,0x0173,0x01e1,0x0242,0x0292,0x02cc, + 0x02ec,0x02f2,0x02da,0x02a5,0x0253,0x01e7,0x0162,0x00c9, + 0x0021,0xff70,0xfebc,0xfe0c,0xfd68,0xfcd5,0xfc5b,0xfc00, + 0xfbc9,0xfbb8,0xfbd2,0xfc16,0xfc85,0xfd1b,0xfdd6,0xfeae, + 0xff9e,0x009c,0x01a0,0x02a1,0x0392,0x046c,0x0523,0x05b0, + 0x060a,0x062c,0x0613,0x05bb,0x0526,0x0456,0x0351,0x021f, + 0x00c9,0xff5a,0xfde1,0xfc6a,0xfb05,0xf9c0,0xf8aa,0xf7d0, + 0xf73d,0xf6fa,0xf70f,0xf77e,0xf848,0xf96b,0xfadf,0xfc9a, + 0xfe8f,0x00ad,0x02e3,0x051a,0x073f,0x0939,0x0af4,0x0c5a, + 0x0d59,0x0de1,0x0de5,0x0d5c,0x0c44,0x0a9e,0x0870,0x05c7, + 0x02b4,0xff4e,0xfbaf,0xf7f8,0xf449,0xf0c7,0xed98,0xeae0, + 0xe8c4,0xe765,0xe6e3,0xe756,0xe8d2,0xeb67,0xef19,0xf3e9, + 0xf9cd,0x00b5,0x088a,0x112b,0x1a72,0x2435,0x2e42,0x3866, + 0x426b,0x4c1b,0x553e,0x5da2,0x6516,0x6b6f,0x7087,0x7441, + 0x7686,0x774a,0x7686,0x7441,0x7087,0x6b6f,0x6516,0x5da2, + 0x553e,0x4c1b,0x426b,0x3866,0x2e42,0x2435,0x1a72,0x112b, + 0x088a,0x00b5,0xf9cd,0xf3e9,0xef19,0xeb67,0xe8d2,0xe756, + 0xe6e3,0xe765,0xe8c4,0xeae0,0xed98,0xf0c7,0xf449,0xf7f8, + 0xfbaf,0xff4e,0x02b4,0x05c7,0x0870,0x0a9e,0x0c44,0x0d5c, + 0x0de5,0x0de1,0x0d59,0x0c5a,0x0af4,0x0939,0x073f,0x051a, + 0x02e3,0x00ad,0xfe8f,0xfc9a,0xfadf,0xf96b,0xf848,0xf77e, + 0xf70f,0xf6fa,0xf73d,0xf7d0,0xf8aa,0xf9c0,0xfb05,0xfc6a, + 0xfde1,0xff5a,0x00c9,0x021f,0x0351,0x0456,0x0526,0x05bb, + 0x0613,0x062c,0x060a,0x05b0,0x0523,0x046c,0x0392,0x02a1, + 0x01a0,0x009c,0xff9e,0xfeae,0xfdd6,0xfd1b,0xfc85,0xfc16, + 0xfbd2,0xfbb8,0xfbc9,0xfc00,0xfc5b,0xfcd5,0xfd68,0xfe0c, + 0xfebc,0xff70,0x0021,0x00c9,0x0162,0x01e7,0x0253,0x02a5, + 0x02da,0x02f2,0x02ec,0x02cc,0x0292,0x0242,0x01e1,0x0173, + 0x00fd,0x0082,0x0009,0xff95,0xff2a,0xfecd,0xfe7f,0xfe43, + 0xfe1b,0xfe06,0xfe05,0xfe16,0xfe39,0xfe6a,0xfea8,0xfeef, + 0xff3d,0xff8e,0xffde,0x002c,0x0075,0x00b5,0x00eb,0x0116, + 0x0134,0x0146,0x014b,0x0144,0x0131,0x0114,0x00f0,0x00c4, + 0x0094,0x0061,0x002e,0xfffc,0xffcd,0xffa2,0xff7e,0xff60, + 0xff4a,0xff3b,0xff34,0xff35,0xff3d,0xff4c,0xff5f,0xff77, + 0xff93,0xffb0,0xffcf,0xffed,0x0009,0x0024,0x003c,0x004f, + 0x005f,0x006b,0x0072,0x0075,0x0074,0x006f,0x0066,0x005b, + 0x004e,0x003f,0x002f,0x001f,0x0010,0x0001,0xfff3,0xffe6, + 0xffdc,0xffd3,0xffcc,0xffc7,0xffc5,0xffc4,0xffc4,0xffc7, + 0xffca,0xffce,0xffd3,0xffd8,0xffde,0xffe3,0xffe8,0xffed, + 0xfff2,0xfff5,0xfff9,0xfffb,0xfffe,0xffff,0x0000,0x0001, + 0x0001,0x0001,0x0001,0x0000, + 0x0007,0x0001, /*copy 1*/ + 0x180b, + 0x0006,0x000d, /*copy 13*/ + 0x000f,0x0010,0x001c,0xfab3,0x3580,0x8037,0xa037,0x0001, + 0x0000,0x3580,0x01a4,0x0728,0x0746, + 0x0006, 0x8006, 0x078e, /*Rle(6)*/ + 0x0006,0x0027, /*copy 39*/ + 0x0763,0x0763,0x0763,0x0763,0x0763,0x0992,0x0976,0x097a, + 0x097e,0x0982,0x0986,0x098a,0x098e,0x09c1,0x09c5,0x09c8, + 0x09c8,0x09c8,0x09c8,0x09d0,0x09e3,0x0aae,0x0a1a,0x0a1f, + 0x0a25,0x0a2b,0x0a30,0x0a35,0x0a3a,0x0a3f,0x0a44,0x0a49, + 0x0a4e,0x0a53,0x0a6c,0x0a8b,0x0aaa,0x5a82,0x5a82, + 0x0006, 0x8006, 0x0000, /*Rle(6)*/ + 0x0006,0x0018, /*copy 24*/ + 0x6fb8,0xc180,0xc180,0x6fb8,0x0000,0x0000,0x0000,0x0000, + 0x5a82,0x5a82,0x6fb8,0xc180,0xc180,0x6fb8,0x0000,0x0000, + 0x5a82,0x5a82,0x5a82,0x5a82,0x6fb8,0xc180,0xc180,0x6fb8, + 0x0007,0x0001, /*copy 1*/ + 0x8025, + 0x0006,0x0002, /*copy 2*/ + 0x2a00,0x5c4e, + 0x0007,0x0001, /*copy 1*/ + 0x5800, + 0x0006,0x0001, /*copy 1*/ + 0x0001, + 0x0006, 0x8007, 0x0000, /*Rle(7)*/ + 0x0006,0x0018, /*copy 24*/ + 0x0002,0x0000,0xffff,0xffff,0x0000,0x0000,0x0000,0x0000, + 0x0003,0x0000,0xfffd,0xffff,0x0001,0x0000,0x0000,0x0000, + 0x0004,0x0000,0xfffa,0xffff,0x0004,0x0000,0xffff,0xffff, + 0x000a,0x0001, /*copy 1*/ + 0x0050, +#define PLUGIN_SIZE 8414 +}; +