From 12147eb57000c0ec1d1e045eda68eabe32b7d56b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien?= Date: Wed, 26 Apr 2023 13:51:52 -0400 Subject: [PATCH 01/29] Exclude bundles form analysis [skip actions] --- .github/workflows/codeql-analysis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index fd209220..d6a23afe 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -46,6 +46,7 @@ jobs: # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main + paths-ignore: components/wifi-manager/webapp/dist/js/index*, components/wifi-manager/webapp/dist/js/index* # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) From 8cf9c161401b2be5a13a5587422eca665d78120e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien?= Date: Wed, 26 Apr 2023 13:54:00 -0400 Subject: [PATCH 02/29] allow manual run - [skip actions] --- .github/workflows/codeql-analysis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d6a23afe..3b2a1e11 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -19,6 +19,7 @@ on: branches: [ master-cmake ] schedule: - cron: '19 12 * * 4' + workflow_dispatch: jobs: analyze: From c0a9fd3100b0113793eab0f94b6f7d5d6ee67f4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien?= Date: Wed, 26 Apr 2023 17:39:13 -0400 Subject: [PATCH 03/29] Update codeql-analysis.yml [skip actions] --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 3b2a1e11..0dc23e93 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -40,7 +40,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -52,7 +52,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v2 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -66,4 +66,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 From a38cb554684db798f66df4af59f1bc56e0677f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien?= Date: Wed, 26 Apr 2023 18:02:51 -0400 Subject: [PATCH 04/29] Update codeql-analysis.yml [skip actions] --- .github/workflows/codeql-analysis.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 0dc23e93..b32a91fb 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -47,7 +47,6 @@ jobs: # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main - paths-ignore: components/wifi-manager/webapp/dist/js/index*, components/wifi-manager/webapp/dist/js/index* # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) @@ -64,6 +63,13 @@ jobs: #- run: | # make bootstrap # make release - + # Exclude specific artifacts from analysis + - name: Exclude Artifacts + run: | + # Exclude components/wifi-manager/webapp/dist/js/index* from analysis + echo 'components/wifi-manager/webapp/dist/js/index*' >> .codeql-exclude-paths + echo 'components/wifi-manager/webapp/dist/js/index*' >> .codeql-exclude-paths.txt + echo 'components/wifi-manager/webapp/dist/index*' >> .codeql-exclude-paths + echo 'components/wifi-manager/webapp/dist/index*' >> .codeql-exclude-paths.txt - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 From 8bad4801124d3c5d387df83a8df9130bc9bc9a10 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Sat, 6 May 2023 23:50:26 +0200 Subject: [PATCH 05/29] new cspot/bell --- components/spotify/CMakeLists.txt | 1 + components/spotify/Shim.cpp | 133 +- components/spotify/cspot/bell/.clangd | 1 + components/spotify/cspot/bell/CMakeLists.txt | 37 +- .../spotify/cspot/bell/example/CMakeLists.txt | 2 +- .../spotify/cspot/bell/example/main.cpp | 31 +- .../bell/main/audio-codec/AACDecoder.cpp | 9 +- .../bell/main/audio-codec/AudioCodecs.cpp | 24 +- .../cspot/bell/main/audio-codec/BaseCodec.cpp | 4 +- .../bell/main/audio-codec/DecoderGlobals.cpp | 5 +- .../bell/main/audio-codec/MP3Decoder.cpp | 8 + .../bell/main/audio-codec/OPUSDecoder.cpp | 6 +- .../bell/main/audio-codec/VorbisDecoder.cpp | 10 +- .../main/audio-codec/include/AACDecoder.h | 7 +- .../main/audio-codec/include/AudioCodecs.h | 9 +- .../bell/main/audio-codec/include/BaseCodec.h | 3 +- .../main/audio-codec/include/DecoderGlobals.h | 64 +- .../main/audio-codec/include/MP3Decoder.h | 8 +- .../main/audio-codec/include/OPUSDecoder.h | 4 +- .../main/audio-codec/include/VorbisDecoder.h | 9 +- .../main/audio-containers/AACContainer.cpp | 7 +- .../main/audio-containers/AudioContainers.cpp | 14 +- .../main/audio-containers/MP3Container.cpp | 5 + .../audio-containers/include/AACContainer.h | 12 +- .../audio-containers/include/AudioContainer.h | 2 +- .../include/AudioContainers.h | 11 +- .../audio-containers/include/MP3Container.h | 12 +- .../cspot/bell/main/audio-dsp/AudioMixer.cpp | 65 +- .../bell/main/audio-dsp/AudioPipeline.cpp | 56 +- .../cspot/bell/main/audio-dsp/BellDSP.cpp | 8 +- .../cspot/bell/main/audio-dsp/Biquad.cpp | 669 +++++----- .../cspot/bell/main/audio-dsp/BiquadCombo.cpp | 160 +-- .../cspot/bell/main/audio-dsp/Compressor.cpp | 14 +- .../cspot/bell/main/audio-dsp/Gain.cpp | 38 +- .../bell/main/audio-dsp/include/AudioMixer.h | 145 +-- .../main/audio-dsp/include/AudioPipeline.h | 45 +- .../main/audio-dsp/include/AudioTransform.h | 35 +- .../bell/main/audio-dsp/include/BellDSP.h | 38 +- .../bell/main/audio-dsp/include/Biquad.h | 256 ++-- .../bell/main/audio-dsp/include/BiquadCombo.h | 128 +- .../audio-dsp/include/CentralAudioBuffer.h | 10 +- .../bell/main/audio-dsp/include/Compressor.h | 155 ++- .../cspot/bell/main/audio-dsp/include/Gain.h | 61 +- .../audio-dsp/include/JSONTransformConfig.h | 149 +-- .../bell/main/audio-dsp/include/StreamInfo.h | 50 +- .../main/audio-dsp/include/TransformConfig.h | 210 ++- .../main/audio-sinks/esp/AC101AudioSink.cpp | 59 +- .../audio-sinks/esp/BufferedAudioSink.cpp | 58 +- .../main/audio-sinks/esp/ES8311AudioSink.cpp | 167 ++- .../main/audio-sinks/esp/ES8388AudioSink.cpp | 229 ++-- .../main/audio-sinks/esp/ES9018AudioSink.cpp | 52 +- .../audio-sinks/esp/InternalAudioSink.cpp | 51 +- .../main/audio-sinks/esp/PCM5102AudioSink.cpp | 50 +- .../main/audio-sinks/esp/SPDIFAudioSink.cpp | 204 ++- .../main/audio-sinks/esp/TAS5711AudioSink.cpp | 164 ++- .../cspot/bell/main/audio-sinks/esp/ac101.c | 514 ++++---- .../cspot/bell/main/audio-sinks/esp/es8311.c | 1154 +++++++++-------- .../bell/main/audio-sinks/include/AudioSink.h | 32 +- .../audio-sinks/include/esp/AC101AudioSink.h | 28 +- .../include/esp/BufferedAudioSink.h | 28 +- .../audio-sinks/include/esp/ES8311AudioSink.h | 30 +- .../audio-sinks/include/esp/ES8388AudioSink.h | 52 +- .../audio-sinks/include/esp/ES9018AudioSink.h | 20 +- .../include/esp/InternalAudioSink.h | 20 +- .../include/esp/PCM5102AudioSink.h | 20 +- .../audio-sinks/include/esp/SPDIFAudioSink.h | 30 +- .../include/esp/TAS5711AudioSink.h | 28 +- .../bell/main/audio-sinks/include/esp/ac101.h | 244 ++-- .../bell/main/audio-sinks/include/esp/adac.h | 14 +- .../main/audio-sinks/include/esp/es8311.h | 132 +- .../audio-sinks/include/esp/esxxx_common.h | 248 ++-- .../audio-sinks/include/unix/ALSAAudioSink.h | 190 ++- .../include/unix/NamedPipeAudioSink.h | 25 +- .../main/audio-sinks/unix/ALSAAudioSink.cpp | 175 ++- .../audio-sinks/unix/NamedPipeAudioSink.cpp | 24 +- .../main/audio-sinks/unix/PortAudioSink.cpp | 106 +- .../cspot/bell/main/io/BellHTTPServer.cpp | 23 +- .../spotify/cspot/bell/main/io/BellTar.cpp | 20 +- .../cspot/bell/main/io/BinaryReader.cpp | 71 +- .../cspot/bell/main/io/BinaryStream.cpp | 2 +- .../cspot/bell/main/io/BufferedStream.cpp | 278 ++-- .../cspot/bell/main/io/CircularBuffer.cpp | 130 +- .../cspot/bell/main/io/EncodedAudioStream.cpp | 13 +- .../spotify/cspot/bell/main/io/FileStream.cpp | 89 +- .../spotify/cspot/bell/main/io/HTTPClient.cpp | 9 + .../cspot/bell/main/io/SocketStream.cpp | 10 +- .../spotify/cspot/bell/main/io/TLSSocket.cpp | 11 +- .../spotify/cspot/bell/main/io/URLParser.cpp | 68 +- .../spotify/cspot/bell/main/io/X509Bundle.cpp | 22 +- .../bell/main/io/include/BellHTTPServer.h | 40 +- .../cspot/bell/main/io/include/BellSocket.h | 2 +- .../cspot/bell/main/io/include/BellTar.h | 5 +- .../cspot/bell/main/io/include/BinaryReader.h | 51 +- .../cspot/bell/main/io/include/BinaryStream.h | 7 +- .../bell/main/io/include/BufferedStream.h | 134 +- .../cspot/bell/main/io/include/ByteStream.h | 28 +- .../bell/main/io/include/CircularBuffer.h | 50 +- .../bell/main/io/include/EncodedAudioStream.h | 16 +- .../cspot/bell/main/io/include/FileStream.h | 43 +- .../cspot/bell/main/io/include/HTTPClient.h | 41 +- .../cspot/bell/main/io/include/SocketStream.h | 8 +- .../cspot/bell/main/io/include/TCPSocket.h | 6 +- .../cspot/bell/main/io/include/TLSSocket.h | 32 +- .../cspot/bell/main/io/include/URLParser.h | 14 +- .../cspot/bell/main/io/include/X509Bundle.h | 10 +- .../bell/main/io/include/picohttpparser.h | 36 +- .../cspot/bell/main/io/picohttpparser.c | 1111 ++++++++-------- .../cspot/bell/main/platform/MDNSService.h | 26 +- .../bell/main/platform/WrappedSemaphore.h | 26 +- .../bell/main/platform/apple/MDNSService.cpp | 75 +- .../main/platform/apple/WrappedSemaphore.cpp | 28 +- .../bell/main/platform/esp/MDNSService.cpp | 56 +- .../main/platform/esp/WrappedSemaphore.cpp | 38 +- .../bell/main/platform/linux/MDNSService.cpp | 250 ++-- .../main/platform/linux/WrappedSemaphore.cpp | 40 +- .../bell/main/platform/win32/MDNSService.cpp | 113 +- .../main/platform/win32/WrappedSemaphore.cpp | 28 +- .../bell/main/platform/win32/win32shim.h | 12 +- .../cspot/bell/main/utilities/BellLogger.cpp | 10 +- .../cspot/bell/main/utilities/BellUtils.cpp | 6 + .../cspot/bell/main/utilities/Crypto.cpp | 12 + .../bell/main/utilities/NanoPBHelper.cpp | 106 +- .../spotify/cspot/bell/main/utilities/aes.c | 378 +++--- .../bell/main/utilities/include/BellLogger.h | 218 ++-- .../bell/main/utilities/include/BellTask.h | 2 +- .../bell/main/utilities/include/BellUtils.h | 21 +- .../bell/main/utilities/include/Crypto.h | 115 +- .../main/utilities/include/NanoPBHelper.h | 64 +- .../cspot/bell/main/utilities/include/Queue.h | 200 ++- .../bell/main/utilities/include/TimeDefs.h | 2 +- .../cspot/bell/main/utilities/include/aes.h | 50 +- .../spotify/cspot/include/AccessKeyFetcher.h | 32 +- components/spotify/cspot/include/ApResolve.h | 13 +- .../spotify/cspot/include/AuthChallenges.h | 42 +- .../spotify/cspot/include/CSpotContext.h | 8 +- .../cspot/include/ConstantParameters.h | 14 +- .../spotify/cspot/include/CspotAssert.h | 17 +- components/spotify/cspot/include/Logger.h | 9 +- components/spotify/cspot/include/LoginBlob.h | 10 +- .../spotify/cspot/include/MercurySession.h | 29 +- .../spotify/cspot/include/PlainConnection.h | 6 +- .../spotify/cspot/include/PlaybackState.h | 69 +- components/spotify/cspot/include/Shannon.h | 65 +- .../spotify/cspot/include/ShannonConnection.h | 8 +- .../spotify/cspot/include/SpircHandler.h | 35 +- .../spotify/cspot/include/TrackPlayer.h | 67 +- .../spotify/cspot/include/TrackReference.h | 47 +- components/spotify/cspot/include/Utils.h | 41 +- .../spotify/cspot/protobuf/spirc.options | 5 +- components/spotify/cspot/protobuf/spirc.proto | 4 + .../spotify/cspot/src/AccessKeyFetcher.cpp | 66 +- components/spotify/cspot/src/ApResolve.cpp | 48 +- .../spotify/cspot/src/AuthChallenges.cpp | 13 +- components/spotify/cspot/src/LoginBlob.cpp | 57 +- .../spotify/cspot/src/MercurySession.cpp | 61 +- .../spotify/cspot/src/PlainConnection.cpp | 40 +- .../spotify/cspot/src/PlaybackState.cpp | 180 +-- components/spotify/cspot/src/Shannon.cpp | 666 +++++----- .../spotify/cspot/src/ShannonConnection.cpp | 7 +- components/spotify/cspot/src/SpircHandler.cpp | 263 ++-- components/spotify/cspot/src/TimeProvider.cpp | 21 +- components/spotify/cspot/src/TrackPlayer.cpp | 271 ++-- components/spotify/cspot/src/Utils.cpp | 2 +- 163 files changed, 6611 insertions(+), 6739 deletions(-) diff --git a/components/spotify/CMakeLists.txt b/components/spotify/CMakeLists.txt index 1eaa59df..63c65cd4 100644 --- a/components/spotify/CMakeLists.txt +++ b/components/spotify/CMakeLists.txt @@ -17,6 +17,7 @@ set(BELL_DISABLE_SINKS ON) set(BELL_DISABLE_FMT ON) set(BELL_DISABLE_REGEX ON) set(BELL_ONLY_CJSON ON) +set(BELL_DISABLE_MQTT ON) set(CSPOT_TARGET_ESP32 ON) # because CMake is so broken, the cache set below overrides a normal "set" for the first build diff --git a/components/spotify/Shim.cpp b/components/spotify/Shim.cpp index eb30f70c..014d2f0d 100644 --- a/components/spotify/Shim.cpp +++ b/components/spotify/Shim.cpp @@ -34,78 +34,6 @@ static class cspotPlayer *player; -/**************************************************************************************** - * Chunk manager class (task) - */ - -class chunkManager : public bell::Task { -public: - std::atomic isRunning = true; - std::atomic isPaused = true; - chunkManager(std::function trackHandler, std::function dataHandler); - size_t writePCM(uint8_t* data, size_t bytes, std::string_view trackId, size_t sequence); - void flush(); - void teardown(); - -private: - std::unique_ptr centralAudioBuffer; - std::function trackHandler; - std::function dataHandler; - std::mutex runningMutex; - - void runTask() override; -}; - -chunkManager::chunkManager(std::function trackHandler, std::function dataHandler) - : bell::Task("chunker", 4 * 1024, 0, 0) { - this->centralAudioBuffer = std::make_unique(32); - this->trackHandler = trackHandler; - this->dataHandler = dataHandler; - startTask(); -} - -size_t chunkManager::writePCM(uint8_t* data, size_t bytes, std::string_view trackId, size_t sequence) { - return centralAudioBuffer->writePCM(data, bytes, sequence); -} - -void chunkManager::teardown() { - isRunning = false; - std::scoped_lock lock(runningMutex); -} - -void chunkManager::flush() { - centralAudioBuffer->clearBuffer(); -} - -void chunkManager::runTask() { - std::scoped_lock lock(runningMutex); - size_t lastHash = 0; - - while (isRunning) { - - if (isPaused) { - BELL_SLEEP_MS(100); - continue; - } - - auto chunk = centralAudioBuffer->readChunk(); - - if (!chunk || chunk->pcmSize == 0) { - BELL_SLEEP_MS(50); - continue; - } - - // receiving first chunk of new track from Spotify server - if (lastHash != chunk->trackHash) { - CSPOT_LOG(info, "hash update %x => %x", lastHash, chunk->trackHash); - lastHash = chunk->trackHash; - trackHandler(); - } - - dataHandler(chunk->pcmData, chunk->pcmSize); - } -} - /**************************************************************************************** * Player's main class & task */ @@ -114,19 +42,21 @@ class cspotPlayer : public bell::Task { private: std::string name; bell::WrappedSemaphore clientConnected; - + std::atomic isPaused, isConnected; + int startOffset, volume = 0, bitrate = 160; httpd_handle_t serverHandle; int serverPort; cspot_cmd_cb_t cmdHandler; cspot_data_cb_t dataHandler; + std::string lastTrackId; std::shared_ptr blob; std::unique_ptr spirc; - std::unique_ptr chunker; void eventHandler(std::unique_ptr event); void trackHandler(void); + size_t pcmWrite(uint8_t *pcm, size_t bytes, std::string_view trackId); void runTask(); @@ -155,6 +85,17 @@ cspotPlayer::cspotPlayer(const char* name, httpd_handle_t server, int port, cspo if (bitrate != 96 && bitrate != 160 && bitrate != 320) bitrate = 160; } +size_t cspotPlayer::pcmWrite(uint8_t *pcm, size_t bytes, std::string_view trackId) { + if (lastTrackId != trackId) { + CSPOT_LOG(info, "new track started <%s> => <%s>", lastTrackId.c_str(), trackId.data()); + lastTrackId = trackId; + trackHandler(); + } + + dataHandler(pcm, bytes); + return bytes; +} + extern "C" { static esp_err_t handleGET(httpd_req_t *request) { return player->handleGET(request); @@ -233,8 +174,7 @@ esp_err_t cspotPlayer::handlePOST(httpd_req_t *request) { void cspotPlayer::eventHandler(std::unique_ptr event) { switch (event->eventType) { case cspot::SpircHandler::EventType::PLAYBACK_START: { - chunker->flush(); - + lastTrackId.clear(); // we are not playing anymore trackStatus = TRACK_INIT; // memorize position for when track's beginning will be detected @@ -247,13 +187,12 @@ void cspotPlayer::eventHandler(std::unique_ptr event break; } case cspot::SpircHandler::EventType::PLAY_PAUSE: { - bool pause = std::get(event->data); - cmdHandler(pause ? CSPOT_PAUSE : CSPOT_PLAY); - chunker->isPaused = pause; + isPaused = std::get(event->data); + cmdHandler(isPaused ? CSPOT_PAUSE : CSPOT_PLAY); break; } case cspot::SpircHandler::EventType::TRACK_INFO: { - auto trackInfo = std::get(event->data); + auto trackInfo = std::get(event->data); cmdHandler(CSPOT_TRACK_INFO, trackInfo.duration, startOffset, trackInfo.artist.c_str(), trackInfo.album.c_str(), trackInfo.name.c_str(), trackInfo.imageUrl.c_str()); spirc->updatePositionMs(startOffset); @@ -264,17 +203,14 @@ void cspotPlayer::eventHandler(std::unique_ptr event case cspot::SpircHandler::EventType::PREV: case cspot::SpircHandler::EventType::FLUSH: { // FLUSH is sent when there is no next, just clean everything - chunker->flush(); cmdHandler(CSPOT_FLUSH); break; } case cspot::SpircHandler::EventType::DISC: - chunker->flush(); cmdHandler(CSPOT_DISC); - chunker->teardown(); + isConnected = false; break; case cspot::SpircHandler::EventType::SEEK: { - chunker->flush(); cmdHandler(CSPOT_SEEK, std::get(event->data)); break; } @@ -293,10 +229,9 @@ void cspotPlayer::eventHandler(std::unique_ptr event void cspotPlayer::trackHandler(void) { // this is just informative - auto trackInfo = spirc->getTrackPlayer()->getCurrentTrackInfo(); uint32_t remains; cmdHandler(CSPOT_QUERY_REMAINING, &remains); - CSPOT_LOG(info, "next track <%s> will play in %d ms", trackInfo.name.c_str(), remains); + CSPOT_LOG(info, "next track will play in %d ms", remains); // inform sink of track beginning trackStatus = TRACK_NOTIFY; @@ -317,7 +252,8 @@ void cspotPlayer::command(cspot_event_t event) { break; // setPause comes back through cspot::event with PLAY/PAUSE case CSPOT_TOGGLE: - spirc->setPause(!chunker->isPaused); + isPaused = !isPaused; + spirc->setPause(isPaused); break; case CSPOT_STOP: case CSPOT_PAUSE: @@ -326,12 +262,11 @@ void cspotPlayer::command(cspot_event_t event) { case CSPOT_PLAY: spirc->setPause(false); break; - // calling spirc->disconnect() might have been logical but it does not - // generate any cspot::event, so we need to manually force exiting player - // loop through chunker which will eventually do the disconnect + /* Calling spirc->disconnect() might have been logical but it does not + * generate any cspot::event */ case CSPOT_DISC: cmdHandler(CSPOT_DISC); - chunker->teardown(); + isConnected = false; break; // spirc->setRemoteVolume does not generate a cspot::event so call cmdHandler case CSPOT_VOLUME_UP: @@ -391,20 +326,12 @@ void cspotPlayer::runTask() { // Auth successful if (token.size() > 0) { spirc = std::make_unique(ctx); + isConnected = true; - // Create a player, pass the track handler - chunker = std::make_unique( - [this](void) { - return trackHandler(); - }, - [this](const uint8_t* data, size_t bytes) { - return dataHandler(data, bytes); - }); - // set call back to calculate a hash on trackId spirc->getTrackPlayer()->setDataCallback( - [this](uint8_t* data, size_t bytes, std::string_view trackId, size_t sequence) { - return chunker->writePCM(data, bytes, trackId, sequence); + [this](uint8_t* data, size_t bytes, std::string_view trackId) { + return pcmWrite(data, bytes, trackId); }); // set event (PLAY, VOLUME...) handler @@ -420,7 +347,7 @@ void cspotPlayer::runTask() { cmdHandler(CSPOT_VOLUME, volume); // exit when player has stopped (received a DISC) - while (chunker->isRunning) { + while (isConnected) { ctx->session->handlePacket(); // low-accuracy polling events diff --git a/components/spotify/cspot/bell/.clangd b/components/spotify/cspot/bell/.clangd index ffa67d1f..33c8b9b7 100644 --- a/components/spotify/cspot/bell/.clangd +++ b/components/spotify/cspot/bell/.clangd @@ -1,2 +1,3 @@ + CompileFlags: CompilationDatabase: example/build # Search build/ directory for compile_commands.json diff --git a/components/spotify/cspot/bell/CMakeLists.txt b/components/spotify/cspot/bell/CMakeLists.txt index 80af580b..d2aaab08 100644 --- a/components/spotify/cspot/bell/CMakeLists.txt +++ b/components/spotify/cspot/bell/CMakeLists.txt @@ -7,6 +7,7 @@ project(bell) option(BELL_DISABLE_CODECS "Disable the entire audio codec wrapper" OFF) option(BELL_CODEC_AAC "Support libhelix-aac codec" ON) option(BELL_CODEC_MP3 "Support libhelix-mp3 codec" ON) +option(BELL_DISABLE_MQTT "Disable the built-in MQTT wrapper" OFF) option(BELL_CODEC_VORBIS "Support tremor Vorbis codec" ON) option(BELL_CODEC_ALAC "Support Apple ALAC codec" ON) option(BELL_CODEC_OPUS "Support Opus codec" ON) @@ -63,13 +64,14 @@ endif() message(STATUS " Use cJSON only: ${BELL_ONLY_CJSON}") message(STATUS " Disable Fmt: ${BELL_DISABLE_FMT}") +message(STATUS " Disable Mqtt: ${BELL_DISABLE_MQTT}") message(STATUS " Disable Regex: ${BELL_DISABLE_REGEX}") # Include nanoPB library set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/external/nanopb/extra") find_package(Nanopb REQUIRED) message(${NANOPB_INCLUDE_DIRS}) -list(APPEND EXTRA_INCLUDES ${NANOPB_INCLUDE_DIRS}) +list(APPEND EXTERNAL_INCLUDES ${NANOPB_INCLUDE_DIRS}) # CMake options set(CMAKE_CXX_STANDARD 20) @@ -84,7 +86,7 @@ set(IO_DIR "${CMAKE_CURRENT_SOURCE_DIR}/main/io") set(PLATFORM_DIR "${CMAKE_CURRENT_SOURCE_DIR}/main/platform") set(UTILITIES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/main/utilities") -add_definitions("-DUSE_DEFAULT_STDLIB=1") +add_definitions("-DUSE_DEFAULT_STDLIB=1 -DTARGET_OS_IPHONE=0") # Main library sources file(GLOB SOURCES @@ -111,7 +113,7 @@ endif() if(APPLE) file(GLOB APPLE_PLATFORM_SOURCES "main/platform/apple/*.cpp" "main/platform/apple/*.c") list(APPEND SOURCES ${APPLE_PLATFORM_SOURCES}) - list(APPEND EXTRA_INCLUDES "/usr/local/opt/mbedtls@3/include") + list(APPEND EXTERNAL_INCLUDES "/usr/local/opt/mbedtls@3/include") endif() if(UNIX AND NOT APPLE) @@ -122,7 +124,7 @@ endif() if(WIN32) file(GLOB WIN32_PLATFORM_SOURCES "main/platform/win32/*.cpp" "main/platform/win32/*.c") list(APPEND SOURCES ${WIN32_PLATFORM_SOURCES}) - list(APPEND EXTRA_INCLUDES "main/platform/win32") + list(APPEND EXTERNAL_INCLUDES "main/platform/win32") endif() # A hack to make Opus keep quiet @@ -139,7 +141,7 @@ if(ESP_PLATFORM) else() find_package(Threads REQUIRED) find_package(MbedTLS REQUIRED) - list(APPEND EXTRA_INCLUDES ${MBEDTLS_INCLUDE_DIRS}) + list(APPEND EXTERNAL_INCLUDES ${MBEDTLS_INCLUDE_DIRS}) set(THREADS_PREFER_PTHREAD_FLAG ON) list(APPEND EXTRA_LIBS ${MBEDTLS_LIBRARIES} Threads::Threads) @@ -149,6 +151,14 @@ else() endif() endif() +if (NOT BELL_DISABLE_MQTT) + file(GLOB MQTT_SOURCES "external/mqtt/*.c") + list(APPEND SOURCES ${MQTT_SOURCES}) + list(APPEND EXTRA_INCLUDES "external/mqtt/include") +else() + list(REMOVE_ITEM SOURCES "${IO_DIR}/BellMQTTClient.cpp") +endif() + if(NOT BELL_DISABLE_CODECS) file(GLOB EXTRA_SOURCES "main/audio-containers/*.cpp" "main/audio-codec/*.cpp" "main/audio-codec/*.c" "main/audio-dsp/*.cpp" "main/audio-dsp/*.c") @@ -162,7 +172,7 @@ if(NOT BELL_DISABLE_CODECS) if(BELL_CODEC_AAC) file(GLOB LIBHELIX_AAC_SOURCES "external/libhelix-aac/*.c") list(APPEND LIBHELIX_SOURCES ${LIBHELIX_AAC_SOURCES}) - list(APPEND EXTRA_INCLUDES "external/libhelix-aac") + list(APPEND EXTERNAL_INCLUDES "external/libhelix-aac") list(APPEND SOURCES "${AUDIO_CODEC_DIR}/AACDecoder.cpp") list(APPEND CODEC_FLAGS "-DBELL_CODEC_AAC") endif() @@ -171,7 +181,7 @@ if(NOT BELL_DISABLE_CODECS) if(BELL_CODEC_MP3) file(GLOB LIBHELIX_MP3_SOURCES "external/libhelix-mp3/*.c") list(APPEND LIBHELIX_SOURCES ${LIBHELIX_MP3_SOURCES}) - list(APPEND EXTRA_INCLUDES "external/libhelix-mp3") + list(APPEND EXTERNAL_INCLUDES "external/libhelix-mp3") list(APPEND SOURCES "${AUDIO_CODEC_DIR}/MP3Decoder.cpp") list(APPEND CODEC_FLAGS "-DBELL_CODEC_MP3") endif() @@ -230,7 +240,7 @@ else() file(GLOB TREMOR_SOURCES "external/tremor/*.c") list(REMOVE_ITEM TREMOR_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/external/tremor/ivorbisfile_example.c") list(APPEND SOURCES ${TREMOR_SOURCES}) - list(APPEND EXTRA_INCLUDES "external/tremor") + list(APPEND EXTERNAL_INCLUDES "external/tremor") endif() if(NOT BELL_DISABLE_SINKS) @@ -247,7 +257,7 @@ if(NOT BELL_DISABLE_SINKS) # Find ALSA if required, else remove the sink if(BELL_SINK_ALSA) find_package(ALSA REQUIRED) - list(APPEND EXTRA_INCLUDES ${ALSA_INCLUDE_DIRS}) + list(APPEND EXTERNAL_INCLUDES ${ALSA_INCLUDE_DIRS}) list(APPEND EXTRA_LIBS ${ALSA_LIBRARIES}) else() list(REMOVE_ITEM SINK_SOURCES "${AUDIO_SINKS_DIR}/unix/ALSAAudioSink.cpp") @@ -256,7 +266,7 @@ if(NOT BELL_DISABLE_SINKS) # Find PortAudio if required, else remove the sink if(BELL_SINK_PORTAUDIO) find_package(Portaudio REQUIRED) - list(APPEND EXTRA_INCLUDES ${PORTAUDIO_INCLUDE_DIRS}) + list(APPEND EXTERNAL_INCLUDES ${PORTAUDIO_INCLUDE_DIRS}) list(APPEND EXTRA_LIBS ${PORTAUDIO_LIBRARIES}) else() list(REMOVE_ITEM SINK_SOURCES "${AUDIO_SINKS_DIR}/unix/PortAudioSink.cpp") @@ -274,16 +284,16 @@ if(BELL_EXTERNAL_CJSON) list(APPEND EXTRA_LIBS ${BELL_EXTERNAL_CJSON}) else() list(APPEND SOURCES "external/cJSON/cJSON.c") - list(APPEND EXTRA_INCLUDES "external/cJSON") + list(APPEND EXTERNAL_INCLUDES "external/cJSON") endif() if (NOT BELL_DISABLE_FMT) - list(APPEND EXTRA_INCLUDES "external/fmt/include") + list(APPEND EXTERNAL_INCLUDES "external/fmt/include") endif() if(WIN32 OR UNIX) list(APPEND SOURCES "external/mdnssvc/mdns.c" "external/mdnssvc/mdnsd.c") - list(APPEND EXTRA_INCLUDES "external/mdnssvc") + list(APPEND EXTERNAL_INCLUDES "external/mdnssvc") endif() # file(GLOB CIVET_SRC "external/civetweb/*.c" "external/civetweb/*.inl" "external/civetweb/*.cpp") @@ -305,6 +315,7 @@ endif() # PUBLIC to propagate esp-idf includes to bell dependents target_link_libraries(bell PUBLIC ${EXTRA_LIBS}) target_include_directories(bell PUBLIC ${EXTRA_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR}) +target_include_directories(bell SYSTEM PUBLIC ${EXTERNAL_INCLUDES}) target_compile_definitions(bell PUBLIC PB_ENABLE_MALLOC FMT_HEADER_ONLY) if(BELL_DISABLE_CODECS) diff --git a/components/spotify/cspot/bell/example/CMakeLists.txt b/components/spotify/cspot/bell/example/CMakeLists.txt index 4e1466fc..29fee79b 100644 --- a/components/spotify/cspot/bell/example/CMakeLists.txt +++ b/components/spotify/cspot/bell/example/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.18) set(CMAKE_CXX_STANDARD 20) set(CMAKE_BUILD_TYPE Debug) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - +set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../ ${CMAKE_CURRENT_BINARY_DIR}/bell) file(GLOB SOURCES "*.cpp") diff --git a/components/spotify/cspot/bell/example/main.cpp b/components/spotify/cspot/bell/example/main.cpp index 6d3a6129..0f043125 100644 --- a/components/spotify/cspot/bell/example/main.cpp +++ b/components/spotify/cspot/bell/example/main.cpp @@ -1,26 +1,14 @@ -#include #include -#include -#include -#include -#include #include -#include -#include "AudioCodecs.h" -#include "AudioContainers.h" -#include "BellHTTPServer.h" -#include "BellTar.h" +#include +#include + #include "BellTask.h" #include "CentralAudioBuffer.h" -#include "Compressor.h" -#include "DecoderGlobals.h" -#include "EncodedAudioStream.h" -#include "HTTPClient.h" #include "PortAudioSink.h" -#define DEBUG_LEVEL 4 -#include "X509Bundle.h" -#include "mbedtls/debug.h" +#include "StreamInfo.h" +#define DEBUG_LEVEL 4 #include #include @@ -58,13 +46,8 @@ class AudioPlayer : bell::Task { int main() { bell::setDefaultLogger(); - std::fstream file("system.tar", std::ios::in | std::ios::binary); - if (!file.is_open()) { - std::cout << "file not open" << std::endl; - return 1; - } - BellTar::reader reader(file); - reader.extract_all_files("./dupa2"); + BELL_LOG(info, "cock", "Published?"); + return 0; } diff --git a/components/spotify/cspot/bell/main/audio-codec/AACDecoder.cpp b/components/spotify/cspot/bell/main/audio-codec/AACDecoder.cpp index bf8d9d25..b6565b97 100644 --- a/components/spotify/cspot/bell/main/audio-codec/AACDecoder.cpp +++ b/components/spotify/cspot/bell/main/audio-codec/AACDecoder.cpp @@ -1,5 +1,12 @@ #include "AACDecoder.h" -#include + +#include // for free, malloc + +#include "CodecType.h" // for bell + +namespace bell { +class AudioContainer; +} // namespace bell using namespace bell; diff --git a/components/spotify/cspot/bell/main/audio-codec/AudioCodecs.cpp b/components/spotify/cspot/bell/main/audio-codec/AudioCodecs.cpp index 71215997..a21e513a 100644 --- a/components/spotify/cspot/bell/main/audio-codec/AudioCodecs.cpp +++ b/components/spotify/cspot/bell/main/audio-codec/AudioCodecs.cpp @@ -1,27 +1,37 @@ #include "AudioCodecs.h" -#include -#include -#include + +#include // for map, operator!=, map<>::iterator, map<>:... +#include // for remove_extent_t + +#include "AudioContainer.h" // for AudioContainer + +namespace bell { +class BaseCodec; +} // namespace bell using namespace bell; #ifdef BELL_CODEC_AAC -#include "AACDecoder.h" +#include "AACDecoder.h" // for AACDecoder + static std::shared_ptr codecAac; #endif #ifdef BELL_CODEC_MP3 -#include "MP3Decoder.h" +#include "MP3Decoder.h" // for MP3Decoder + static std::shared_ptr codecMp3; #endif #ifdef BELL_CODEC_VORBIS -#include "VorbisDecoder.h" +#include "VorbisDecoder.h" // for VorbisDecoder + static std::shared_ptr codecVorbis; #endif #ifdef BELL_CODEC_OPUS -#include "OPUSDecoder.h" +#include "OPUSDecoder.h" // for OPUSDecoder + static std::shared_ptr codecOpus; #endif diff --git a/components/spotify/cspot/bell/main/audio-codec/BaseCodec.cpp b/components/spotify/cspot/bell/main/audio-codec/BaseCodec.cpp index 12c38a71..46d5f8ad 100644 --- a/components/spotify/cspot/bell/main/audio-codec/BaseCodec.cpp +++ b/components/spotify/cspot/bell/main/audio-codec/BaseCodec.cpp @@ -1,5 +1,7 @@ #include "BaseCodec.h" -#include + +#include "AudioContainer.h" // for AudioContainer +#include "CodecType.h" // for bell using namespace bell; diff --git a/components/spotify/cspot/bell/main/audio-codec/DecoderGlobals.cpp b/components/spotify/cspot/bell/main/audio-codec/DecoderGlobals.cpp index f7c04e5e..037a5a30 100644 --- a/components/spotify/cspot/bell/main/audio-codec/DecoderGlobals.cpp +++ b/components/spotify/cspot/bell/main/audio-codec/DecoderGlobals.cpp @@ -2,7 +2,6 @@ bell::DecodersInstance* bell::decodersInstance; -void bell::createDecoders() -{ - bell::decodersInstance = new bell::DecodersInstance(); +void bell::createDecoders() { + bell::decodersInstance = new bell::DecodersInstance(); } \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-codec/MP3Decoder.cpp b/components/spotify/cspot/bell/main/audio-codec/MP3Decoder.cpp index 7a5713a4..5293d0ef 100644 --- a/components/spotify/cspot/bell/main/audio-codec/MP3Decoder.cpp +++ b/components/spotify/cspot/bell/main/audio-codec/MP3Decoder.cpp @@ -1,5 +1,13 @@ #include "MP3Decoder.h" +#include // for free, malloc + +#include "CodecType.h" // for bell + +namespace bell { +class AudioContainer; +} // namespace bell + using namespace bell; MP3Decoder::MP3Decoder() { diff --git a/components/spotify/cspot/bell/main/audio-codec/OPUSDecoder.cpp b/components/spotify/cspot/bell/main/audio-codec/OPUSDecoder.cpp index 5c3d77d9..0bd3529b 100644 --- a/components/spotify/cspot/bell/main/audio-codec/OPUSDecoder.cpp +++ b/components/spotify/cspot/bell/main/audio-codec/OPUSDecoder.cpp @@ -1,5 +1,9 @@ #include "OPUSDecoder.h" -#include "opus.h" + +#include // for free, malloc + +#include "CodecType.h" // for bell +#include "opus.h" // for opus_decoder_destroy, opus_decode, opus_decod... using namespace bell; diff --git a/components/spotify/cspot/bell/main/audio-codec/VorbisDecoder.cpp b/components/spotify/cspot/bell/main/audio-codec/VorbisDecoder.cpp index a2fc685d..a4902fad 100644 --- a/components/spotify/cspot/bell/main/audio-codec/VorbisDecoder.cpp +++ b/components/spotify/cspot/bell/main/audio-codec/VorbisDecoder.cpp @@ -1,5 +1,13 @@ #include "VorbisDecoder.h" -#include "AudioCodecs.h" + +#include // for free, malloc + +#include "CodecType.h" // for bell +#include "config_types.h" // for ogg_int16_t + +namespace bell { +class AudioContainer; +} // namespace bell using namespace bell; diff --git a/components/spotify/cspot/bell/main/audio-codec/include/AACDecoder.h b/components/spotify/cspot/bell/main/audio-codec/include/AACDecoder.h index 6c1c7eab..e44b101c 100644 --- a/components/spotify/cspot/bell/main/audio-codec/include/AACDecoder.h +++ b/components/spotify/cspot/bell/main/audio-codec/include/AACDecoder.h @@ -1,9 +1,12 @@ #pragma once -#include "BaseCodec.h" -#include "aacdec.h" +#include // for uint8_t, uint32_t, int16_t + +#include "BaseCodec.h" // for BaseCodec +#include "aacdec.h" // for AACFrameInfo, HAACDecoder namespace bell { +class AudioContainer; class AACDecoder : public BaseCodec { private: diff --git a/components/spotify/cspot/bell/main/audio-codec/include/AudioCodecs.h b/components/spotify/cspot/bell/main/audio-codec/include/AudioCodecs.h index a230cb1a..2d5de6a7 100644 --- a/components/spotify/cspot/bell/main/audio-codec/include/AudioCodecs.h +++ b/components/spotify/cspot/bell/main/audio-codec/include/AudioCodecs.h @@ -1,11 +1,12 @@ #pragma once -#include -#include "BaseCodec.h" -#include "AudioContainer.h" +#include // for shared_ptr + +#include "AudioContainer.h" // for AudioContainer +#include "BaseCodec.h" // for BaseCodec +#include "CodecType.h" // for AudioCodec namespace bell { - class AudioCodecs { public: static std::shared_ptr getCodec(AudioCodec type); diff --git a/components/spotify/cspot/bell/main/audio-codec/include/BaseCodec.h b/components/spotify/cspot/bell/main/audio-codec/include/BaseCodec.h index 59d533ab..8d6270bf 100644 --- a/components/spotify/cspot/bell/main/audio-codec/include/BaseCodec.h +++ b/components/spotify/cspot/bell/main/audio-codec/include/BaseCodec.h @@ -1,8 +1,9 @@ #pragma once -#include "AudioContainer.h" +#include // for uint32_t, uint8_t namespace bell { +class AudioContainer; class BaseCodec { private: diff --git a/components/spotify/cspot/bell/main/audio-codec/include/DecoderGlobals.h b/components/spotify/cspot/bell/main/audio-codec/include/DecoderGlobals.h index 5a03a3a5..f8335a25 100644 --- a/components/spotify/cspot/bell/main/audio-codec/include/DecoderGlobals.h +++ b/components/spotify/cspot/bell/main/audio-codec/include/DecoderGlobals.h @@ -5,48 +5,40 @@ #define AAC_READBUF_SIZE (4 * AAC_MAINBUF_SIZE * AAC_MAX_NCHANS) #define MP3_READBUF_SIZE (2 * 1024); -#include -#include -#include -#include "aacdec.h" -#include "mp3dec.h" +#include // for NULL -namespace bell -{ - class DecodersInstance - { - public: - DecodersInstance(){}; - ~DecodersInstance() - { - MP3FreeDecoder(mp3Decoder); - AACFreeDecoder(aacDecoder); - }; +#include "aacdec.h" // for AACFreeDecoder, AACInitDecoder, HAACDecoder +#include "mp3dec.h" // for MP3FreeDecoder, MP3InitDecoder, HMP3Decoder - HAACDecoder aacDecoder = NULL; - HMP3Decoder mp3Decoder = NULL; +namespace bell { +class DecodersInstance { + public: + DecodersInstance(){}; + ~DecodersInstance() { + MP3FreeDecoder(mp3Decoder); + AACFreeDecoder(aacDecoder); + }; - void ensureAAC() - { - if (aacDecoder == NULL) - { - aacDecoder = AACInitDecoder(); - } - } + HAACDecoder aacDecoder = NULL; + HMP3Decoder mp3Decoder = NULL; - void ensureMP3() - { - if (mp3Decoder == NULL) - { - mp3Decoder = MP3InitDecoder(); - } - } - }; + void ensureAAC() { + if (aacDecoder == NULL) { + aacDecoder = AACInitDecoder(); + } + } - extern bell::DecodersInstance* decodersInstance; + void ensureMP3() { + if (mp3Decoder == NULL) { + mp3Decoder = MP3InitDecoder(); + } + } +}; - void createDecoders(); -} +extern bell::DecodersInstance* decodersInstance; + +void createDecoders(); +} // namespace bell #endif #endif diff --git a/components/spotify/cspot/bell/main/audio-codec/include/MP3Decoder.h b/components/spotify/cspot/bell/main/audio-codec/include/MP3Decoder.h index a9509c57..f38df171 100644 --- a/components/spotify/cspot/bell/main/audio-codec/include/MP3Decoder.h +++ b/components/spotify/cspot/bell/main/audio-codec/include/MP3Decoder.h @@ -1,9 +1,13 @@ #pragma once -#include "BaseCodec.h" -#include "mp3dec.h" +#include // for uint8_t, uint32_t, int16_t + +#include "BaseCodec.h" // for BaseCodec +#include "mp3dec.h" // for HMP3Decoder, MP3FrameInfo namespace bell { +class AudioContainer; + class MP3Decoder : public BaseCodec { private: HMP3Decoder mp3; diff --git a/components/spotify/cspot/bell/main/audio-codec/include/OPUSDecoder.h b/components/spotify/cspot/bell/main/audio-codec/include/OPUSDecoder.h index 13780d20..49c78ca7 100644 --- a/components/spotify/cspot/bell/main/audio-codec/include/OPUSDecoder.h +++ b/components/spotify/cspot/bell/main/audio-codec/include/OPUSDecoder.h @@ -1,6 +1,8 @@ #pragma once -#include "BaseCodec.h" +#include // for uint8_t, uint32_t, int16_t + +#include "BaseCodec.h" // for BaseCodec struct OpusDecoder; diff --git a/components/spotify/cspot/bell/main/audio-codec/include/VorbisDecoder.h b/components/spotify/cspot/bell/main/audio-codec/include/VorbisDecoder.h index 05a8d030..f7ed6285 100644 --- a/components/spotify/cspot/bell/main/audio-codec/include/VorbisDecoder.h +++ b/components/spotify/cspot/bell/main/audio-codec/include/VorbisDecoder.h @@ -1,9 +1,14 @@ #pragma once -#include "BaseCodec.h" -#include "ivorbiscodec.h" +#include // for uint8_t, uint32_t, int16_t + +#include "BaseCodec.h" // for BaseCodec +#include "ivorbiscodec.h" // for vorbis_comment, vorbis_dsp_state, vorbis_info +#include "ogg.h" // for ogg_packet namespace bell { +class AudioContainer; + class VorbisDecoder : public BaseCodec { private: vorbis_info* vi = nullptr; diff --git a/components/spotify/cspot/bell/main/audio-containers/AACContainer.cpp b/components/spotify/cspot/bell/main/audio-containers/AACContainer.cpp index b4c8f18f..314fd999 100644 --- a/components/spotify/cspot/bell/main/audio-containers/AACContainer.cpp +++ b/components/spotify/cspot/bell/main/audio-containers/AACContainer.cpp @@ -1,5 +1,10 @@ #include "AACContainer.h" -#include "iostream" + +#include // for memmove + +#include "StreamInfo.h" // for BitWidth, BitWidth::BW_16, SampleRate, Sampl... +#include "aacdec.h" // for AACFindSyncWord + using namespace bell; #define SYNC_WORLD_LEN 4 diff --git a/components/spotify/cspot/bell/main/audio-containers/AudioContainers.cpp b/components/spotify/cspot/bell/main/audio-containers/AudioContainers.cpp index 68940a51..bd8f45b5 100644 --- a/components/spotify/cspot/bell/main/audio-containers/AudioContainers.cpp +++ b/components/spotify/cspot/bell/main/audio-containers/AudioContainers.cpp @@ -1,5 +1,16 @@ #include "AudioContainers.h" +#include // for memcmp +#include // for byte + +#include "AACContainer.h" // for AACContainer +#include "CodecType.h" // for bell +#include "MP3Container.h" // for MP3Container + +namespace bell { +class AudioContainer; +} // namespace bell + using namespace bell; std::unique_ptr AudioContainers::guessAudioContainer( @@ -7,8 +18,7 @@ std::unique_ptr AudioContainers::guessAudioContainer( std::byte tmp[14]; istr.read((char*)tmp, sizeof(tmp)); - if (memcmp(tmp, "\xFF\xF1", 2) == 0 || - memcmp(tmp, "\xFF\xF9", 2) == 0) { + if (memcmp(tmp, "\xFF\xF1", 2) == 0 || memcmp(tmp, "\xFF\xF9", 2) == 0) { // AAC found std::cout << "AAC" << std::endl; return std::make_unique(istr); diff --git a/components/spotify/cspot/bell/main/audio-containers/MP3Container.cpp b/components/spotify/cspot/bell/main/audio-containers/MP3Container.cpp index 08629464..98bb6b66 100644 --- a/components/spotify/cspot/bell/main/audio-containers/MP3Container.cpp +++ b/components/spotify/cspot/bell/main/audio-containers/MP3Container.cpp @@ -1,5 +1,10 @@ #include "MP3Container.h" +#include // for memmove + +#include "StreamInfo.h" // for BitWidth, BitWidth::BW_16, SampleRate, Sampl... +#include "mp3dec.h" // for MP3FindSyncWord + using namespace bell; MP3Container::MP3Container(std::istream& istr) : bell::AudioContainer(istr) {} diff --git a/components/spotify/cspot/bell/main/audio-containers/include/AACContainer.h b/components/spotify/cspot/bell/main/audio-containers/include/AACContainer.h index d283c0c1..da8d9443 100644 --- a/components/spotify/cspot/bell/main/audio-containers/include/AACContainer.h +++ b/components/spotify/cspot/bell/main/audio-containers/include/AACContainer.h @@ -1,10 +1,12 @@ #pragma once -#include -#include -#include -#include "AudioContainer.h" -#include "aacdec.h" +#include // for uint32_t +#include // for byte, size_t +#include // for istream +#include // for vector + +#include "AudioContainer.h" // for AudioContainer +#include "CodecType.h" // for AudioCodec, AudioCodec::AAC namespace bell { class AACContainer : public AudioContainer { diff --git a/components/spotify/cspot/bell/main/audio-containers/include/AudioContainer.h b/components/spotify/cspot/bell/main/audio-containers/include/AudioContainer.h index 38648a0e..2c25cb96 100644 --- a/components/spotify/cspot/bell/main/audio-containers/include/AudioContainer.h +++ b/components/spotify/cspot/bell/main/audio-containers/include/AudioContainer.h @@ -1,8 +1,8 @@ #pragma once #include -#include #include +#include #include "CodecType.h" #include "StreamInfo.h" diff --git a/components/spotify/cspot/bell/main/audio-containers/include/AudioContainers.h b/components/spotify/cspot/bell/main/audio-containers/include/AudioContainers.h index a4c0e3af..a6369e1f 100644 --- a/components/spotify/cspot/bell/main/audio-containers/include/AudioContainers.h +++ b/components/spotify/cspot/bell/main/audio-containers/include/AudioContainers.h @@ -1,10 +1,11 @@ #pragma once -#include -#include -#include "AACContainer.h" -#include "AudioContainer.h" -#include "MP3Container.h" +#include // for istream +#include // for unique_ptr + +namespace bell { +class AudioContainer; +} // namespace bell namespace bell::AudioContainers { std::unique_ptr guessAudioContainer(std::istream& istr); diff --git a/components/spotify/cspot/bell/main/audio-containers/include/MP3Container.h b/components/spotify/cspot/bell/main/audio-containers/include/MP3Container.h index 028e17b6..d4bbfd0e 100644 --- a/components/spotify/cspot/bell/main/audio-containers/include/MP3Container.h +++ b/components/spotify/cspot/bell/main/audio-containers/include/MP3Container.h @@ -1,10 +1,12 @@ #pragma once -#include -#include -#include -#include "AudioContainer.h" -#include "mp3dec.h" +#include // for uint32_t +#include // for byte, size_t +#include // for istream +#include // for vector + +#include "AudioContainer.h" // for AudioContainer +#include "CodecType.h" // for AudioCodec, AudioCodec::MP3 namespace bell { class MP3Container : public AudioContainer { diff --git a/components/spotify/cspot/bell/main/audio-dsp/AudioMixer.cpp b/components/spotify/cspot/bell/main/audio-dsp/AudioMixer.cpp index 1c0c486a..fd0f7e4d 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/AudioMixer.cpp +++ b/components/spotify/cspot/bell/main/audio-dsp/AudioMixer.cpp @@ -1,39 +1,44 @@ #include "AudioMixer.h" +#include // for scoped_lock + using namespace bell; -AudioMixer::AudioMixer() { -} +AudioMixer::AudioMixer() {} -std::unique_ptr AudioMixer::process(std::unique_ptr info) { - std::scoped_lock lock(this->accessMutex); - if (info->numChannels != from) { - throw std::runtime_error("AudioMixer: Input channel count does not match configuration"); - } - info->numChannels = to; +std::unique_ptr AudioMixer::process( + std::unique_ptr info) { + std::scoped_lock lock(this->accessMutex); + if (info->numChannels != from) { + throw std::runtime_error( + "AudioMixer: Input channel count does not match configuration"); + } + info->numChannels = to; - for (auto &singleConf : mixerConfig) { - if (singleConf.source.size() == 1) { - if (singleConf.source[0] == singleConf.destination) { - continue; - } - // Copy channel - for (int i = 0; i < info->numSamples; i++) { - info->data[singleConf.destination][i] = info->data[singleConf.source[0]][i]; - } - } else { - // Mix channels - float sample = 0.0f; - for (int i = 0; i < info->numSamples; i++) { - sample = 0.0; - for (auto &source : singleConf.source) { - sample += info->data[source][i]; - } - - info->data[singleConf.destination][i] = sample / (float) singleConf.source.size(); - } + for (auto& singleConf : mixerConfig) { + if (singleConf.source.size() == 1) { + if (singleConf.source[0] == singleConf.destination) { + continue; + } + // Copy channel + for (int i = 0; i < info->numSamples; i++) { + info->data[singleConf.destination][i] = + info->data[singleConf.source[0]][i]; + } + } else { + // Mix channels + float sample = 0.0f; + for (int i = 0; i < info->numSamples; i++) { + sample = 0.0; + for (auto& source : singleConf.source) { + sample += info->data[source][i]; } - } - return info; + info->data[singleConf.destination][i] = + sample / (float)singleConf.source.size(); + } + } + } + + return info; } \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-dsp/AudioPipeline.cpp b/components/spotify/cspot/bell/main/audio-dsp/AudioPipeline.cpp index 23b39fc5..853e1f2b 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/AudioPipeline.cpp +++ b/components/spotify/cspot/bell/main/audio-dsp/AudioPipeline.cpp @@ -1,47 +1,53 @@ #include "AudioPipeline.h" -#include -#include "BellLogger.h" + +#include // for remove_extent_t +#include // for move + +#include "AudioTransform.h" // for AudioTransform +#include "BellLogger.h" // for AbstractLogger, BELL_LOG +#include "TransformConfig.h" // for TransformConfig using namespace bell; -AudioPipeline::AudioPipeline() { +AudioPipeline::AudioPipeline(){ // this->headroomGainTransform = std::make_shared(Channels::LEFT_RIGHT); // this->transforms.push_back(this->headroomGainTransform); }; void AudioPipeline::addTransform(std::shared_ptr transform) { - transforms.push_back(transform); - recalculateHeadroom(); + transforms.push_back(transform); + recalculateHeadroom(); } void AudioPipeline::recalculateHeadroom() { - float headroom = 0.0f; + float headroom = 0.0f; - // Find largest headroom required by any transform down the chain, and apply it - for (auto transform : transforms) { - if (headroom < transform->calculateHeadroom()) { - headroom = transform->calculateHeadroom(); - } + // Find largest headroom required by any transform down the chain, and apply it + for (auto transform : transforms) { + if (headroom < transform->calculateHeadroom()) { + headroom = transform->calculateHeadroom(); } + } - // headroomGainTransform->configure(-headroom); + // headroomGainTransform->configure(-headroom); } void AudioPipeline::volumeUpdated(int volume) { - BELL_LOG(debug, "AudioPipeline", "Requested"); - std::scoped_lock lock(this->accessMutex); - for (auto transform : transforms) { - transform->config->currentVolume = volume; - transform->reconfigure(); - } - BELL_LOG(debug, "AudioPipeline", "Volume applied, DSP reconfigured"); + BELL_LOG(debug, "AudioPipeline", "Requested"); + std::scoped_lock lock(this->accessMutex); + for (auto transform : transforms) { + transform->config->currentVolume = volume; + transform->reconfigure(); + } + BELL_LOG(debug, "AudioPipeline", "Volume applied, DSP reconfigured"); } -std::unique_ptr AudioPipeline::process(std::unique_ptr data) { - std::scoped_lock lock(this->accessMutex); - for (auto &transform : transforms) { - data = transform->process(std::move(data)); - } +std::unique_ptr AudioPipeline::process( + std::unique_ptr data) { + std::scoped_lock lock(this->accessMutex); + for (auto& transform : transforms) { + data = transform->process(std::move(data)); + } - return data; + return data; } \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-dsp/BellDSP.cpp b/components/spotify/cspot/bell/main/audio-dsp/BellDSP.cpp index 56d1d3ac..d6f04272 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/BellDSP.cpp +++ b/components/spotify/cspot/bell/main/audio-dsp/BellDSP.cpp @@ -1,6 +1,10 @@ #include "BellDSP.h" -#include -#include "CentralAudioBuffer.h" + +#include // for remove_extent_t +#include // for move + +#include "AudioPipeline.h" // for CentralAudioBuffer +#include "CentralAudioBuffer.h" // for CentralAudioBuffer using namespace bell; diff --git a/components/spotify/cspot/bell/main/audio-dsp/Biquad.cpp b/components/spotify/cspot/bell/main/audio-dsp/Biquad.cpp index 20391fa0..858593bd 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/Biquad.cpp +++ b/components/spotify/cspot/bell/main/audio-dsp/Biquad.cpp @@ -1,466 +1,439 @@ #include "Biquad.h" +#include // for pow, cosf, sinf, M_PI, sqrtf, tanf, logf, sinh + using namespace bell; -Biquad::Biquad() -{ - this->filterType = "biquad"; +Biquad::Biquad() { + this->filterType = "biquad"; } -void Biquad::sampleRateChanged(uint32_t sampleRate) -{ - this->sampleRate = sampleRate; - //this->configure(this->type, this->currentConfig); +void Biquad::sampleRateChanged(uint32_t sampleRate) { + this->sampleRate = sampleRate; + //this->configure(this->type, this->currentConfig); } -void Biquad::configure(Type type, std::map &newConf) -{ - this->type = type; - this->currentConfig = newConf; +void Biquad::configure(Type type, std::map& newConf) { + this->type = type; + this->currentConfig = newConf; - switch (type) - { + switch (type) { case Type::Free: - coeffs[0] = newConf["a1"]; - coeffs[1] = newConf["a2"]; - coeffs[2] = newConf["b0"]; - coeffs[3] = newConf["b1"]; - coeffs[4] = newConf["b2"]; - break; + coeffs[0] = newConf["a1"]; + coeffs[1] = newConf["a2"]; + coeffs[2] = newConf["b0"]; + coeffs[3] = newConf["b1"]; + coeffs[4] = newConf["b2"]; + break; case Type::Highpass: - highPassCoEffs(newConf["freq"], newConf["q"]); - break; + highPassCoEffs(newConf["freq"], newConf["q"]); + break; case Type::HighpassFO: - highPassFOCoEffs(newConf["freq"]); - break; + highPassFOCoEffs(newConf["freq"]); + break; case Type::Lowpass: - lowPassCoEffs(newConf["freq"], newConf["q"]); - break; + lowPassCoEffs(newConf["freq"], newConf["q"]); + break; case Type::LowpassFO: - lowPassFOCoEffs(newConf["freq"]); - break; + lowPassFOCoEffs(newConf["freq"]); + break; case Type::Highshelf: - // check if config has slope key - if (newConf.find("slope") != newConf.end()) - { - highShelfCoEffsSlope(newConf["freq"], newConf["gain"], newConf["slope"]); - } - else - { - highShelfCoEffs(newConf["freq"], newConf["gain"], newConf["q"]); - } - break; + // check if config has slope key + if (newConf.find("slope") != newConf.end()) { + highShelfCoEffsSlope(newConf["freq"], newConf["gain"], + newConf["slope"]); + } else { + highShelfCoEffs(newConf["freq"], newConf["gain"], newConf["q"]); + } + break; case Type::HighshelfFO: - highShelfFOCoEffs(newConf["freq"], newConf["gain"]); - break; + highShelfFOCoEffs(newConf["freq"], newConf["gain"]); + break; case Type::Lowshelf: - // check if config has slope key - if (newConf.find("slope") != newConf.end()) - { - lowShelfCoEffsSlope(newConf["freq"], newConf["gain"], newConf["slope"]); - } - else - { - lowShelfCoEffs(newConf["freq"], newConf["gain"], newConf["q"]); - } - break; + // check if config has slope key + if (newConf.find("slope") != newConf.end()) { + lowShelfCoEffsSlope(newConf["freq"], newConf["gain"], newConf["slope"]); + } else { + lowShelfCoEffs(newConf["freq"], newConf["gain"], newConf["q"]); + } + break; case Type::LowshelfFO: - lowShelfFOCoEffs(newConf["freq"], newConf["gain"]); - break; + lowShelfFOCoEffs(newConf["freq"], newConf["gain"]); + break; case Type::Peaking: - // check if config has bandwidth key - if (newConf.find("bandwidth") != newConf.end()) - { - peakCoEffsBandwidth(newConf["freq"], newConf["gain"], newConf["bandwidth"]); - } - else - { - peakCoEffs(newConf["freq"], newConf["gain"], newConf["q"]); - } - break; + // check if config has bandwidth key + if (newConf.find("bandwidth") != newConf.end()) { + peakCoEffsBandwidth(newConf["freq"], newConf["gain"], + newConf["bandwidth"]); + } else { + peakCoEffs(newConf["freq"], newConf["gain"], newConf["q"]); + } + break; case Type::Notch: - if (newConf.find("bandwidth") != newConf.end()) - { - notchCoEffsBandwidth(newConf["freq"], newConf["gain"], newConf["bandwidth"]); - } - else - { - notchCoEffs(newConf["freq"], newConf["gain"], newConf["q"]); - } - break; + if (newConf.find("bandwidth") != newConf.end()) { + notchCoEffsBandwidth(newConf["freq"], newConf["gain"], + newConf["bandwidth"]); + } else { + notchCoEffs(newConf["freq"], newConf["gain"], newConf["q"]); + } + break; case Type::Bandpass: - if (newConf.find("bandwidth") != newConf.end()) - { - bandPassCoEffsBandwidth(newConf["freq"], newConf["bandwidth"]); - } - else - { - bandPassCoEffs(newConf["freq"], newConf["q"]); - } - break; + if (newConf.find("bandwidth") != newConf.end()) { + bandPassCoEffsBandwidth(newConf["freq"], newConf["bandwidth"]); + } else { + bandPassCoEffs(newConf["freq"], newConf["q"]); + } + break; case Type::Allpass: - if (newConf.find("bandwidth") != newConf.end()) - { - allPassCoEffsBandwidth(newConf["freq"], newConf["bandwidth"]); - } - else - { - allPassCoEffs(newConf["freq"], newConf["q"]); - } - break; + if (newConf.find("bandwidth") != newConf.end()) { + allPassCoEffsBandwidth(newConf["freq"], newConf["bandwidth"]); + } else { + allPassCoEffs(newConf["freq"], newConf["q"]); + } + break; case Type::AllpassFO: - allPassFOCoEffs(newConf["freq"]); - break; - } + allPassFOCoEffs(newConf["freq"]); + break; + } } // coefficients for a high pass biquad filter -void Biquad::highPassCoEffs(float f, float q) -{ - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = s / (2 * q); +void Biquad::highPassCoEffs(float f, float q) { + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = s / (2 * q); - float b0 = (1 + c) / 2; - float b1 = -(1 + c); - float b2 = b0; - float a0 = 1 + alpha; - float a1 = -2 * c; - float a2 = 1 - alpha; + float b0 = (1 + c) / 2; + float b1 = -(1 + c); + float b2 = b0; + float a0 = 1 + alpha; + float a1 = -2 * c; + float a2 = 1 - alpha; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } // coefficients for a high pass first order biquad filter -void Biquad::highPassFOCoEffs(float f) -{ - float w0 = 2 * M_PI * f / this->sampleRate; - float k = tanf(w0 / 2.0); +void Biquad::highPassFOCoEffs(float f) { + float w0 = 2 * M_PI * f / this->sampleRate; + float k = tanf(w0 / 2.0); - float alpha = 1.0 + k; + float alpha = 1.0 + k; - float b0 = 1.0 / alpha; - float b1 = -1.0 / alpha; - float b2 = 0.0; - float a0 = 1.0; - float a1 = -(1.0 - k) / alpha; - float a2 = 0.0; + float b0 = 1.0 / alpha; + float b1 = -1.0 / alpha; + float b2 = 0.0; + float a0 = 1.0; + float a1 = -(1.0 - k) / alpha; + float a2 = 0.0; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } // coefficients for a low pass biquad filter -void Biquad::lowPassCoEffs(float f, float q) -{ - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = s / (2 * q); +void Biquad::lowPassCoEffs(float f, float q) { + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = s / (2 * q); - float b0 = (1 - c) / 2; - float b1 = 1 - c; - float b2 = b0; - float a0 = 1 + alpha; - float a1 = -2 * c; - float a2 = 1 - alpha; + float b0 = (1 - c) / 2; + float b1 = 1 - c; + float b2 = b0; + float a0 = 1 + alpha; + float a1 = -2 * c; + float a2 = 1 - alpha; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } // coefficients for a low pass first order biquad filter -void Biquad::lowPassFOCoEffs(float f) -{ - float w0 = 2 * M_PI * f / this->sampleRate; - float k = tanf(w0 / 2.0); +void Biquad::lowPassFOCoEffs(float f) { + float w0 = 2 * M_PI * f / this->sampleRate; + float k = tanf(w0 / 2.0); - float alpha = 1.0 + k; + float alpha = 1.0 + k; - float b0 = k / alpha; - float b1 = k / alpha; - float b2 = 0.0; - float a0 = 1.0; - float a1 = -(1.0 - k) / alpha; - float a2 = 0.0; + float b0 = k / alpha; + float b1 = k / alpha; + float b2 = 0.0; + float a0 = 1.0; + float a1 = -(1.0 - k) / alpha; + float a2 = 0.0; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } // coefficients for a peak biquad filter -void Biquad::peakCoEffs(float f, float gain, float q) -{ - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = s / (2 * q); +void Biquad::peakCoEffs(float f, float gain, float q) { + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = s / (2 * q); - float ampl = std::pow(10.0f, gain / 40.0f); - float b0 = 1.0 + (alpha * ampl); - float b1 = -2.0 * c; - float b2 = 1.0 - (alpha * ampl); - float a0 = 1 + (alpha / ampl); - float a1 = -2 * c; - float a2 = 1 - (alpha / ampl); + float ampl = std::pow(10.0f, gain / 40.0f); + float b0 = 1.0 + (alpha * ampl); + float b1 = -2.0 * c; + float b2 = 1.0 - (alpha * ampl); + float a0 = 1 + (alpha / ampl); + float a1 = -2 * c; + float a2 = 1 - (alpha / ampl); - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } -void Biquad::peakCoEffsBandwidth(float f, float gain, float bandwidth) -{ - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s); +void Biquad::peakCoEffsBandwidth(float f, float gain, float bandwidth) { + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s); - float ampl = std::pow(10.0f, gain / 40.0f); - float b0 = 1.0 + (alpha * ampl); - float b1 = -2.0 * c; - float b2 = 1.0 - (alpha * ampl); - float a0 = 1 + (alpha / ampl); - float a1 = -2 * c; - float a2 = 1 - (alpha / ampl); + float ampl = std::pow(10.0f, gain / 40.0f); + float b0 = 1.0 + (alpha * ampl); + float b1 = -2.0 * c; + float b2 = 1.0 - (alpha * ampl); + float a0 = 1 + (alpha / ampl); + float a1 = -2 * c; + float a2 = 1 - (alpha / ampl); - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } -void Biquad::highShelfCoEffs(float f, float gain, float q) -{ - float A = std::pow(10.0f, gain / 40.0f); - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = s / (2 * q); - float beta = s * sqrtf(A) / q; - float b0 = A * ((A + 1.0) + (A - 1.0) * c + beta); - float b1 = -2.0 * A * ((A - 1.0) + (A + 1.0) * c); - float b2 = A * ((A + 1.0) + (A - 1.0) * c - beta); - float a0 = (A + 1.0) - (A - 1.0) * c + beta; - float a1 = 2.0 * ((A - 1.0) - (A + 1.0) * c); - float a2 = (A + 1.0) - (A - 1.0) * c - beta; +void Biquad::highShelfCoEffs(float f, float gain, float q) { + float A = std::pow(10.0f, gain / 40.0f); + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = s / (2 * q); + float beta = s * sqrtf(A) / q; + float b0 = A * ((A + 1.0) + (A - 1.0) * c + beta); + float b1 = -2.0 * A * ((A - 1.0) + (A + 1.0) * c); + float b2 = A * ((A + 1.0) + (A - 1.0) * c - beta); + float a0 = (A + 1.0) - (A - 1.0) * c + beta; + float a1 = 2.0 * ((A - 1.0) - (A + 1.0) * c); + float a2 = (A + 1.0) - (A - 1.0) * c - beta; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } -void Biquad::highShelfCoEffsSlope(float f, float gain, float slope) -{ - float A = std::pow(10.0f, gain / 40.0f); - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = - s / 2.0 * sqrtf((A + 1.0 / A) * (1.0 / (slope / 12.0) - 1.0) + 2.0); - float beta = 2.0 * sqrtf(A) * alpha; - float b0 = A * ((A + 1.0) + (A - 1.0) * c + beta); - float b1 = -2.0 * A * ((A - 1.0) + (A + 1.0) * c); - float b2 = A * ((A + 1.0) + (A - 1.0) * c - beta); - float a0 = (A + 1.0) - (A - 1.0) * c + beta; - float a1 = 2.0 * ((A - 1.0) - (A + 1.0) * c); - float a2 = (A + 1.0) - (A - 1.0) * c - beta; +void Biquad::highShelfCoEffsSlope(float f, float gain, float slope) { + float A = std::pow(10.0f, gain / 40.0f); + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = + s / 2.0 * sqrtf((A + 1.0 / A) * (1.0 / (slope / 12.0) - 1.0) + 2.0); + float beta = 2.0 * sqrtf(A) * alpha; + float b0 = A * ((A + 1.0) + (A - 1.0) * c + beta); + float b1 = -2.0 * A * ((A - 1.0) + (A + 1.0) * c); + float b2 = A * ((A + 1.0) + (A - 1.0) * c - beta); + float a0 = (A + 1.0) - (A - 1.0) * c + beta; + float a1 = 2.0 * ((A - 1.0) - (A + 1.0) * c); + float a2 = (A + 1.0) - (A - 1.0) * c - beta; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } -void Biquad::highShelfFOCoEffs(float f, float gain) -{ - float A = std::pow(10.0f, gain / 40.0f); - float w0 = 2 * M_PI * f / this->sampleRate; - float tn = tanf(w0 / 2.0); +void Biquad::highShelfFOCoEffs(float f, float gain) { + float A = std::pow(10.0f, gain / 40.0f); + float w0 = 2 * M_PI * f / this->sampleRate; + float tn = tanf(w0 / 2.0); - float b0 = A * tn + std::pow(A, 2); - float b1 = A * tn - std::pow(A, 2); - float b2 = 0.0; - float a0 = A * tn + 1.0; - float a1 = A * tn - 1.0; - float a2 = 0.0; + float b0 = A * tn + std::pow(A, 2); + float b1 = A * tn - std::pow(A, 2); + float b2 = 0.0; + float a0 = A * tn + 1.0; + float a1 = A * tn - 1.0; + float a2 = 0.0; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } void Biquad::lowShelfCoEffs(float f, float gain, float q) { - float A = std::pow(10.0f, gain / 40.0f); - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float beta = s * sqrtf(A) / q; + float A = std::pow(10.0f, gain / 40.0f); + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float beta = s * sqrtf(A) / q; - float b0 = A * ((A + 1.0) - (A - 1.0) * c + beta); - float b1 = 2.0 * A * ((A - 1.0) - (A + 1.0) * c); - float b2 = A * ((A + 1.0) - (A - 1.0) * c - beta); - float a0 = (A + 1.0) + (A - 1.0) * c + beta; - float a1 = -2.0 * ((A - 1.0) + (A + 1.0) * c); - float a2 = (A + 1.0) + (A - 1.0) * c - beta; + float b0 = A * ((A + 1.0) - (A - 1.0) * c + beta); + float b1 = 2.0 * A * ((A - 1.0) - (A + 1.0) * c); + float b2 = A * ((A + 1.0) - (A - 1.0) * c - beta); + float a0 = (A + 1.0) + (A - 1.0) * c + beta; + float a1 = -2.0 * ((A - 1.0) + (A + 1.0) * c); + float a2 = (A + 1.0) + (A - 1.0) * c - beta; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } void Biquad::lowShelfCoEffsSlope(float f, float gain, float slope) { - float A = std::pow(10.0f, gain / 40.0f); - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = - s / 2.0 * sqrtf((A + 1.0 / A) * (1.0 / (slope / 12.0) - 1.0) + 2.0); - float beta = 2.0 * sqrtf(A) * alpha; + float A = std::pow(10.0f, gain / 40.0f); + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = + s / 2.0 * sqrtf((A + 1.0 / A) * (1.0 / (slope / 12.0) - 1.0) + 2.0); + float beta = 2.0 * sqrtf(A) * alpha; - float b0 = A * ((A + 1.0) - (A - 1.0) * c + beta); - float b1 = 2.0 * A * ((A - 1.0) - (A + 1.0) * c); - float b2 = A * ((A + 1.0) - (A - 1.0) * c - beta); - float a0 = (A + 1.0) + (A - 1.0) * c + beta; - float a1 = -2.0 * ((A - 1.0) + (A + 1.0) * c); - float a2 = (A + 1.0) + (A - 1.0) * c - beta; + float b0 = A * ((A + 1.0) - (A - 1.0) * c + beta); + float b1 = 2.0 * A * ((A - 1.0) - (A + 1.0) * c); + float b2 = A * ((A + 1.0) - (A - 1.0) * c - beta); + float a0 = (A + 1.0) + (A - 1.0) * c + beta; + float a1 = -2.0 * ((A - 1.0) + (A + 1.0) * c); + float a2 = (A + 1.0) + (A - 1.0) * c - beta; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } void Biquad::lowShelfFOCoEffs(float f, float gain) { - float A = std::pow(10.0f, gain / 40.0f); - float w0 = 2 * M_PI * f / this->sampleRate; - float tn = tanf(w0 / 2.0); + float A = std::pow(10.0f, gain / 40.0f); + float w0 = 2 * M_PI * f / this->sampleRate; + float tn = tanf(w0 / 2.0); - float b0 = std::pow(A, 2) * tn + A; - float b1 = std::pow(A, 2) * tn - A; - float b2 = 0.0; - float a0 = tn + A; - float a1 = tn - A; - float a2 = 0.0; + float b0 = std::pow(A, 2) * tn + A; + float b1 = std::pow(A, 2) * tn - A; + float b2 = 0.0; + float a0 = tn + A; + float a1 = tn - A; + float a2 = 0.0; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } void Biquad::notchCoEffs(float f, float gain, float q) { - float A = std::pow(10.0f, gain / 40.0f); - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = s / (2.0 * q); + float A = std::pow(10.0f, gain / 40.0f); + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = s / (2.0 * q); - float b0 = 1.0; - float b1 = -2.0 * c; - float b2 = 1.0; - float a0 = 1.0 + alpha; - float a1 = -2.0 * c; - float a2 = 1.0 - alpha; + float b0 = 1.0; + float b1 = -2.0 * c; + float b2 = 1.0; + float a0 = 1.0 + alpha; + float a1 = -2.0 * c; + float a2 = 1.0 - alpha; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } void Biquad::notchCoEffsBandwidth(float f, float gain, float bandwidth) { - float A = std::pow(10.0f, gain / 40.0f); - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s); + float A = std::pow(10.0f, gain / 40.0f); + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s); - float b0 = 1.0; - float b1 = -2.0 * c; - float b2 = 1.0; - float a0 = 1.0 + alpha; - float a1 = -2.0 * c; - float a2 = 1.0 - alpha; + float b0 = 1.0; + float b1 = -2.0 * c; + float b2 = 1.0; + float a0 = 1.0 + alpha; + float a1 = -2.0 * c; + float a2 = 1.0 - alpha; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } void Biquad::bandPassCoEffs(float f, float q) { - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = s / (2.0 * q); + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = s / (2.0 * q); - float b0 = alpha; - float b1 = 0.0; - float b2 = -alpha; - float a0 = 1.0 + alpha; - float a1 = -2.0 * c; - float a2 = 1.0 - alpha; + float b0 = alpha; + float b1 = 0.0; + float b2 = -alpha; + float a0 = 1.0 + alpha; + float a1 = -2.0 * c; + float a2 = 1.0 - alpha; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } void Biquad::bandPassCoEffsBandwidth(float f, float bandwidth) { - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s); + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s); - float b0 = alpha; - float b1 = 0.0; - float b2 = -alpha; - float a0 = 1.0 + alpha; - float a1 = -2.0 * c; - float a2 = 1.0 - alpha; + float b0 = alpha; + float b1 = 0.0; + float b2 = -alpha; + float a0 = 1.0 + alpha; + float a1 = -2.0 * c; + float a2 = 1.0 - alpha; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } void Biquad::allPassCoEffs(float f, float q) { - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = s / (2.0 * q); + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = s / (2.0 * q); - float b0 = 1.0 - alpha; - float b1 = -2.0 * c; - float b2 = 1.0 + alpha; - float a0 = 1.0 + alpha; - float a1 = -2.0 * c; - float a2 = 1.0 - alpha; + float b0 = 1.0 - alpha; + float b1 = -2.0 * c; + float b2 = 1.0 + alpha; + float a0 = 1.0 + alpha; + float a1 = -2.0 * c; + float a2 = 1.0 - alpha; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } void Biquad::allPassCoEffsBandwidth(float f, float bandwidth) { - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s); + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s); - float b0 = 1.0 - alpha; - float b1 = -2.0 * c; - float b2 = 1.0 + alpha; - float a0 = 1.0 + alpha; - float a1 = -2.0 * c; - float a2 = 1.0 - alpha; + float b0 = 1.0 - alpha; + float b1 = -2.0 * c; + float b2 = 1.0 + alpha; + float a0 = 1.0 + alpha; + float a1 = -2.0 * c; + float a2 = 1.0 - alpha; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } void Biquad::allPassFOCoEffs(float f) { - float w0 = 2 * M_PI * f / this->sampleRate; - float tn = tanf(w0 / 2.0); + float w0 = 2 * M_PI * f / this->sampleRate; + float tn = tanf(w0 / 2.0); - float alpha = (tn + 1.0) / (tn - 1.0); + float alpha = (tn + 1.0) / (tn - 1.0); - float b0 = 1.0; - float b1 = alpha; - float b2 = 0.0; - float a0 = alpha; - float a1 = 1.0; - float a2 = 0.0; + float b0 = 1.0; + float b1 = alpha; + float b2 = 0.0; + float a0 = alpha; + float a1 = 1.0; + float a2 = 0.0; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } -void Biquad::normalizeCoEffs(float a0, float a1, float a2, float b0, float b1, float b2) -{ - coeffs[0] = b0 / a0; - coeffs[1] = b1 / a0; - coeffs[2] = b2 / a0; - coeffs[3] = a1 / a0; - coeffs[4] = a2 / a0; +void Biquad::normalizeCoEffs(float a0, float a1, float a2, float b0, float b1, + float b2) { + coeffs[0] = b0 / a0; + coeffs[1] = b1 / a0; + coeffs[2] = b2 / a0; + coeffs[3] = a1 / a0; + coeffs[4] = a2 / a0; } -std::unique_ptr Biquad::process(std::unique_ptr stream) -{ - std::scoped_lock lock(accessMutex); +std::unique_ptr Biquad::process( + std::unique_ptr stream) { + std::scoped_lock lock(accessMutex); - auto input = stream->data[this->channel]; - auto numSamples = stream->numSamples; + auto input = stream->data[this->channel]; + auto numSamples = stream->numSamples; #ifdef ESP_PLATFORM - dsps_biquad_f32_ae32(input, input, numSamples, coeffs, w); + dsps_biquad_f32_ae32(input, input, numSamples, coeffs, w); #else - // Apply the set coefficients - for (int i = 0; i < numSamples; i++) - { - float d0 = input[i] - coeffs[3] * w[0] - coeffs[4] * w[1]; - input[i] = coeffs[0] * d0 + coeffs[1] * w[0] + coeffs[2] * w[1]; - w[1] = w[0]; - w[0] = d0; - } + // Apply the set coefficients + for (int i = 0; i < numSamples; i++) { + float d0 = input[i] - coeffs[3] * w[0] - coeffs[4] * w[1]; + input[i] = coeffs[0] * d0 + coeffs[1] * w[0] + coeffs[2] * w[1]; + w[1] = w[0]; + w[0] = d0; + } #endif - return stream; + return stream; }; diff --git a/components/spotify/cspot/bell/main/audio-dsp/BiquadCombo.cpp b/components/spotify/cspot/bell/main/audio-dsp/BiquadCombo.cpp index 8c962700..90ed50fb 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/BiquadCombo.cpp +++ b/components/spotify/cspot/bell/main/audio-dsp/BiquadCombo.cpp @@ -1,109 +1,89 @@ #include "BiquadCombo.h" +#include // for printf +#include // for sinf, M_PI +#include // for move + using namespace bell; -BiquadCombo::BiquadCombo() -{ +BiquadCombo::BiquadCombo() {} + +void BiquadCombo::sampleRateChanged(uint32_t sampleRate) { + for (auto& biquad : biquads) { + biquad->sampleRateChanged(sampleRate); + } } -void BiquadCombo::sampleRateChanged(uint32_t sampleRate) -{ - for (auto &biquad : biquads) - { - biquad->sampleRateChanged(sampleRate); - } +std::vector BiquadCombo::calculateBWQ(int order) { + + std::vector qValues; + for (int n = 0; n < order / 2; n++) { + float q = 1.0f / (2.0f * sinf(M_PI / order * (((float)n) + 0.5))); + qValues.push_back(q); + } + + if (order % 2 > 0) { + qValues.push_back(-1.0); + } + + printf("%d\n", qValues.size()); + + return qValues; } -std::vector BiquadCombo::calculateBWQ(int order) -{ +std::vector BiquadCombo::calculateLRQ(int order) { + auto qValues = calculateBWQ(order / 2); - std::vector qValues; - for (int n = 0; n < order / 2; n++) - { - float q = 1.0f / (2.0f * sinf(M_PI / order * (((float)n) + 0.5))); - qValues.push_back(q); - } + if (order % 4 > 0) { + qValues.pop_back(); + qValues.insert(qValues.end(), qValues.begin(), qValues.end()); + qValues.push_back(0.5); + } else { + qValues.insert(qValues.end(), qValues.begin(), qValues.end()); + } - if (order % 2 > 0) - { - qValues.push_back(-1.0); - } - - printf("%d\n", qValues.size()); - - return qValues; + return qValues; } -std::vector BiquadCombo::calculateLRQ(int order) -{ - auto qValues = calculateBWQ(order / 2); - - if (order % 4 > 0) - { - qValues.pop_back(); - qValues.insert(qValues.end(), qValues.begin(), qValues.end()); - qValues.push_back(0.5); - } - else - { - qValues.insert(qValues.end(), qValues.begin(), qValues.end()); - } - - return qValues; +void BiquadCombo::butterworth(float freq, int order, FilterType type) { + std::vector qValues = calculateBWQ(order); + for (auto& q : qValues) {} } -void BiquadCombo::butterworth(float freq, int order, FilterType type) -{ - std::vector qValues = calculateBWQ(order); - for (auto &q : qValues) - { +void BiquadCombo::linkwitzRiley(float freq, int order, FilterType type) { + std::vector qValues = calculateLRQ(order); + for (auto& q : qValues) { + auto filter = std::make_unique(); + filter->channel = channel; + + auto config = std::map(); + config["freq"] = freq; + config["q"] = q; + + if (q >= 0.0) { + if (type == FilterType::Highpass) { + filter->configure(Biquad::Type::Highpass, config); + } else { + filter->configure(Biquad::Type::Lowpass, config); + } + } else { + if (type == FilterType::Highpass) { + filter->configure(Biquad::Type::HighpassFO, config); + } else { + filter->configure(Biquad::Type::LowpassFO, config); + } } + + this->biquads.push_back(std::move(filter)); + } } -void BiquadCombo::linkwitzRiley(float freq, int order, FilterType type) -{ - std::vector qValues = calculateLRQ(order); - for (auto &q : qValues) - { - auto filter = std::make_unique(); - filter->channel = channel; +std::unique_ptr BiquadCombo::process( + std::unique_ptr data) { + std::scoped_lock lock(this->accessMutex); + for (auto& transform : this->biquads) { + data = transform->process(std::move(data)); + } - auto config = std::map(); - config["freq"] = freq; - config["q"] = q; - - if (q >= 0.0) - { - if (type == FilterType::Highpass) - { - filter->configure(Biquad::Type::Highpass, config); - } - else - { - filter->configure(Biquad::Type::Lowpass, config); - } - } - else - { - if (type == FilterType::Highpass) - { - filter->configure(Biquad::Type::HighpassFO, config); - } - else - { - filter->configure(Biquad::Type::LowpassFO, config); - } - } - - this->biquads.push_back(std::move(filter)); - } -} - -std::unique_ptr BiquadCombo::process(std::unique_ptr data) { - std::scoped_lock lock(this->accessMutex); - for (auto &transform : this->biquads) { - data = transform->process(std::move(data)); - } - - return data; + return data; } \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-dsp/Compressor.cpp b/components/spotify/cspot/bell/main/audio-dsp/Compressor.cpp index 95194fbf..aa8bb48b 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/Compressor.cpp +++ b/components/spotify/cspot/bell/main/audio-dsp/Compressor.cpp @@ -1,5 +1,7 @@ #include "Compressor.h" +#include // for abs + using namespace bell; float log2f_approx(float X) { @@ -19,11 +21,11 @@ float log2f_approx(float X) { Compressor::Compressor() {} -void Compressor::sumChannels(std::unique_ptr &data) { +void Compressor::sumChannels(std::unique_ptr& data) { tmp.resize(data->numSamples); for (int i = 0; i < data->numSamples; i++) { float sum = 0.0f; - for (auto &channel : channels) { + for (auto& channel : channels) { sum += data->data[channel][i]; } tmp[i] = sum; @@ -31,7 +33,7 @@ void Compressor::sumChannels(std::unique_ptr &data) { } void Compressor::calLoudness() { - for (auto &value : tmp) { + for (auto& value : tmp) { value = 20 * log10f_fast(std::abs(value) + 1.0e-9f); if (value >= lastLoudness) { value = attack * lastLoudness + (1.0 - attack) * value; @@ -44,7 +46,7 @@ void Compressor::calLoudness() { } void Compressor::calGain() { - for (auto &value : tmp) { + for (auto& value : tmp) { if (value > threshold) { value = -(value - threshold) * (factor - 1.0) / factor; } else { @@ -58,9 +60,9 @@ void Compressor::calGain() { } } -void Compressor::applyGain(std::unique_ptr &data) { +void Compressor::applyGain(std::unique_ptr& data) { for (int i = 0; i < data->numSamples; i++) { - for (auto &channel : channels) { + for (auto& channel : channels) { data->data[channel][i] *= tmp[i]; } } diff --git a/components/spotify/cspot/bell/main/audio-dsp/Gain.cpp b/components/spotify/cspot/bell/main/audio-dsp/Gain.cpp index 9775758a..a2b8fe23 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/Gain.cpp +++ b/components/spotify/cspot/bell/main/audio-dsp/Gain.cpp @@ -1,31 +1,29 @@ #include "Gain.h" +#include // for pow +#include // for string + using namespace bell; -Gain::Gain() : AudioTransform() -{ - this->gainFactor = 1.0f; - this->filterType = "gain"; +Gain::Gain() : AudioTransform() { + this->gainFactor = 1.0f; + this->filterType = "gain"; } -void Gain::configure(std::vector channels, float gainDB) -{ - this->channels = channels; - this->gainDb = gainDB; - this->gainFactor = std::pow(10.0f, gainDB / 20.0f); +void Gain::configure(std::vector channels, float gainDB) { + this->channels = channels; + this->gainDb = gainDB; + this->gainFactor = std::pow(10.0f, gainDB / 20.0f); } -std::unique_ptr Gain::process(std::unique_ptr data) -{ - std::scoped_lock lock(this->accessMutex); - for (int i = 0; i < data->numSamples; i++) - { - // Apply gain to all channels - for (auto &channel : channels) - { - data->data[channel][i] *= gainFactor; - } +std::unique_ptr Gain::process(std::unique_ptr data) { + std::scoped_lock lock(this->accessMutex); + for (int i = 0; i < data->numSamples; i++) { + // Apply gain to all channels + for (auto& channel : channels) { + data->data[channel][i] *= gainFactor; } + } - return data; + return data; } \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-dsp/include/AudioMixer.h b/components/spotify/cspot/bell/main/audio-dsp/include/AudioMixer.h index 979b878c..04b86b09 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/include/AudioMixer.h +++ b/components/spotify/cspot/bell/main/audio-dsp/include/AudioMixer.h @@ -1,89 +1,80 @@ #pragma once -#include -#include -#include +#include // for cJSON_GetObjectItem, cJSON, cJSON_IsArray +#include // for NULL +#include // for find +#include // for uint8_t +#include // for unique_ptr +#include // for invalid_argument +#include // for vector -#include "AudioTransform.h" +#include "AudioTransform.h" // for AudioTransform +#include "StreamInfo.h" // for StreamInfo -namespace bell -{ - class AudioMixer : public bell::AudioTransform - { - public: - enum DownmixMode - { - DEFAULT - }; +namespace bell { +class AudioMixer : public bell::AudioTransform { + public: + enum DownmixMode { DEFAULT }; - struct MixerConfig - { - std::vector source; - int destination; - }; + struct MixerConfig { + std::vector source; + int destination; + }; - AudioMixer(); - ~AudioMixer(){}; - // Amount of channels in the input - int from; + AudioMixer(); + ~AudioMixer(){}; + // Amount of channels in the input + int from; - // Amount of channels in the output - int to; + // Amount of channels in the output + int to; - // Configuration of each channels in the mixer - std::vector mixerConfig; + // Configuration of each channels in the mixer + std::vector mixerConfig; - std::unique_ptr process(std::unique_ptr data) override; + std::unique_ptr process( + std::unique_ptr data) override; - void reconfigure() override - { + void reconfigure() override {} + + void fromJSON(cJSON* json) { + cJSON* mappedChannels = cJSON_GetObjectItem(json, "mapped_channels"); + + if (mappedChannels == NULL || !cJSON_IsArray(mappedChannels)) { + throw std::invalid_argument("Mixer configuration invalid"); + } + + this->mixerConfig = std::vector(); + + cJSON* iterator = NULL; + cJSON_ArrayForEach(iterator, mappedChannels) { + std::vector sources(0); + cJSON* iteratorNested = NULL; + cJSON_ArrayForEach(iteratorNested, + cJSON_GetObjectItem(iterator, "source")) { + sources.push_back(iteratorNested->valueint); + } + + int destination = cJSON_GetObjectItem(iterator, "destination")->valueint; + + this->mixerConfig.push_back( + MixerConfig{.source = sources, .destination = destination}); + } + + std::vector sources(0); + + for (auto& config : mixerConfig) { + + for (auto& source : config.source) { + if (std::find(sources.begin(), sources.end(), source) == + sources.end()) { + sources.push_back(source); } + } + } - void fromJSON(cJSON *json) - { - cJSON *mappedChannels = cJSON_GetObjectItem(json, "mapped_channels"); - - if (mappedChannels == NULL || !cJSON_IsArray(mappedChannels)) - { - throw std::invalid_argument("Mixer configuration invalid"); - } - - this->mixerConfig = std::vector(); - - cJSON *iterator = NULL; - cJSON_ArrayForEach(iterator, mappedChannels) - { - std::vector sources(0); - cJSON *iteratorNested = NULL; - cJSON_ArrayForEach(iteratorNested, cJSON_GetObjectItem(iterator, "source")) - { - sources.push_back(iteratorNested->valueint); - } - - int destination = cJSON_GetObjectItem(iterator, "destination")->valueint; - - this->mixerConfig.push_back(MixerConfig{ - .source = sources, - .destination = destination - }); - } - - std::vector sources(0); - - for (auto &config : mixerConfig) - { - - for (auto &source : config.source) - { - if (std::find(sources.begin(), sources.end(), source) == sources.end()) - { - sources.push_back(source); - } - } - } - - this->from = sources.size(); - this->to = mixerConfig.size(); - } - }; -} \ No newline at end of file + this->from = sources.size(); + this->to = mixerConfig.size(); + } +}; +} // namespace bell \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-dsp/include/AudioPipeline.h b/components/spotify/cspot/bell/main/audio-dsp/include/AudioPipeline.h index 5f1b0559..211d5b57 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/include/AudioPipeline.h +++ b/components/spotify/cspot/bell/main/audio-dsp/include/AudioPipeline.h @@ -1,28 +1,29 @@ #pragma once -#include "AudioTransform.h" -#include "StreamInfo.h" -#include -#include "Gain.h" -#include +#include // for shared_ptr, unique_ptr +#include // for mutex +#include // for vector -namespace bell -{ - class AudioPipeline - { - private: - std::shared_ptr headroomGainTransform; +#include "StreamInfo.h" // for StreamInfo - public: - AudioPipeline(); - ~AudioPipeline(){}; +namespace bell { +class AudioTransform; +class Gain; - std::mutex accessMutex; - std::vector> transforms; +class AudioPipeline { + private: + std::shared_ptr headroomGainTransform; - void recalculateHeadroom(); - void addTransform(std::shared_ptr transform); - void volumeUpdated(int volume); - std::unique_ptr process(std::unique_ptr data); - }; -}; // namespace bell \ No newline at end of file + public: + AudioPipeline(); + ~AudioPipeline(){}; + + std::mutex accessMutex; + std::vector> transforms; + + void recalculateHeadroom(); + void addTransform(std::shared_ptr transform); + void volumeUpdated(int volume); + std::unique_ptr process(std::unique_ptr data); +}; +}; // namespace bell \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-dsp/include/AudioTransform.h b/components/spotify/cspot/bell/main/audio-dsp/include/AudioTransform.h index 1845aca4..697a0e10 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/include/AudioTransform.h +++ b/components/spotify/cspot/bell/main/audio-dsp/include/AudioTransform.h @@ -1,29 +1,28 @@ #pragma once #include -#include #include +#include #include "StreamInfo.h" #include "TransformConfig.h" -namespace bell -{ - class AudioTransform - { - protected: - std::mutex accessMutex; +namespace bell { +class AudioTransform { + protected: + std::mutex accessMutex; - public: - virtual std::unique_ptr process(std::unique_ptr data) = 0; - virtual void sampleRateChanged(uint32_t sampleRate){}; - virtual float calculateHeadroom() { return 0; }; + public: + virtual std::unique_ptr process( + std::unique_ptr data) = 0; + virtual void sampleRateChanged(uint32_t sampleRate){}; + virtual float calculateHeadroom() { return 0; }; - virtual void reconfigure() {}; + virtual void reconfigure(){}; - std::string filterType; - std::unique_ptr config; + std::string filterType; + std::unique_ptr config; - AudioTransform() = default; - virtual ~AudioTransform() = default; - }; -}; \ No newline at end of file + AudioTransform() = default; + virtual ~AudioTransform() = default; +}; +}; // namespace bell \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-dsp/include/BellDSP.h b/components/spotify/cspot/bell/main/audio-dsp/include/BellDSP.h index 5589731e..8e618710 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/include/BellDSP.h +++ b/components/spotify/cspot/bell/main/audio-dsp/include/BellDSP.h @@ -1,34 +1,43 @@ #pragma once -#include -#include -#include -#include "AudioPipeline.h" -#include "CentralAudioBuffer.h" +#include // for size_t +#include // for uint32_t, uint8_t +#include // for function +#include // for shared_ptr, unique_ptr +#include // for mutex +#include // for vector + +#include "StreamInfo.h" // for BitWidth namespace bell { +class AudioPipeline; +class CentralAudioBuffer; + #define MAX_INT16 32767 class BellDSP { public: BellDSP(std::shared_ptr centralAudioBuffer); - ~BellDSP() {}; + ~BellDSP(){}; class AudioEffect { public: AudioEffect() = default; ~AudioEffect() = default; size_t duration; - virtual void apply(float* sampleData, size_t samples, size_t relativePosition) = 0; + virtual void apply(float* sampleData, size_t samples, + size_t relativePosition) = 0; }; - class FadeEffect: public AudioEffect { - private: + class FadeEffect : public AudioEffect { + private: std::function onFinish; bool isFadeIn; - public: - FadeEffect(size_t duration, bool isFadeIn, std::function onFinish = nullptr); - ~FadeEffect() {}; + + public: + FadeEffect(size_t duration, bool isFadeIn, + std::function onFinish = nullptr); + ~FadeEffect(){}; void apply(float* sampleData, size_t samples, size_t relativePosition); }; @@ -38,8 +47,8 @@ class BellDSP { std::shared_ptr getActivePipeline(); - size_t process(uint8_t* data, size_t bytes, int channels, - uint32_t sampleRate, BitWidth bitWidth); + size_t process(uint8_t* data, size_t bytes, int channels, uint32_t sampleRate, + BitWidth bitWidth); private: std::shared_ptr activePipeline; @@ -48,7 +57,6 @@ class BellDSP { std::vector dataLeft = std::vector(1024); std::vector dataRight = std::vector(1024); - std::unique_ptr underflowEffect = nullptr; std::unique_ptr startEffect = nullptr; std::unique_ptr instantEffect = nullptr; diff --git a/components/spotify/cspot/bell/main/audio-dsp/include/Biquad.h b/components/spotify/cspot/bell/main/audio-dsp/include/Biquad.h index 2c2ce662..01195a3c 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/include/Biquad.h +++ b/components/spotify/cspot/bell/main/audio-dsp/include/Biquad.h @@ -1,158 +1,158 @@ #pragma once -#include -#include -#include -#include +#include // for uint32_t +#include // for map +#include // for unique_ptr, allocator +#include // for scoped_lock +#include // for invalid_argument +#include // for string, operator<, hash, operator== +#include // for operator!=, unordered_map, __hash_map_c... +#include // for pair +#include // for vector -#include "AudioTransform.h" -extern "C" int dsps_biquad_f32_ae32(const float *input, float *output, int len, float *coef, float *w); +#include "AudioTransform.h" // for AudioTransform +#include "StreamInfo.h" // for StreamInfo +#include "TransformConfig.h" // for TransformConfig -namespace bell -{ - class Biquad : public bell::AudioTransform - { - public: - Biquad(); - ~Biquad(){}; +extern "C" int dsps_biquad_f32_ae32(const float* input, float* output, int len, + float* coef, float* w); - enum class Type - { - Free, - Highpass, - Lowpass, - HighpassFO, - LowpassFO, +namespace bell { +class Biquad : public bell::AudioTransform { + public: + Biquad(); + ~Biquad(){}; - Peaking, - Highshelf, - HighshelfFO, - Lowshelf, - LowshelfFO, - Notch, - Bandpass, - Allpass, - AllpassFO - }; + enum class Type { + Free, + Highpass, + Lowpass, + HighpassFO, + LowpassFO, - std::map currentConfig; + Peaking, + Highshelf, + HighshelfFO, + Lowshelf, + LowshelfFO, + Notch, + Bandpass, + Allpass, + AllpassFO + }; - std::unordered_map const strMapType = { - {"free", Type::Free}, - {"highpass", Type::Highpass}, - {"lowpass", Type::Lowpass}, - {"highpass_fo", Type::HighpassFO}, - {"lowpass_fo", Type::LowpassFO}, - {"peaking", Type::Peaking}, - {"highshelf", Type::Highshelf}, - {"highshelf_fo", Type::HighpassFO}, - {"lowshelf", Type::Lowshelf}, - {"lowshelf_fo", Type::LowpassFO}, - {"notch", Type::Notch}, - {"bandpass", Type::Bandpass}, - {"allpass", Type::Allpass}, - {"allpass_fo", Type::AllpassFO}, - }; + std::map currentConfig; - float freq, q, gain; - int channel; - Biquad::Type type; + std::unordered_map const strMapType = { + {"free", Type::Free}, + {"highpass", Type::Highpass}, + {"lowpass", Type::Lowpass}, + {"highpass_fo", Type::HighpassFO}, + {"lowpass_fo", Type::LowpassFO}, + {"peaking", Type::Peaking}, + {"highshelf", Type::Highshelf}, + {"highshelf_fo", Type::HighpassFO}, + {"lowshelf", Type::Lowshelf}, + {"lowshelf_fo", Type::LowpassFO}, + {"notch", Type::Notch}, + {"bandpass", Type::Bandpass}, + {"allpass", Type::Allpass}, + {"allpass_fo", Type::AllpassFO}, + }; - std::unique_ptr process(std::unique_ptr data) override; + float freq, q, gain; + int channel; + Biquad::Type type; - void configure(Type type, std::map &config); + std::unique_ptr process( + std::unique_ptr data) override; - void sampleRateChanged(uint32_t sampleRate) override; + void configure(Type type, std::map& config); - void reconfigure() override - { - std::scoped_lock lock(this->accessMutex); - std::map biquadConfig; - this->channel = config->getChannels()[0]; + void sampleRateChanged(uint32_t sampleRate) override; - float invalid = -0x7C; + void reconfigure() override { + std::scoped_lock lock(this->accessMutex); + std::map biquadConfig; + this->channel = config->getChannels()[0]; - auto type = config->getString("biquad_type"); - float bandwidth = config->getFloat("bandwidth", false, invalid); - float slope = config->getFloat("slope", false, invalid); - float gain = config->getFloat("gain", false, invalid); - float frequency = config->getFloat("frequency", false, invalid); - float q = config->getFloat("q", false, invalid); + float invalid = -0x7C; - if (currentConfig["bandwidth"] == bandwidth && - currentConfig["slope"] == slope && - currentConfig["gain"] == gain && - currentConfig["frequency"] == frequency && - currentConfig["q"] == q) - { - return; - } + auto type = config->getString("biquad_type"); + float bandwidth = config->getFloat("bandwidth", false, invalid); + float slope = config->getFloat("slope", false, invalid); + float gain = config->getFloat("gain", false, invalid); + float frequency = config->getFloat("frequency", false, invalid); + float q = config->getFloat("q", false, invalid); - if (bandwidth != invalid) - biquadConfig["bandwidth"] = bandwidth; - if (slope != invalid) - biquadConfig["slope"] = slope; - if (gain != invalid) - biquadConfig["gain"] = gain; - if (frequency != invalid) - biquadConfig["freq"] = frequency; - if (q != invalid) - biquadConfig["q"] = q; + if (currentConfig["bandwidth"] == bandwidth && + currentConfig["slope"] == slope && currentConfig["gain"] == gain && + currentConfig["frequency"] == frequency && currentConfig["q"] == q) { + return; + } - if (type == "free") - { - biquadConfig["a1"] = config->getFloat("a1"); - biquadConfig["a2"] = config->getFloat("a2"); - biquadConfig["b0"] = config->getFloat("b0"); - biquadConfig["b1"] = config->getFloat("b1"); - biquadConfig["b2"] = config->getFloat("b2"); - } + if (bandwidth != invalid) + biquadConfig["bandwidth"] = bandwidth; + if (slope != invalid) + biquadConfig["slope"] = slope; + if (gain != invalid) + biquadConfig["gain"] = gain; + if (frequency != invalid) + biquadConfig["freq"] = frequency; + if (q != invalid) + biquadConfig["q"] = q; - auto typeElement = strMapType.find(type); - if (typeElement != strMapType.end()) - { - this->configure(typeElement->second, biquadConfig); - } - else - { - throw std::invalid_argument("No biquad of type " + type); - } - } + if (type == "free") { + biquadConfig["a1"] = config->getFloat("a1"); + biquadConfig["a2"] = config->getFloat("a2"); + biquadConfig["b0"] = config->getFloat("b0"); + biquadConfig["b1"] = config->getFloat("b1"); + biquadConfig["b2"] = config->getFloat("b2"); + } - private: - float coeffs[5]; - float w[2] = {1.0, 1.0}; + auto typeElement = strMapType.find(type); + if (typeElement != strMapType.end()) { + this->configure(typeElement->second, biquadConfig); + } else { + throw std::invalid_argument("No biquad of type " + type); + } + } - float sampleRate = 44100; + private: + float coeffs[5]; + float w[2] = {1.0, 1.0}; - // Generator methods for different filter types - void highPassCoEffs(float f, float q); - void highPassFOCoEffs(float f); - void lowPassCoEffs(float f, float q); - void lowPassFOCoEffs(float f); + float sampleRate = 44100; - void peakCoEffs(float f, float gain, float q); - void peakCoEffsBandwidth(float f, float gain, float bandwidth); + // Generator methods for different filter types + void highPassCoEffs(float f, float q); + void highPassFOCoEffs(float f); + void lowPassCoEffs(float f, float q); + void lowPassFOCoEffs(float f); - void highShelfCoEffs(float f, float gain, float q); - void highShelfCoEffsSlope(float f, float gain, float slope); - void highShelfFOCoEffs(float f, float gain); + void peakCoEffs(float f, float gain, float q); + void peakCoEffsBandwidth(float f, float gain, float bandwidth); - void lowShelfCoEffs(float f, float gain, float q); - void lowShelfCoEffsSlope(float f, float gain, float slope); - void lowShelfFOCoEffs(float f, float gain); + void highShelfCoEffs(float f, float gain, float q); + void highShelfCoEffsSlope(float f, float gain, float slope); + void highShelfFOCoEffs(float f, float gain); - void notchCoEffs(float f, float gain, float q); - void notchCoEffsBandwidth(float f, float gain, float bandwidth); + void lowShelfCoEffs(float f, float gain, float q); + void lowShelfCoEffsSlope(float f, float gain, float slope); + void lowShelfFOCoEffs(float f, float gain); - void bandPassCoEffs(float f, float q); - void bandPassCoEffsBandwidth(float f, float bandwidth); + void notchCoEffs(float f, float gain, float q); + void notchCoEffsBandwidth(float f, float gain, float bandwidth); - void allPassCoEffs(float f, float q); - void allPassCoEffsBandwidth(float f, float bandwidth); - void allPassFOCoEffs(float f); + void bandPassCoEffs(float f, float q); + void bandPassCoEffsBandwidth(float f, float bandwidth); - void normalizeCoEffs(float a0, float a1, float a2, float b0, float b1, float b2); - }; + void allPassCoEffs(float f, float q); + void allPassCoEffsBandwidth(float f, float bandwidth); + void allPassFOCoEffs(float f); -} \ No newline at end of file + void normalizeCoEffs(float a0, float a1, float a2, float b0, float b1, + float b2); +}; + +} // namespace bell \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-dsp/include/BiquadCombo.h b/components/spotify/cspot/bell/main/audio-dsp/include/BiquadCombo.h index 66a75f38..bc7234dd 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/include/BiquadCombo.h +++ b/components/spotify/cspot/bell/main/audio-dsp/include/BiquadCombo.h @@ -1,85 +1,71 @@ #pragma once -#include -#include -#include -#include -#include +#include // for uint32_t +#include // for map +#include // for unique_ptr, allocator +#include // for scoped_lock +#include // for invalid_argument +#include // for string, operator==, char_traits, basic_... +#include // for vector -#include "Biquad.h" -#include "AudioTransform.h" +#include "AudioTransform.h" // for AudioTransform +#include "Biquad.h" // for Biquad +#include "StreamInfo.h" // for StreamInfo +#include "TransformConfig.h" // for TransformConfig -namespace bell -{ - class BiquadCombo : public bell::AudioTransform - { - private: - std::vector> biquads; +namespace bell { +class BiquadCombo : public bell::AudioTransform { + private: + std::vector> biquads; - // Calculates Q values for Nth order Butterworth / Linkwitz-Riley filters - std::vector calculateBWQ(int order); - std::vector calculateLRQ(int order); + // Calculates Q values for Nth order Butterworth / Linkwitz-Riley filters + std::vector calculateBWQ(int order); + std::vector calculateLRQ(int order); - public: - BiquadCombo(); - ~BiquadCombo(){}; - int channel; + public: + BiquadCombo(); + ~BiquadCombo(){}; + int channel; - std::map paramCache = { - {"order", 0.0f}, - {"frequency", 0.0f} - }; + std::map paramCache = {{"order", 0.0f}, + {"frequency", 0.0f}}; - enum class FilterType - { - Highpass, - Lowpass - }; + enum class FilterType { Highpass, Lowpass }; - void linkwitzRiley(float freq, int order, FilterType type); - void butterworth(float freq, int order, FilterType type); + void linkwitzRiley(float freq, int order, FilterType type); + void butterworth(float freq, int order, FilterType type); - std::unique_ptr process(std::unique_ptr data) override; - void sampleRateChanged(uint32_t sampleRate) override; + std::unique_ptr process( + std::unique_ptr data) override; + void sampleRateChanged(uint32_t sampleRate) override; - void reconfigure() override - { - std::scoped_lock lock(this->accessMutex); + void reconfigure() override { + std::scoped_lock lock(this->accessMutex); - float freq = config->getFloat("frequency"); - int order = config->getInt("order"); + float freq = config->getFloat("frequency"); + int order = config->getInt("order"); - if (paramCache["frequency"] == freq && paramCache["order"] == order) - { - return; - } else { - paramCache["frequency"] = freq; - paramCache["order"] = order; - } + if (paramCache["frequency"] == freq && paramCache["order"] == order) { + return; + } else { + paramCache["frequency"] = freq; + paramCache["order"] = order; + } - this->channel = config->getChannels()[0]; - this->biquads = std::vector>(); - auto type = config->getString("combo_type"); - if (type == "lr_lowpass") - { - this->linkwitzRiley(freq, order, FilterType::Lowpass); - } - else if (type == "lr_highpass") - { - this->linkwitzRiley(freq, order, FilterType::Highpass); - } - else if (type == "bw_highpass") - { - this->butterworth(freq, order, FilterType::Highpass); - } - else if (type == "bw_lowpass") - { - this->butterworth(freq, order, FilterType::Highpass); - } - else - { - throw std::invalid_argument("Invalid combo filter type"); - } - } - }; -}; \ No newline at end of file + this->channel = config->getChannels()[0]; + this->biquads = std::vector>(); + auto type = config->getString("combo_type"); + if (type == "lr_lowpass") { + this->linkwitzRiley(freq, order, FilterType::Lowpass); + } else if (type == "lr_highpass") { + this->linkwitzRiley(freq, order, FilterType::Highpass); + } else if (type == "bw_highpass") { + this->butterworth(freq, order, FilterType::Highpass); + } else if (type == "bw_lowpass") { + this->butterworth(freq, order, FilterType::Highpass); + } else { + throw std::invalid_argument("Invalid combo filter type"); + } + } +}; +}; // namespace bell \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-dsp/include/CentralAudioBuffer.h b/components/spotify/cspot/bell/main/audio-dsp/include/CentralAudioBuffer.h index c2723da5..d55f3ed7 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/include/CentralAudioBuffer.h +++ b/components/spotify/cspot/bell/main/audio-dsp/include/CentralAudioBuffer.h @@ -2,10 +2,10 @@ #include #include +#include #include #include #include -#include #include "BellUtils.h" #include "CircularBuffer.h" @@ -70,9 +70,9 @@ class CentralAudioBuffer { */ void clearBuffer() { std::scoped_lock lock(this->dataAccessMutex); - //size_t exceptSize = currentSampleRate + (sizeof(AudioChunk) - (currentSampleRate % sizeof(AudioChunk))); - hasChunk = false; + audioBuffer->emptyBuffer(); + hasChunk = false; } void emptyCompletely() { @@ -106,10 +106,10 @@ class CentralAudioBuffer { } } - AudioChunk currentChunk = { }; + AudioChunk currentChunk = {}; bool hasChunk = false; - AudioChunk lastReadChunk = { }; + AudioChunk lastReadChunk = {}; AudioChunk* readChunk() { std::scoped_lock lock(this->dataAccessMutex); diff --git a/components/spotify/cspot/bell/main/audio-dsp/include/Compressor.h b/components/spotify/cspot/bell/main/audio-dsp/include/Compressor.h index 4308160d..9d1995c3 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/include/Compressor.h +++ b/components/spotify/cspot/bell/main/audio-dsp/include/Compressor.h @@ -1,103 +1,102 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include +#include // for expf +#include // for uint32_t +#include // for map +#include // for unique_ptr +#include // for scoped_lock +#include // for string, operator< +#include // for vector -#include "Biquad.h" -#include "AudioTransform.h" +#include "AudioTransform.h" // for AudioTransform +#include "StreamInfo.h" // for StreamInfo +#include "TransformConfig.h" // for TransformConfig -#define pow10f(x) expf(2.302585092994046f*x) +#define pow10f(x) expf(2.302585092994046f * x) // This is a fast approximation to log2() // Y = C[0]*F*F*F + C[1]*F*F + C[2]*F + C[3] + E; -float log2f_approx(float X); +float log2f_approx(float X); -#define log10f_fast(x) (log2f_approx(x)*0.3010299956639812f) +#define log10f_fast(x) (log2f_approx(x) * 0.3010299956639812f) -namespace bell -{ - class Compressor : public bell::AudioTransform - { - private: - std::vector channels; - std::vector tmp; +namespace bell { +class Compressor : public bell::AudioTransform { + private: + std::vector channels; + std::vector tmp; - std::map paramCache; + std::map paramCache; - float attack; - float release; - float threshold; - float factor; - float clipLimit; - float makeupGain; + float attack; + float release; + float threshold; + float factor; + float clipLimit; + float makeupGain; - float lastLoudness = -100.0f; + float lastLoudness = -100.0f; - float sampleRate = 44100; + float sampleRate = 44100; - public: - Compressor(); - ~Compressor(){}; + public: + Compressor(); + ~Compressor(){}; - void configure(std::vector channels, float attack, float release, float threshold, float factor, float makeupGain); + void configure(std::vector channels, float attack, float release, + float threshold, float factor, float makeupGain); - void sumChannels(std::unique_ptr &data); - void calLoudness(); - void calGain(); + void sumChannels(std::unique_ptr& data); + void calLoudness(); + void calGain(); - void applyGain(std::unique_ptr &data); + void applyGain(std::unique_ptr& data); - void reconfigure() override - { - std::scoped_lock lock(this->accessMutex); - auto newChannels = config->getChannels(); - - float newAttack = config->getFloat("attack"); - float newRelease = config->getFloat("release"); - float newThreshold = config->getFloat("threshold"); - float newFactor = config->getFloat("factor"); - float newMakeupGain = config->getFloat("makeup_gain"); + void reconfigure() override { + std::scoped_lock lock(this->accessMutex); + auto newChannels = config->getChannels(); - if (paramCache["attack"] == newAttack && - paramCache["release"] == newRelease && - paramCache["threshold"] == newThreshold && - paramCache["factor"] == newFactor && - paramCache["makeup_gain"] == newMakeupGain) - { - return; - } - else - { + float newAttack = config->getFloat("attack"); + float newRelease = config->getFloat("release"); + float newThreshold = config->getFloat("threshold"); + float newFactor = config->getFloat("factor"); + float newMakeupGain = config->getFloat("makeup_gain"); - paramCache["attack"] = newAttack; - paramCache["release"] = newRelease; - paramCache["threshold"] = newThreshold; - paramCache["factor"] = newFactor; - paramCache["makeup_gain"] = newMakeupGain; - } + if (paramCache["attack"] == newAttack && + paramCache["release"] == newRelease && + paramCache["threshold"] == newThreshold && + paramCache["factor"] == newFactor && + paramCache["makeup_gain"] == newMakeupGain) { + return; + } else { - this->configure(newChannels, newAttack, newRelease, newThreshold, newFactor, newMakeupGain); - } + paramCache["attack"] = newAttack; + paramCache["release"] = newRelease; + paramCache["threshold"] = newThreshold; + paramCache["factor"] = newFactor; + paramCache["makeup_gain"] = newMakeupGain; + } - // void fromJSON(cJSON* json) override { - // // get field channels - // channels = jsonGetChannels(json); - // float attack = jsonGetNumber(json, "attack", false, 0); - // float release = jsonGetNumber(json, "release", false, 0); - // float factor = jsonGetNumber(json, "factor", false, 4); - // float makeupGain = jsonGetNumber(json, "makeup_gain", false, 0); - // float threshold = jsonGetNumber(json, "threshold", false, 0); + this->configure(newChannels, newAttack, newRelease, newThreshold, newFactor, + newMakeupGain); + } - // this->configure(attack, release, clipLimit, threshold, factor, makeupGain); - // } + // void fromJSON(cJSON* json) override { + // // get field channels + // channels = jsonGetChannels(json); + // float attack = jsonGetNumber(json, "attack", false, 0); + // float release = jsonGetNumber(json, "release", false, 0); + // float factor = jsonGetNumber(json, "factor", false, 4); + // float makeupGain = jsonGetNumber(json, "makeup_gain", false, 0); + // float threshold = jsonGetNumber(json, "threshold", false, 0); - std::unique_ptr process(std::unique_ptr data) override; - void sampleRateChanged(uint32_t sampleRate) override { this->sampleRate = sampleRate; }; - }; + // this->configure(attack, release, clipLimit, threshold, factor, makeupGain); + // } + + std::unique_ptr process( + std::unique_ptr data) override; + void sampleRateChanged(uint32_t sampleRate) override { + this->sampleRate = sampleRate; + }; }; +}; // namespace bell diff --git a/components/spotify/cspot/bell/main/audio-dsp/include/Gain.h b/components/spotify/cspot/bell/main/audio-dsp/include/Gain.h index 8948b223..f8151a9e 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/include/Gain.h +++ b/components/spotify/cspot/bell/main/audio-dsp/include/Gain.h @@ -1,40 +1,41 @@ #pragma once -#include -#include -#include +#include // for unique_ptr +#include // for scoped_lock +#include // for vector -#include "AudioTransform.h" +#include "AudioTransform.h" // for AudioTransform +#include "StreamInfo.h" // for StreamInfo +#include "TransformConfig.h" // for TransformConfig -namespace bell -{ - class Gain : public bell::AudioTransform - { - private: - float gainFactor = 1.0f; +namespace bell { +class Gain : public bell::AudioTransform { + private: + float gainFactor = 1.0f; - std::vector channels; + std::vector channels; - public: - Gain(); - ~Gain() {}; - - float gainDb = 0.0; - - void configure(std::vector channels, float gainDB); + public: + Gain(); + ~Gain(){}; - std::unique_ptr process(std::unique_ptr data) override; + float gainDb = 0.0; - void reconfigure() override { - std::scoped_lock lock(this->accessMutex); - float gain = config->getFloat("gain"); - this->channels = config->getChannels(); + void configure(std::vector channels, float gainDB); - if (gainDb == gain) { - return; - } + std::unique_ptr process( + std::unique_ptr data) override; - this->configure(channels, gain); - } - }; -} \ No newline at end of file + void reconfigure() override { + std::scoped_lock lock(this->accessMutex); + float gain = config->getFloat("gain"); + this->channels = config->getChannels(); + + if (gainDb == gain) { + return; + } + + this->configure(channels, gain); + } +}; +} // namespace bell \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-dsp/include/JSONTransformConfig.h b/components/spotify/cspot/bell/main/audio-dsp/include/JSONTransformConfig.h index 9ee15229..3b63680e 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/include/JSONTransformConfig.h +++ b/components/spotify/cspot/bell/main/audio-dsp/include/JSONTransformConfig.h @@ -3,108 +3,87 @@ #include "TransformConfig.h" #include "cJSON.h" -namespace bell -{ - class JSONTransformConfig : public bell::TransformConfig - { - private: - cJSON *json; +namespace bell { +class JSONTransformConfig : public bell::TransformConfig { + private: + cJSON* json; - public: - JSONTransformConfig(cJSON *body) - { - this->json = body; - }; - ~JSONTransformConfig(){}; + public: + JSONTransformConfig(cJSON* body) { this->json = body; }; + ~JSONTransformConfig(){}; - std::string rawGetString(const std::string &field) override - { - cJSON *value = cJSON_GetObjectItem(json, field.c_str()); + std::string rawGetString(const std::string& field) override { + cJSON* value = cJSON_GetObjectItem(json, field.c_str()); - if (value != NULL && cJSON_IsString(value)) - { - return std::string(value->valuestring); - } + if (value != NULL && cJSON_IsString(value)) { + return std::string(value->valuestring); + } - return "invalid"; + return "invalid"; + } + + std::vector rawGetIntArray(const std::string& field) override { + std::vector result; + + cJSON* value = cJSON_GetObjectItem(json, field.c_str()); + + if (value != NULL && cJSON_IsArray(value)) { + for (int i = 0; i < cJSON_GetArraySize(value); i++) { + cJSON* item = cJSON_GetArrayItem(value, i); + if (item != NULL && cJSON_IsNumber(item)) { + result.push_back(item->valueint); } + } + } - std::vector rawGetIntArray(const std::string &field) override - { - std::vector result; + return result; + } - cJSON *value = cJSON_GetObjectItem(json, field.c_str()); + std::vector rawGetFloatArray(const std::string& field) override { + std::vector result; - if (value != NULL && cJSON_IsArray(value)) - { - for (int i = 0; i < cJSON_GetArraySize(value); i++) - { - cJSON *item = cJSON_GetArrayItem(value, i); - if (item != NULL && cJSON_IsNumber(item)) - { - result.push_back(item->valueint); - } - } - } + cJSON* value = cJSON_GetObjectItem(json, field.c_str()); - return result; + if (value != NULL && cJSON_IsArray(value)) { + for (int i = 0; i < cJSON_GetArraySize(value); i++) { + cJSON* item = cJSON_GetArrayItem(value, i); + if (item != NULL && cJSON_IsNumber(item)) { + result.push_back(item->valuedouble); } + } + } - std::vector rawGetFloatArray(const std::string &field) override - { - std::vector result; + return result; + } - cJSON *value = cJSON_GetObjectItem(json, field.c_str()); + int rawGetInt(const std::string& field) override { + cJSON* value = cJSON_GetObjectItem(json, field.c_str()); - if (value != NULL && cJSON_IsArray(value)) - { - for (int i = 0; i < cJSON_GetArraySize(value); i++) - { - cJSON *item = cJSON_GetArrayItem(value, i); - if (item != NULL && cJSON_IsNumber(item)) - { - result.push_back(item->valuedouble); - } - } - } + if (value != NULL && cJSON_IsNumber(value)) { + return (int)value->valueint; + } - return result; - } + return invalidInt; + } - int rawGetInt(const std::string &field) override - { - cJSON *value = cJSON_GetObjectItem(json, field.c_str()); + bool isArray(const std::string& field) override { + cJSON* value = cJSON_GetObjectItem(json, field.c_str()); - if (value != NULL && cJSON_IsNumber(value)) - { - return (int)value->valueint; - } + if (value != NULL && cJSON_IsArray(value)) { + return true; + } - return invalidInt; - } + return false; + } - bool isArray(const std::string &field) override - { - cJSON *value = cJSON_GetObjectItem(json, field.c_str()); + float rawGetFloat(const std::string& field) override { + cJSON* value = cJSON_GetObjectItem(json, field.c_str()); - if (value != NULL && cJSON_IsArray(value)) - { - return true; - } + if (value != NULL && cJSON_IsNumber(value)) { + return (float)value->valuedouble; + } - return false; - } - - float rawGetFloat(const std::string &field) override - { - cJSON *value = cJSON_GetObjectItem(json, field.c_str()); - - if (value != NULL && cJSON_IsNumber(value)) - { - return (float)value->valuedouble; - } - - return invalidInt; - } - }; -} \ No newline at end of file + return invalidInt; + } +}; +} // namespace bell \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-dsp/include/StreamInfo.h b/components/spotify/cspot/bell/main/audio-dsp/include/StreamInfo.h index 313a3824..bac4661b 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/include/StreamInfo.h +++ b/components/spotify/cspot/bell/main/audio-dsp/include/StreamInfo.h @@ -1,36 +1,28 @@ #pragma once #include -#include #include +#include -namespace bell -{ - enum class Channels { - LEFT, - RIGHT, - LEFT_RIGHT - }; - - enum class SampleRate : uint32_t - { - SR_44100 = 44100, - SR_48000 = 48000, - }; +namespace bell { +enum class Channels { LEFT, RIGHT, LEFT_RIGHT }; - enum class BitWidth : uint32_t - { - BW_16 = 16, - BW_24 = 24, - BW_32 = 32, - }; +enum class SampleRate : uint32_t { + SR_44100 = 44100, + SR_48000 = 48000, +}; - typedef struct - { - float** data; - BitWidth bitwidth; - int numChannels; - SampleRate sampleRate; - size_t numSamples; - } StreamInfo; -}; \ No newline at end of file +enum class BitWidth : uint32_t { + BW_16 = 16, + BW_24 = 24, + BW_32 = 32, +}; + +typedef struct { + float** data; + BitWidth bitwidth; + int numChannels; + SampleRate sampleRate; + size_t numSamples; +} StreamInfo; +}; // namespace bell \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-dsp/include/TransformConfig.h b/components/spotify/cspot/bell/main/audio-dsp/include/TransformConfig.h index c0809fd3..942db428 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/include/TransformConfig.h +++ b/components/spotify/cspot/bell/main/audio-dsp/include/TransformConfig.h @@ -1,134 +1,112 @@ #pragma once -#include -#include -#include -#include #include #include +#include +#include +#include +#include -namespace bell -{ - class TransformConfig - { - protected: - int invalidInt = -0x7C; - std::string invalidString = "_invalid"; +namespace bell { +class TransformConfig { + protected: + int invalidInt = -0x7C; + std::string invalidString = "_invalid"; - public: - TransformConfig() = default; - virtual ~TransformConfig() = default; + public: + TransformConfig() = default; + virtual ~TransformConfig() = default; - int currentVolume = 60; + int currentVolume = 60; - virtual std::string rawGetString(const std::string &field) = 0; + virtual std::string rawGetString(const std::string& field) = 0; - virtual int rawGetInt(const std::string &field) = 0; - virtual bool isArray(const std::string &field) = 0; + virtual int rawGetInt(const std::string& field) = 0; + virtual bool isArray(const std::string& field) = 0; - virtual float rawGetFloat(const std::string &field) = 0; - virtual std::vector rawGetFloatArray(const std::string &field) = 0; - virtual std::vector rawGetIntArray(const std::string &field) = 0; + virtual float rawGetFloat(const std::string& field) = 0; + virtual std::vector rawGetFloatArray(const std::string& field) = 0; + virtual std::vector rawGetIntArray(const std::string& field) = 0; - typedef std::variant Value; - std::map> rawValues; + typedef std::variant Value; + std::map> rawValues; - Value getRawValue(const std::string &field) - { - int index = this->currentVolume * (rawValues[field].size()) / 100; - if (index >= rawValues[field].size()) - index = rawValues[field].size() - 1; - return rawValues[field][index]; + Value getRawValue(const std::string& field) { + int index = this->currentVolume * (rawValues[field].size()) / 100; + if (index >= rawValues[field].size()) + index = rawValues[field].size() - 1; + return rawValues[field][index]; + } + + std::string getString(const std::string& field, bool isRequired = false, + std::string defaultValue = "") { + if (rawValues.count(field) == 0) { + rawValues[field] = std::vector({Value(rawGetString(field))}); + } + auto val = std::get(getRawValue(field)); + if (val == invalidString) { + if (isRequired) + throw std::invalid_argument("Field " + field + " is required"); + else + return defaultValue; + } else + return val; + } + + int getInt(const std::string& field, bool isRequired = false, + int defaultValue = 0) { + if (rawValues.count(field) == 0) { + if (isArray(field)) { + rawValues[field] = std::vector(); + for (auto f : rawGetIntArray(field)) { + rawValues[field].push_back(f); } + } else { + rawValues[field] = std::vector({Value(rawGetInt(field))}); + } + } - std::string getString(const std::string &field, bool isRequired = false, std::string defaultValue = "") - { - if (rawValues.count(field) == 0) - { - rawValues[field] = std::vector({Value(rawGetString(field))}); - } - auto val = std::get(getRawValue(field)); - if (val == invalidString) - { - if (isRequired) - throw std::invalid_argument("Field " + field + " is required"); - else - return defaultValue; - } - else - return val; + auto val = std::get(getRawValue(field)); + if (val == invalidInt) { + if (isRequired) + throw std::invalid_argument("Field " + field + " is required"); + else + return defaultValue; + } else + return val; + } + + float getFloat(const std::string& field, bool isRequired = false, + float defaultValue = 0) { + if (rawValues.count(field) == 0) { + if (isArray(field)) { + + rawValues[field] = std::vector(); + for (auto f : rawGetFloatArray(field)) { + rawValues[field].push_back(f); } + } else { + rawValues[field] = std::vector({Value(rawGetFloat(field))}); + } + } + auto val = std::get(getRawValue(field)); + if (val == invalidInt) { + if (isRequired) + throw std::invalid_argument("Field " + field + " is required"); + else + return defaultValue; + } else + return val; + } - int getInt(const std::string &field, bool isRequired = false, int defaultValue = 0) - { - if (rawValues.count(field) == 0) - { - if (isArray(field)) - { - rawValues[field] = std::vector(); - for (auto f : rawGetIntArray(field)) - { - rawValues[field].push_back(f); - } - } - else - { - rawValues[field] = std::vector({Value(rawGetInt(field))}); - } - } + std::vector getChannels() { + auto channel = getInt("channel", false, invalidInt); - auto val = std::get(getRawValue(field)); - if (val == invalidInt) - { - if (isRequired) - throw std::invalid_argument("Field " + field + " is required"); - else - return defaultValue; - } - else - return val; - } + if (channel != invalidInt) { + return std::vector({channel}); + } - float getFloat(const std::string &field, bool isRequired = false, float defaultValue = 0) - { - if (rawValues.count(field) == 0) - { - if (isArray(field)) - { - - rawValues[field] = std::vector(); - for (auto f : rawGetFloatArray(field)) - { - rawValues[field].push_back(f); - } - } - else - { - rawValues[field] = std::vector({ Value(rawGetFloat(field)) }); - } - } - auto val = std::get(getRawValue(field)); - if (val == invalidInt) - { - if (isRequired) - throw std::invalid_argument("Field " + field + " is required"); - else - return defaultValue; - } - else - return val; - } - - std::vector getChannels() - { - auto channel = getInt("channel", false, invalidInt); - - if (channel != invalidInt) - { - return std::vector({channel}); - } - - return rawGetIntArray("channels"); - } - }; -} \ No newline at end of file + return rawGetIntArray("channels"); + } +}; +} // namespace bell \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/esp/AC101AudioSink.cpp b/components/spotify/cspot/bell/main/audio-sinks/esp/AC101AudioSink.cpp index 5e83f10d..e07f65a2 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/esp/AC101AudioSink.cpp +++ b/components/spotify/cspot/bell/main/audio-sinks/esp/AC101AudioSink.cpp @@ -2,45 +2,42 @@ #include "driver/i2s.h" -AC101AudioSink::AC101AudioSink() -{ - // Disable software volume control, all handled by ::volumeChanged - softwareVolumeControl = false; +AC101AudioSink::AC101AudioSink() { + // Disable software volume control, all handled by ::volumeChanged + softwareVolumeControl = false; - i2s_config_t i2s_config = { + i2s_config_t i2s_config = { - .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX - .sample_rate = 44100, - .bits_per_sample = (i2s_bits_per_sample_t)16, - .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels - .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S, - .intr_alloc_flags = 0, //Default interrupt priority - .dma_buf_count = 8, - .dma_buf_len = 512, - .use_apll = true, - .tx_desc_auto_clear = true //Auto clear tx descriptor on underflow - }; + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX + .sample_rate = 44100, + .bits_per_sample = (i2s_bits_per_sample_t)16, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels + .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S, + .intr_alloc_flags = 0, //Default interrupt priority + .dma_buf_count = 8, + .dma_buf_len = 512, + .use_apll = true, + .tx_desc_auto_clear = true //Auto clear tx descriptor on underflow + }; - i2s_pin_config_t pin_config = { - .bck_io_num = 27, - .ws_io_num = 26, - .data_out_num = 25, - .data_in_num = -1 //Not used - }; + i2s_pin_config_t pin_config = { + .bck_io_num = 27, + .ws_io_num = 26, + .data_out_num = 25, + .data_in_num = -1 //Not used + }; - dac = &dac_a1s; + dac = &dac_a1s; - dac->init(0, 0, &i2s_config); - dac->speaker(false); - dac->power(ADAC_ON); + dac->init(0, 0, &i2s_config); + dac->speaker(false); + dac->power(ADAC_ON); - startI2sFeed(); + startI2sFeed(); } -AC101AudioSink::~AC101AudioSink() -{ -} +AC101AudioSink::~AC101AudioSink() {} void AC101AudioSink::volumeChanged(uint16_t volume) { - dac->volume(volume, volume); + dac->volume(volume, volume); } diff --git a/components/spotify/cspot/bell/main/audio-sinks/esp/BufferedAudioSink.cpp b/components/spotify/cspot/bell/main/audio-sinks/esp/BufferedAudioSink.cpp index 4d849fc3..e32a9c7a 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/esp/BufferedAudioSink.cpp +++ b/components/spotify/cspot/bell/main/audio-sinks/esp/BufferedAudioSink.cpp @@ -1,47 +1,45 @@ #include "BufferedAudioSink.h" #include "driver/i2s.h" -#include "freertos/task.h" #include "freertos/ringbuf.h" +#include "freertos/task.h" RingbufHandle_t dataBuffer; -static void i2sFeed(void *pvParameters) -{ - while (true) - { - size_t itemSize; - char *item = (char *)xRingbufferReceiveUpTo(dataBuffer, &itemSize, portMAX_DELAY, 512); - if (item != NULL) - { - size_t written = 0; - while (written < itemSize) - { - i2s_write((i2s_port_t)0, item, itemSize, &written, portMAX_DELAY); - } - vRingbufferReturnItem(dataBuffer, (void *)item); - } +static void i2sFeed(void* pvParameters) { + while (true) { + size_t itemSize; + char* item = (char*)xRingbufferReceiveUpTo(dataBuffer, &itemSize, + portMAX_DELAY, 512); + if (item != NULL) { + size_t written = 0; + while (written < itemSize) { + i2s_write((i2s_port_t)0, item, itemSize, &written, portMAX_DELAY); + } + vRingbufferReturnItem(dataBuffer, (void*)item); } + } } -void BufferedAudioSink::startI2sFeed(size_t buf_size) -{ - dataBuffer = xRingbufferCreate(buf_size, RINGBUF_TYPE_BYTEBUF); - xTaskCreatePinnedToCore(&i2sFeed, "i2sFeed", 4096, NULL, 10, NULL, tskNO_AFFINITY); +void BufferedAudioSink::startI2sFeed(size_t buf_size) { + dataBuffer = xRingbufferCreate(buf_size, RINGBUF_TYPE_BYTEBUF); + xTaskCreatePinnedToCore(&i2sFeed, "i2sFeed", 4096, NULL, 10, NULL, + tskNO_AFFINITY); } -void BufferedAudioSink::feedPCMFrames(const uint8_t *buffer, size_t bytes) -{ - feedPCMFramesInternal(buffer, bytes); +void BufferedAudioSink::feedPCMFrames(const uint8_t* buffer, size_t bytes) { + feedPCMFramesInternal(buffer, bytes); } -void BufferedAudioSink::feedPCMFramesInternal(const void *pvItem, size_t xItemSize) -{ - xRingbufferSend(dataBuffer, pvItem, xItemSize, portMAX_DELAY); +void BufferedAudioSink::feedPCMFramesInternal(const void* pvItem, + size_t xItemSize) { + xRingbufferSend(dataBuffer, pvItem, xItemSize, portMAX_DELAY); } -bool BufferedAudioSink::setParams(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) { - // TODO override this for sinks with custom mclk - i2s_set_clk((i2s_port_t)0, sampleRate, (i2s_bits_per_sample_t)bitDepth, (i2s_channel_t)channelCount); - return true; +bool BufferedAudioSink::setParams(uint32_t sampleRate, uint8_t channelCount, + uint8_t bitDepth) { + // TODO override this for sinks with custom mclk + i2s_set_clk((i2s_port_t)0, sampleRate, (i2s_bits_per_sample_t)bitDepth, + (i2s_channel_t)channelCount); + return true; } \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/esp/ES8311AudioSink.cpp b/components/spotify/cspot/bell/main/audio-sinks/esp/ES8311AudioSink.cpp index 62041593..aa9ff9fd 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/esp/ES8311AudioSink.cpp +++ b/components/spotify/cspot/bell/main/audio-sinks/esp/ES8311AudioSink.cpp @@ -1,106 +1,101 @@ #include "ES8311AudioSink.h" extern "C" { - #include "es8311.h" +#include "es8311.h" } -ES8311AudioSink::ES8311AudioSink() -{ - this->softwareVolumeControl = false; - esp_err_t ret_val = ESP_OK; - Es8311Config cfg = { - .esMode = ES_MODE_SLAVE, - .i2c_port_num = I2C_NUM_0, - .i2c_cfg = { - .mode = I2C_MODE_MASTER, - .sda_io_num = 1, - .scl_io_num = 2, - .sda_pullup_en = GPIO_PULLUP_ENABLE, - .scl_pullup_en = GPIO_PULLUP_ENABLE, - }, - .dacOutput = (DacOutput) (DAC_OUTPUT_LOUT1 | DAC_OUTPUT_LOUT2 | DAC_OUTPUT_ROUT1 | DAC_OUTPUT_ROUT2), - .adcInput = ADC_INPUT_LINPUT1_RINPUT1, - }; - cfg.i2c_cfg.master.clk_speed = 100000; - Es8311Init(&cfg); - Es8311SetBitsPerSample(ES_MODULE_DAC, BIT_LENGTH_16BITS); - Es8311ConfigFmt(ES_MODULE_DAC, ES_I2S_NORMAL); - Es8311SetVoiceVolume(60); - Es8311Start(ES_MODULE_DAC); - ES8311WriteReg(ES8311_CLK_MANAGER_REG01, 0xbf); - ES8311WriteReg(ES8311_CLK_MANAGER_REG02, 0x18); +ES8311AudioSink::ES8311AudioSink() { + this->softwareVolumeControl = false; + esp_err_t ret_val = ESP_OK; + Es8311Config cfg = { + .esMode = ES_MODE_SLAVE, + .i2c_port_num = I2C_NUM_0, + .i2c_cfg = + { + .mode = I2C_MODE_MASTER, + .sda_io_num = 1, + .scl_io_num = 2, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + }, + .dacOutput = (DacOutput)(DAC_OUTPUT_LOUT1 | DAC_OUTPUT_LOUT2 | + DAC_OUTPUT_ROUT1 | DAC_OUTPUT_ROUT2), + .adcInput = ADC_INPUT_LINPUT1_RINPUT1, + }; + cfg.i2c_cfg.master.clk_speed = 100000; + Es8311Init(&cfg); + Es8311SetBitsPerSample(ES_MODULE_DAC, BIT_LENGTH_16BITS); + Es8311ConfigFmt(ES_MODULE_DAC, ES_I2S_NORMAL); + Es8311SetVoiceVolume(60); + Es8311Start(ES_MODULE_DAC); + ES8311WriteReg(ES8311_CLK_MANAGER_REG01, 0xbf); + ES8311WriteReg(ES8311_CLK_MANAGER_REG02, 0x18); - // .codec_mode = AUDIO_HAL_CODEC_MODE_DECODE, - // .i2s_iface = { - // .mode = AUDIO_HAL_MODE_SLAVE, - // .fmt = AUDIO_HAL_I2S_NORMAL, - // .samples = AUDIO_HAL_44K_SAMPLES, - // .bits = AUDIO_HAL_BIT_LENGTH_16BITS, - // }, - // }; + // .codec_mode = AUDIO_HAL_CODEC_MODE_DECODE, + // .i2s_iface = { + // .mode = AUDIO_HAL_MODE_SLAVE, + // .fmt = AUDIO_HAL_I2S_NORMAL, + // .samples = AUDIO_HAL_44K_SAMPLES, + // .bits = AUDIO_HAL_BIT_LENGTH_16BITS, + // }, + // }; - // ret_val |= es8311_codec_init(&cfg); - // ret_val |= es8311_set_bits_per_sample(cfg.i2s_iface.bits); - // ret_val |= es8311_config_fmt((es_i2s_fmt_t) cfg.i2s_iface.fmt); - // ret_val |= es8311_codec_set_voice_volume(60); - // ret_val |= es8311_codec_ctrl_state(cfg.codec_mode, AUDIO_HAL_CTRL_START); - // ret_val |= es8311_codec_set_clk(); + // ret_val |= es8311_codec_init(&cfg); + // ret_val |= es8311_set_bits_per_sample(cfg.i2s_iface.bits); + // ret_val |= es8311_config_fmt((es_i2s_fmt_t) cfg.i2s_iface.fmt); + // ret_val |= es8311_codec_set_voice_volume(60); + // ret_val |= es8311_codec_ctrl_state(cfg.codec_mode, AUDIO_HAL_CTRL_START); + // ret_val |= es8311_codec_set_clk(); - i2s_config_t i2s_config = { - .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX - .sample_rate = 44100, - .bits_per_sample = (i2s_bits_per_sample_t)16, - .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // 2-channels - .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_I2S, - .intr_alloc_flags = 0, // Default interrupt priority - .dma_buf_count = 8, - .dma_buf_len = 512, - .use_apll = false, - .tx_desc_auto_clear = true, // Auto clear tx descriptor on underflow - }; + i2s_config_t i2s_config = { + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX + .sample_rate = 44100, + .bits_per_sample = (i2s_bits_per_sample_t)16, + .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // 2-channels + .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_I2S, + .intr_alloc_flags = 0, // Default interrupt priority + .dma_buf_count = 8, + .dma_buf_len = 512, + .use_apll = false, + .tx_desc_auto_clear = true, // Auto clear tx descriptor on underflow + }; - i2s_pin_config_t pin_config = { - .mck_io_num = 42, - .bck_io_num = 40, - .ws_io_num = 41, - .data_out_num = 39, - .data_in_num = -1, - }; + i2s_pin_config_t pin_config = { + .mck_io_num = 42, + .bck_io_num = 40, + .ws_io_num = 41, + .data_out_num = 39, + .data_in_num = -1, + }; - int err; + int err; - err = i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); - if (err != ESP_OK) - { - ESP_LOGE("OI", "i2s driver installation error: %d", err); - } + err = i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); + if (err != ESP_OK) { + ESP_LOGE("OI", "i2s driver installation error: %d", err); + } - err = i2s_set_pin((i2s_port_t)0, &pin_config); - if (err != ESP_OK) - { - ESP_LOGE("OI", "i2s set pin error: %d", err); - } + err = i2s_set_pin((i2s_port_t)0, &pin_config); + if (err != ESP_OK) { + ESP_LOGE("OI", "i2s set pin error: %d", err); + } - // PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1); - // REG_SET_FIELD(PIN_CTRL, CLK_OUT1, 0); - // ESP_LOGI("OI", "MCLK output on CLK_OUT1"); + // PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1); + // REG_SET_FIELD(PIN_CTRL, CLK_OUT1, 0); + // ESP_LOGI("OI", "MCLK output on CLK_OUT1"); - startI2sFeed(); + startI2sFeed(); } -void ES8311AudioSink::volumeChanged(uint16_t volume) -{ - Es8311SetVoiceVolume(volume); +void ES8311AudioSink::volumeChanged(uint16_t volume) { + Es8311SetVoiceVolume(volume); } -void ES8311AudioSink::writeReg(uint8_t reg_add, uint8_t data) -{ -} +void ES8311AudioSink::writeReg(uint8_t reg_add, uint8_t data) {} void ES8311AudioSink::setSampleRate(uint32_t sampleRate) { - std::cout << "ES8311AudioSink::setSampleRate(" << sampleRate << ")" << std::endl; - // i2s set sample rate - es8311_Codec_Startup(0, sampleRate); + std::cout << "ES8311AudioSink::setSampleRate(" << sampleRate << ")" + << std::endl; + // i2s set sample rate + es8311_Codec_Startup(0, sampleRate); } -ES8311AudioSink::~ES8311AudioSink() -{ -} +ES8311AudioSink::~ES8311AudioSink() {} diff --git a/components/spotify/cspot/bell/main/audio-sinks/esp/ES8388AudioSink.cpp b/components/spotify/cspot/bell/main/audio-sinks/esp/ES8388AudioSink.cpp index b5b5f312..bd450e26 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/esp/ES8388AudioSink.cpp +++ b/components/spotify/cspot/bell/main/audio-sinks/esp/ES8388AudioSink.cpp @@ -1,150 +1,145 @@ #include "ES8388AudioSink.h" struct es8388_cmd_s { - uint8_t reg; - uint8_t value; + uint8_t reg; + uint8_t value; }; -ES8388AudioSink::ES8388AudioSink() -{ - // configure i2c - i2c_config = { - .mode = I2C_MODE_MASTER, - .sda_io_num = 33, - .scl_io_num = 32, - .sda_pullup_en = GPIO_PULLUP_ENABLE, - .scl_pullup_en = GPIO_PULLUP_ENABLE, - }; +ES8388AudioSink::ES8388AudioSink() { + // configure i2c + i2c_config = { + .mode = I2C_MODE_MASTER, + .sda_io_num = 33, + .scl_io_num = 32, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + }; - i2c_config.master.clk_speed = 100000; + i2c_config.master.clk_speed = 100000; - i2s_config_t i2s_config = { - .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX - .sample_rate = 44100, - .bits_per_sample = (i2s_bits_per_sample_t)16, - .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels - .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_MSB, - .intr_alloc_flags = 0, //Default interrupt priority - .dma_buf_count = 8, - .dma_buf_len = 512, - .use_apll = true, - .tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow - .fixed_mclk = 256 * 44100 - }; + i2s_config_t i2s_config = { + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX + .sample_rate = 44100, + .bits_per_sample = (i2s_bits_per_sample_t)16, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels + .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_MSB, + .intr_alloc_flags = 0, //Default interrupt priority + .dma_buf_count = 8, + .dma_buf_len = 512, + .use_apll = true, + .tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow + .fixed_mclk = 256 * 44100}; - i2s_pin_config_t pin_config = { - .bck_io_num = 27, - .ws_io_num = 25, - .data_out_num = 26, - .data_in_num = -1 //Not used - }; + i2s_pin_config_t pin_config = { + .bck_io_num = 27, + .ws_io_num = 25, + .data_out_num = 26, + .data_in_num = -1 //Not used + }; - int err; + int err; - err = i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); - if (err != ESP_OK) { - ESP_LOGE("OI", "i2s driver installation error: %d", err); - } + err = i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); + if (err != ESP_OK) { + ESP_LOGE("OI", "i2s driver installation error: %d", err); + } - err = i2s_set_pin((i2s_port_t)0, &pin_config); - if (err != ESP_OK) { - ESP_LOGE("OI", "i2s set pin error: %d", err); - } + err = i2s_set_pin((i2s_port_t)0, &pin_config); + if (err != ESP_OK) { + ESP_LOGE("OI", "i2s set pin error: %d", err); + } - err = i2c_param_config(0, &i2c_config); - if (err != ESP_OK) { - ESP_LOGE("OI", "i2c param config error: %d", err); - } - - err = i2c_driver_install(0, I2C_MODE_MASTER, 0, 0, 0); - if (err != ESP_OK) { - ESP_LOGE("OI", "i2c driver installation error: %d", err); - } + err = i2c_param_config(0, &i2c_config); + if (err != ESP_OK) { + ESP_LOGE("OI", "i2c param config error: %d", err); + } - i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create(); + err = i2c_driver_install(0, I2C_MODE_MASTER, 0, 0, 0); + if (err != ESP_OK) { + ESP_LOGE("OI", "i2c driver installation error: %d", err); + } - err = i2c_master_start(i2c_cmd); - if (err != ESP_OK) { - ESP_LOGE("OI", "i2c master start error: %d", err); - } + i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create(); - /* mute DAC during setup, power up all systems, slave mode */ - writeReg(ES8388_DACCONTROL3, 0x04); - writeReg(ES8388_CONTROL2, 0x50); - writeReg(ES8388_CHIPPOWER, 0x00); - writeReg(ES8388_MASTERMODE, 0x00); + err = i2c_master_start(i2c_cmd); + if (err != ESP_OK) { + ESP_LOGE("OI", "i2c master start error: %d", err); + } - /* power up DAC and enable LOUT1+2 / ROUT1+2, ADC sample rate = DAC sample rate */ - writeReg(ES8388_DACPOWER, 0x3e); - writeReg(ES8388_CONTROL1, 0x12); + /* mute DAC during setup, power up all systems, slave mode */ + writeReg(ES8388_DACCONTROL3, 0x04); + writeReg(ES8388_CONTROL2, 0x50); + writeReg(ES8388_CHIPPOWER, 0x00); + writeReg(ES8388_MASTERMODE, 0x00); - /* DAC I2S setup: 16 bit word length, I2S format; MCLK / Fs = 256*/ - writeReg(ES8388_DACCONTROL1, 0x18); - writeReg(ES8388_DACCONTROL2, 0x02); + /* power up DAC and enable LOUT1+2 / ROUT1+2, ADC sample rate = DAC sample rate */ + writeReg(ES8388_DACPOWER, 0x3e); + writeReg(ES8388_CONTROL1, 0x12); - /* DAC to output route mixer configuration: ADC MIX TO OUTPUT */ - writeReg(ES8388_DACCONTROL16, 0x1B); - writeReg(ES8388_DACCONTROL17, 0x90); - writeReg(ES8388_DACCONTROL20, 0x90); + /* DAC I2S setup: 16 bit word length, I2S format; MCLK / Fs = 256*/ + writeReg(ES8388_DACCONTROL1, 0x18); + writeReg(ES8388_DACCONTROL2, 0x02); - /* DAC and ADC use same LRCK, enable MCLK input; output resistance setup */ - writeReg(ES8388_DACCONTROL21, 0x80); - writeReg(ES8388_DACCONTROL23, 0x00); + /* DAC to output route mixer configuration: ADC MIX TO OUTPUT */ + writeReg(ES8388_DACCONTROL16, 0x1B); + writeReg(ES8388_DACCONTROL17, 0x90); + writeReg(ES8388_DACCONTROL20, 0x90); - /* DAC volume control: 0dB (maximum, unattented) */ - writeReg(ES8388_DACCONTROL5, 0x00); - writeReg(ES8388_DACCONTROL4, 0x00); + /* DAC and ADC use same LRCK, enable MCLK input; output resistance setup */ + writeReg(ES8388_DACCONTROL21, 0x80); + writeReg(ES8388_DACCONTROL23, 0x00); - /* power down ADC while configuring; volume: +9dB for both channels */ - writeReg(ES8388_ADCPOWER, 0xff); - writeReg(ES8388_ADCCONTROL1, 0x88); // +24db + /* DAC volume control: 0dB (maximum, unattented) */ + writeReg(ES8388_DACCONTROL5, 0x00); + writeReg(ES8388_DACCONTROL4, 0x00); - /* select LINPUT2 / RINPUT2 as ADC input; stereo; 16 bit word length, format right-justified, MCLK / Fs = 256 */ - writeReg(ES8388_ADCCONTROL2, 0xf0); // 50 - writeReg(ES8388_ADCCONTROL3, 0x80); // 00 - writeReg(ES8388_ADCCONTROL4, 0x0e); - writeReg(ES8388_ADCCONTROL5, 0x02); + /* power down ADC while configuring; volume: +9dB for both channels */ + writeReg(ES8388_ADCPOWER, 0xff); + writeReg(ES8388_ADCCONTROL1, 0x88); // +24db - /* set ADC volume */ - writeReg(ES8388_ADCCONTROL8, 0x20); - writeReg(ES8388_ADCCONTROL9, 0x20); + /* select LINPUT2 / RINPUT2 as ADC input; stereo; 16 bit word length, format right-justified, MCLK / Fs = 256 */ + writeReg(ES8388_ADCCONTROL2, 0xf0); // 50 + writeReg(ES8388_ADCCONTROL3, 0x80); // 00 + writeReg(ES8388_ADCCONTROL4, 0x0e); + writeReg(ES8388_ADCCONTROL5, 0x02); - /* set LOUT1 / ROUT1 volume: 0dB (unattenuated) */ - writeReg(ES8388_DACCONTROL24, 0x1e); - writeReg(ES8388_DACCONTROL25, 0x1e); + /* set ADC volume */ + writeReg(ES8388_ADCCONTROL8, 0x20); + writeReg(ES8388_ADCCONTROL9, 0x20); - /* set LOUT2 / ROUT2 volume: 0dB (unattenuated) */ - writeReg(ES8388_DACCONTROL26, 0x1e); - writeReg(ES8388_DACCONTROL27, 0x1e); + /* set LOUT1 / ROUT1 volume: 0dB (unattenuated) */ + writeReg(ES8388_DACCONTROL24, 0x1e); + writeReg(ES8388_DACCONTROL25, 0x1e); - /* power up and enable DAC; power up ADC (no MIC bias) */ - writeReg(ES8388_DACPOWER, 0x3c); - writeReg(ES8388_DACCONTROL3, 0x00); - writeReg(ES8388_ADCPOWER, 0x00); + /* set LOUT2 / ROUT2 volume: 0dB (unattenuated) */ + writeReg(ES8388_DACCONTROL26, 0x1e); + writeReg(ES8388_DACCONTROL27, 0x1e); - startI2sFeed(); + /* power up and enable DAC; power up ADC (no MIC bias) */ + writeReg(ES8388_DACPOWER, 0x3c); + writeReg(ES8388_DACCONTROL3, 0x00); + writeReg(ES8388_ADCPOWER, 0x00); + + startI2sFeed(); } -void ES8388AudioSink::writeReg(uint8_t reg_add, uint8_t data) -{ +void ES8388AudioSink::writeReg(uint8_t reg_add, uint8_t data) { - int res = 0; - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - res |= i2c_master_start(cmd); - res |= i2c_master_write_byte(cmd, ES8388_ADDR, ACK_CHECK_EN); - res |= i2c_master_write_byte(cmd, reg_add, ACK_CHECK_EN); - res |= i2c_master_write_byte(cmd, data, ACK_CHECK_EN); - res |= i2c_master_stop(cmd); - res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); - i2c_cmd_link_delete(cmd); + int res = 0; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + res |= i2c_master_start(cmd); + res |= i2c_master_write_byte(cmd, ES8388_ADDR, ACK_CHECK_EN); + res |= i2c_master_write_byte(cmd, reg_add, ACK_CHECK_EN); + res |= i2c_master_write_byte(cmd, data, ACK_CHECK_EN); + res |= i2c_master_stop(cmd); + res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); - if (res != ESP_OK) { - ESP_LOGE("RR", "Unable to write to ES8388: %d", res); - }else{ - ESP_LOGE("RR", "register successfull written."); - } + if (res != ESP_OK) { + ESP_LOGE("RR", "Unable to write to ES8388: %d", res); + } else { + ESP_LOGE("RR", "register successfull written."); + } } -ES8388AudioSink::~ES8388AudioSink() -{ -} +ES8388AudioSink::~ES8388AudioSink() {} diff --git a/components/spotify/cspot/bell/main/audio-sinks/esp/ES9018AudioSink.cpp b/components/spotify/cspot/bell/main/audio-sinks/esp/ES9018AudioSink.cpp index 94dc5817..24143fbc 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/esp/ES9018AudioSink.cpp +++ b/components/spotify/cspot/bell/main/audio-sinks/esp/ES9018AudioSink.cpp @@ -2,35 +2,31 @@ #include "driver/i2s.h" -ES9018AudioSink::ES9018AudioSink() -{ - i2s_config_t i2s_config = { +ES9018AudioSink::ES9018AudioSink() { + i2s_config_t i2s_config = { - .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX - .sample_rate = 44100, - .bits_per_sample = (i2s_bits_per_sample_t)16, - .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels - .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_MSB, - .intr_alloc_flags = 0, //Default interrupt priority - .dma_buf_count = 8, - .dma_buf_len = 512, - .use_apll = true, - .tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow - .fixed_mclk = 384 * 44100 - }; + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX + .sample_rate = 44100, + .bits_per_sample = (i2s_bits_per_sample_t)16, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels + .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_MSB, + .intr_alloc_flags = 0, //Default interrupt priority + .dma_buf_count = 8, + .dma_buf_len = 512, + .use_apll = true, + .tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow + .fixed_mclk = 384 * 44100}; - i2s_pin_config_t pin_config = { - .bck_io_num = 27, - .ws_io_num = 32, - .data_out_num = 25, - .data_in_num = -1 //Not used - }; - i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); - i2s_set_pin((i2s_port_t)0, &pin_config); - - startI2sFeed(); + i2s_pin_config_t pin_config = { + .bck_io_num = 27, + .ws_io_num = 32, + .data_out_num = 25, + .data_in_num = -1 //Not used + }; + i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); + i2s_set_pin((i2s_port_t)0, &pin_config); + + startI2sFeed(); } -ES9018AudioSink::~ES9018AudioSink() -{ -} +ES9018AudioSink::~ES9018AudioSink() {} diff --git a/components/spotify/cspot/bell/main/audio-sinks/esp/InternalAudioSink.cpp b/components/spotify/cspot/bell/main/audio-sinks/esp/InternalAudioSink.cpp index 26872a2e..a918f037 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/esp/InternalAudioSink.cpp +++ b/components/spotify/cspot/bell/main/audio-sinks/esp/InternalAudioSink.cpp @@ -1,35 +1,32 @@ #include "InternalAudioSink.h" #include "driver/i2s.h" -InternalAudioSink::InternalAudioSink() -{ - softwareVolumeControl = true; - usign = true; - #ifdef I2S_MODE_DAC_BUILT_IN +InternalAudioSink::InternalAudioSink() { + softwareVolumeControl = true; + usign = true; +#ifdef I2S_MODE_DAC_BUILT_IN - i2s_config_t i2s_config = { - .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN), // Only TX - .sample_rate = (i2s_bits_per_sample_t)44100, - .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, - .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, - .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_I2S, - .intr_alloc_flags = 0,//ESP_INTR_FLAG_LEVEL1 - .dma_buf_count = 6, - .dma_buf_len = 512, - .use_apll = true, - .tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow - .fixed_mclk=-1 - }; + i2s_config_t i2s_config = { + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | + I2S_MODE_DAC_BUILT_IN), // Only TX + .sample_rate = (i2s_bits_per_sample_t)44100, + .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, + .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_I2S, + .intr_alloc_flags = 0, //ESP_INTR_FLAG_LEVEL1 + .dma_buf_count = 6, + .dma_buf_len = 512, + .use_apll = true, + .tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow + .fixed_mclk = -1}; - //install and start i2s driver - i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); - //init DAC - i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN); - #endif + //install and start i2s driver + i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); + //init DAC + i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN); +#endif - startI2sFeed(); + startI2sFeed(); } -InternalAudioSink::~InternalAudioSink() -{ -} +InternalAudioSink::~InternalAudioSink() {} diff --git a/components/spotify/cspot/bell/main/audio-sinks/esp/PCM5102AudioSink.cpp b/components/spotify/cspot/bell/main/audio-sinks/esp/PCM5102AudioSink.cpp index 5cd6ecb6..81f0867a 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/esp/PCM5102AudioSink.cpp +++ b/components/spotify/cspot/bell/main/audio-sinks/esp/PCM5102AudioSink.cpp @@ -2,35 +2,31 @@ #include "driver/i2s.h" -PCM5102AudioSink::PCM5102AudioSink() -{ - i2s_config_t i2s_config = { +PCM5102AudioSink::PCM5102AudioSink() { + i2s_config_t i2s_config = { - .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX - .sample_rate = 44100, - .bits_per_sample = (i2s_bits_per_sample_t)16, - .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels - .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S, - .intr_alloc_flags = 0, //Default interrupt priority - .dma_buf_count = 8, - .dma_buf_len = 512, - .use_apll = true, - .tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow - .fixed_mclk = 384 * 44100 - }; + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX + .sample_rate = 44100, + .bits_per_sample = (i2s_bits_per_sample_t)16, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels + .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S, + .intr_alloc_flags = 0, //Default interrupt priority + .dma_buf_count = 8, + .dma_buf_len = 512, + .use_apll = true, + .tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow + .fixed_mclk = 384 * 44100}; - i2s_pin_config_t pin_config = { - .bck_io_num = 27, - .ws_io_num = 32, - .data_out_num = 25, - .data_in_num = -1 //Not used - }; - i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); - i2s_set_pin((i2s_port_t)0, &pin_config); + i2s_pin_config_t pin_config = { + .bck_io_num = 27, + .ws_io_num = 32, + .data_out_num = 25, + .data_in_num = -1 //Not used + }; + i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); + i2s_set_pin((i2s_port_t)0, &pin_config); - startI2sFeed(); + startI2sFeed(); } -PCM5102AudioSink::~PCM5102AudioSink() -{ -} +PCM5102AudioSink::~PCM5102AudioSink() {} diff --git a/components/spotify/cspot/bell/main/audio-sinks/esp/SPDIFAudioSink.cpp b/components/spotify/cspot/bell/main/audio-sinks/esp/SPDIFAudioSink.cpp index d6ce712b..1b4644f0 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/esp/SPDIFAudioSink.cpp +++ b/components/spotify/cspot/bell/main/audio-sinks/esp/SPDIFAudioSink.cpp @@ -5,124 +5,118 @@ // See http://www.hardwarebook.info/S/PDIF for more info on this protocol // Conversion table to biphase code mark (LSB first, ending in 1) static const uint16_t bmc_convert[256] = { - 0x3333, 0xb333, 0xd333, 0x5333, 0xcb33, 0x4b33, 0x2b33, 0xab33, - 0xcd33, 0x4d33, 0x2d33, 0xad33, 0x3533, 0xb533, 0xd533, 0x5533, - 0xccb3, 0x4cb3, 0x2cb3, 0xacb3, 0x34b3, 0xb4b3, 0xd4b3, 0x54b3, - 0x32b3, 0xb2b3, 0xd2b3, 0x52b3, 0xcab3, 0x4ab3, 0x2ab3, 0xaab3, - 0xccd3, 0x4cd3, 0x2cd3, 0xacd3, 0x34d3, 0xb4d3, 0xd4d3, 0x54d3, - 0x32d3, 0xb2d3, 0xd2d3, 0x52d3, 0xcad3, 0x4ad3, 0x2ad3, 0xaad3, - 0x3353, 0xb353, 0xd353, 0x5353, 0xcb53, 0x4b53, 0x2b53, 0xab53, - 0xcd53, 0x4d53, 0x2d53, 0xad53, 0x3553, 0xb553, 0xd553, 0x5553, - 0xcccb, 0x4ccb, 0x2ccb, 0xaccb, 0x34cb, 0xb4cb, 0xd4cb, 0x54cb, - 0x32cb, 0xb2cb, 0xd2cb, 0x52cb, 0xcacb, 0x4acb, 0x2acb, 0xaacb, - 0x334b, 0xb34b, 0xd34b, 0x534b, 0xcb4b, 0x4b4b, 0x2b4b, 0xab4b, - 0xcd4b, 0x4d4b, 0x2d4b, 0xad4b, 0x354b, 0xb54b, 0xd54b, 0x554b, - 0x332b, 0xb32b, 0xd32b, 0x532b, 0xcb2b, 0x4b2b, 0x2b2b, 0xab2b, - 0xcd2b, 0x4d2b, 0x2d2b, 0xad2b, 0x352b, 0xb52b, 0xd52b, 0x552b, - 0xccab, 0x4cab, 0x2cab, 0xacab, 0x34ab, 0xb4ab, 0xd4ab, 0x54ab, - 0x32ab, 0xb2ab, 0xd2ab, 0x52ab, 0xcaab, 0x4aab, 0x2aab, 0xaaab, - 0xcccd, 0x4ccd, 0x2ccd, 0xaccd, 0x34cd, 0xb4cd, 0xd4cd, 0x54cd, - 0x32cd, 0xb2cd, 0xd2cd, 0x52cd, 0xcacd, 0x4acd, 0x2acd, 0xaacd, - 0x334d, 0xb34d, 0xd34d, 0x534d, 0xcb4d, 0x4b4d, 0x2b4d, 0xab4d, - 0xcd4d, 0x4d4d, 0x2d4d, 0xad4d, 0x354d, 0xb54d, 0xd54d, 0x554d, - 0x332d, 0xb32d, 0xd32d, 0x532d, 0xcb2d, 0x4b2d, 0x2b2d, 0xab2d, - 0xcd2d, 0x4d2d, 0x2d2d, 0xad2d, 0x352d, 0xb52d, 0xd52d, 0x552d, - 0xccad, 0x4cad, 0x2cad, 0xacad, 0x34ad, 0xb4ad, 0xd4ad, 0x54ad, - 0x32ad, 0xb2ad, 0xd2ad, 0x52ad, 0xcaad, 0x4aad, 0x2aad, 0xaaad, - 0x3335, 0xb335, 0xd335, 0x5335, 0xcb35, 0x4b35, 0x2b35, 0xab35, - 0xcd35, 0x4d35, 0x2d35, 0xad35, 0x3535, 0xb535, 0xd535, 0x5535, - 0xccb5, 0x4cb5, 0x2cb5, 0xacb5, 0x34b5, 0xb4b5, 0xd4b5, 0x54b5, - 0x32b5, 0xb2b5, 0xd2b5, 0x52b5, 0xcab5, 0x4ab5, 0x2ab5, 0xaab5, - 0xccd5, 0x4cd5, 0x2cd5, 0xacd5, 0x34d5, 0xb4d5, 0xd4d5, 0x54d5, - 0x32d5, 0xb2d5, 0xd2d5, 0x52d5, 0xcad5, 0x4ad5, 0x2ad5, 0xaad5, - 0x3355, 0xb355, 0xd355, 0x5355, 0xcb55, 0x4b55, 0x2b55, 0xab55, - 0xcd55, 0x4d55, 0x2d55, 0xad55, 0x3555, 0xb555, 0xd555, 0x5555, + 0x3333, 0xb333, 0xd333, 0x5333, 0xcb33, 0x4b33, 0x2b33, 0xab33, 0xcd33, + 0x4d33, 0x2d33, 0xad33, 0x3533, 0xb533, 0xd533, 0x5533, 0xccb3, 0x4cb3, + 0x2cb3, 0xacb3, 0x34b3, 0xb4b3, 0xd4b3, 0x54b3, 0x32b3, 0xb2b3, 0xd2b3, + 0x52b3, 0xcab3, 0x4ab3, 0x2ab3, 0xaab3, 0xccd3, 0x4cd3, 0x2cd3, 0xacd3, + 0x34d3, 0xb4d3, 0xd4d3, 0x54d3, 0x32d3, 0xb2d3, 0xd2d3, 0x52d3, 0xcad3, + 0x4ad3, 0x2ad3, 0xaad3, 0x3353, 0xb353, 0xd353, 0x5353, 0xcb53, 0x4b53, + 0x2b53, 0xab53, 0xcd53, 0x4d53, 0x2d53, 0xad53, 0x3553, 0xb553, 0xd553, + 0x5553, 0xcccb, 0x4ccb, 0x2ccb, 0xaccb, 0x34cb, 0xb4cb, 0xd4cb, 0x54cb, + 0x32cb, 0xb2cb, 0xd2cb, 0x52cb, 0xcacb, 0x4acb, 0x2acb, 0xaacb, 0x334b, + 0xb34b, 0xd34b, 0x534b, 0xcb4b, 0x4b4b, 0x2b4b, 0xab4b, 0xcd4b, 0x4d4b, + 0x2d4b, 0xad4b, 0x354b, 0xb54b, 0xd54b, 0x554b, 0x332b, 0xb32b, 0xd32b, + 0x532b, 0xcb2b, 0x4b2b, 0x2b2b, 0xab2b, 0xcd2b, 0x4d2b, 0x2d2b, 0xad2b, + 0x352b, 0xb52b, 0xd52b, 0x552b, 0xccab, 0x4cab, 0x2cab, 0xacab, 0x34ab, + 0xb4ab, 0xd4ab, 0x54ab, 0x32ab, 0xb2ab, 0xd2ab, 0x52ab, 0xcaab, 0x4aab, + 0x2aab, 0xaaab, 0xcccd, 0x4ccd, 0x2ccd, 0xaccd, 0x34cd, 0xb4cd, 0xd4cd, + 0x54cd, 0x32cd, 0xb2cd, 0xd2cd, 0x52cd, 0xcacd, 0x4acd, 0x2acd, 0xaacd, + 0x334d, 0xb34d, 0xd34d, 0x534d, 0xcb4d, 0x4b4d, 0x2b4d, 0xab4d, 0xcd4d, + 0x4d4d, 0x2d4d, 0xad4d, 0x354d, 0xb54d, 0xd54d, 0x554d, 0x332d, 0xb32d, + 0xd32d, 0x532d, 0xcb2d, 0x4b2d, 0x2b2d, 0xab2d, 0xcd2d, 0x4d2d, 0x2d2d, + 0xad2d, 0x352d, 0xb52d, 0xd52d, 0x552d, 0xccad, 0x4cad, 0x2cad, 0xacad, + 0x34ad, 0xb4ad, 0xd4ad, 0x54ad, 0x32ad, 0xb2ad, 0xd2ad, 0x52ad, 0xcaad, + 0x4aad, 0x2aad, 0xaaad, 0x3335, 0xb335, 0xd335, 0x5335, 0xcb35, 0x4b35, + 0x2b35, 0xab35, 0xcd35, 0x4d35, 0x2d35, 0xad35, 0x3535, 0xb535, 0xd535, + 0x5535, 0xccb5, 0x4cb5, 0x2cb5, 0xacb5, 0x34b5, 0xb4b5, 0xd4b5, 0x54b5, + 0x32b5, 0xb2b5, 0xd2b5, 0x52b5, 0xcab5, 0x4ab5, 0x2ab5, 0xaab5, 0xccd5, + 0x4cd5, 0x2cd5, 0xacd5, 0x34d5, 0xb4d5, 0xd4d5, 0x54d5, 0x32d5, 0xb2d5, + 0xd2d5, 0x52d5, 0xcad5, 0x4ad5, 0x2ad5, 0xaad5, 0x3355, 0xb355, 0xd355, + 0x5355, 0xcb55, 0x4b55, 0x2b55, 0xab55, 0xcd55, 0x4d55, 0x2d55, 0xad55, + 0x3555, 0xb555, 0xd555, 0x5555, }; -#define I2S_BUG_MAGIC (26 * 1000 * 1000) // magic number for avoiding I2S bug -#define BITS_PER_SUBFRAME 64 -#define FRAMES_PER_BLOCK 192 -#define SPDIF_BUF_SIZE (BITS_PER_SUBFRAME/8 * 2 * FRAMES_PER_BLOCK) -#define SPDIF_BUF_ARRAY_SIZE (SPDIF_BUF_SIZE / sizeof(uint32_t)) +#define I2S_BUG_MAGIC (26 * 1000 * 1000) // magic number for avoiding I2S bug +#define BITS_PER_SUBFRAME 64 +#define FRAMES_PER_BLOCK 192 +#define SPDIF_BUF_SIZE (BITS_PER_SUBFRAME / 8 * 2 * FRAMES_PER_BLOCK) +#define SPDIF_BUF_ARRAY_SIZE (SPDIF_BUF_SIZE / sizeof(uint32_t)) -#define BMC_B 0x33173333 // block start -#define BMC_M 0x331d3333 // left ch -#define BMC_W 0x331b3333 // right ch -#define BMC_MW_DIF (BMC_M ^ BMC_W) +#define BMC_B 0x33173333 // block start +#define BMC_M 0x331d3333 // left ch +#define BMC_W 0x331b3333 // right ch +#define BMC_MW_DIF (BMC_M ^ BMC_W) static uint32_t spdif_buf[SPDIF_BUF_ARRAY_SIZE]; -static uint32_t *spdif_ptr; +static uint32_t* spdif_ptr; -static void spdif_buf_init(void) -{ - // first bllock has W preamble - spdif_buf[0] = BMC_B; +static void spdif_buf_init(void) { + // first bllock has W preamble + spdif_buf[0] = BMC_B; - // all other blocks are alternating M, then W preamble - uint32_t bmc_mw = BMC_M; - for (int i = 2; i < SPDIF_BUF_ARRAY_SIZE; i += 2) - { - spdif_buf[i] = bmc_mw ^= BMC_MW_DIF; - } + // all other blocks are alternating M, then W preamble + uint32_t bmc_mw = BMC_M; + for (int i = 2; i < SPDIF_BUF_ARRAY_SIZE; i += 2) { + spdif_buf[i] = bmc_mw ^= BMC_MW_DIF; + } } -SPDIFAudioSink::SPDIFAudioSink(uint8_t spdifPin) -{ - // initialize S/PDIF buffer - spdif_buf_init(); - spdif_ptr = spdif_buf; - this->spdifPin = spdifPin; - this->setParams(44100, 16, 2); - startI2sFeed(SPDIF_BUF_SIZE * 16); +SPDIFAudioSink::SPDIFAudioSink(uint8_t spdifPin) { + // initialize S/PDIF buffer + spdif_buf_init(); + spdif_ptr = spdif_buf; + this->spdifPin = spdifPin; + this->setParams(44100, 16, 2); + startI2sFeed(SPDIF_BUF_SIZE * 16); } -bool SPDIFAudioSink::setParams(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) { - if (bitDepth != 16 || channelCount != 2) // TODO support mono playback and different bit widths - return false; - int sample_rate = (int)sampleRate * 2; - int bclk = sample_rate * 64 * 2; - int mclk = (I2S_BUG_MAGIC / bclk) * bclk; +bool SPDIFAudioSink::setParams(uint32_t sampleRate, uint8_t channelCount, + uint8_t bitDepth) { + if (bitDepth != 16 || + channelCount != 2) // TODO support mono playback and different bit widths + return false; + int sample_rate = (int)sampleRate * 2; + int bclk = sample_rate * 64 * 2; + int mclk = (I2S_BUG_MAGIC / bclk) * bclk; - i2s_config_t i2s_config = { - .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), + i2s_config_t i2s_config = { + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0) - .sample_rate = (uint32_t) sample_rate, + .sample_rate = (uint32_t)sample_rate, #else - .sample_rate = (int) sample_rate, + .sample_rate = (int)sample_rate, #endif - .bits_per_sample = (i2s_bits_per_sample_t)(bitDepth * 2), - .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, - .communication_format = I2S_COMM_FORMAT_STAND_I2S, - .intr_alloc_flags = 0, - .dma_buf_count = 8, - .dma_buf_len = 512, - .use_apll = true, - .tx_desc_auto_clear = true, - .fixed_mclk = mclk, // avoiding I2S bug - }; - i2s_pin_config_t pin_config = { - .bck_io_num = -1, - .ws_io_num = -1, - .data_out_num = spdifPin, - .data_in_num = -1, - }; - i2s_driver_uninstall((i2s_port_t)0); - int err = i2s_driver_install((i2s_port_t)0, &i2s_config, 0, nullptr); - i2s_set_pin((i2s_port_t)0, &pin_config); - return !err; + .bits_per_sample = (i2s_bits_per_sample_t)(bitDepth * 2), + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, + .communication_format = I2S_COMM_FORMAT_STAND_I2S, + .intr_alloc_flags = 0, + .dma_buf_count = 8, + .dma_buf_len = 512, + .use_apll = true, + .tx_desc_auto_clear = true, + .fixed_mclk = mclk, // avoiding I2S bug + }; + i2s_pin_config_t pin_config = { + .bck_io_num = -1, + .ws_io_num = -1, + .data_out_num = spdifPin, + .data_in_num = -1, + }; + i2s_driver_uninstall((i2s_port_t)0); + int err = i2s_driver_install((i2s_port_t)0, &i2s_config, 0, nullptr); + i2s_set_pin((i2s_port_t)0, &pin_config); + return !err; } SPDIFAudioSink::~SPDIFAudioSink() { - i2s_driver_uninstall((i2s_port_t)0); + i2s_driver_uninstall((i2s_port_t)0); } int num_frames = 0; -void SPDIFAudioSink::feedPCMFrames(const uint8_t *buffer, size_t bytes) -{ - for (int i = 0; i < bytes; i += 2) - { - /** +void SPDIFAudioSink::feedPCMFrames(const uint8_t* buffer, size_t bytes) { + for (int i = 0; i < bytes; i += 2) { + /** * What is this, and why does it work? * * Rather than assemble all S/PDIF frames from scratch we want to do the @@ -171,16 +165,16 @@ void SPDIFAudioSink::feedPCMFrames(const uint8_t *buffer, size_t bytes) * I did not come up with this, all credit goes to * github.com/amedes/esp_a2dp_sink_spdif */ - uint32_t lo = ((uint32_t)(bmc_convert[buffer[i]]) << 16); - uint32_t hi = (uint32_t)((int16_t)bmc_convert[buffer[i+1]]); + uint32_t lo = ((uint32_t)(bmc_convert[buffer[i]]) << 16); + uint32_t hi = (uint32_t)((int16_t)bmc_convert[buffer[i + 1]]); - *(spdif_ptr + 1) = ((lo ^ hi) << 1) >> 1; + *(spdif_ptr + 1) = ((lo ^ hi) << 1) >> 1; - spdif_ptr += 2; // advance to next audio data - - if (spdif_ptr >= &spdif_buf[SPDIF_BUF_ARRAY_SIZE]) { - feedPCMFramesInternal(spdif_buf, sizeof(spdif_buf)); - spdif_ptr = spdif_buf; - } + spdif_ptr += 2; // advance to next audio data + + if (spdif_ptr >= &spdif_buf[SPDIF_BUF_ARRAY_SIZE]) { + feedPCMFramesInternal(spdif_buf, sizeof(spdif_buf)); + spdif_ptr = spdif_buf; } + } } \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/esp/TAS5711AudioSink.cpp b/components/spotify/cspot/bell/main/audio-sinks/esp/TAS5711AudioSink.cpp index ecee28da..935c5fba 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/esp/TAS5711AudioSink.cpp +++ b/components/spotify/cspot/bell/main/audio-sinks/esp/TAS5711AudioSink.cpp @@ -1,117 +1,107 @@ #include "TAS5711AudioSink.h" - struct tas5711_cmd_s { - uint8_t reg; - uint8_t value; + uint8_t reg; + uint8_t value; }; static const struct tas5711_cmd_s tas5711_init_sequence[] = { - { 0x00, 0x6c }, // 0x6c - 256 x mclk - { 0x04, 0x03 }, // 0x03 - 16 bit i2s - { 0x05, 0x00 }, // system control 0x00 is audio playback - { 0x06, 0x00 }, // disable mute - { 0x07, 0x50 }, // volume register - { 0xff, 0xff } + {0x00, 0x6c}, // 0x6c - 256 x mclk + {0x04, 0x03}, // 0x03 - 16 bit i2s + {0x05, 0x00}, // system control 0x00 is audio playback + {0x06, 0x00}, // disable mute + {0x07, 0x50}, // volume register + {0xff, 0xff} }; i2c_ack_type_t ACK_CHECK_EN = (i2c_ack_type_t)0x1; -TAS5711AudioSink::TAS5711AudioSink() -{ - i2s_config_t i2s_config = { +TAS5711AudioSink::TAS5711AudioSink() { + i2s_config_t i2s_config = { - .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX - .sample_rate = 44100, - .bits_per_sample = (i2s_bits_per_sample_t)16, - .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels - .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_MSB, - .intr_alloc_flags = 0, //Default interrupt priority - .dma_buf_count = 8, - .dma_buf_len = 512, - .use_apll = true, - .tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow - .fixed_mclk = 256 * 44100 - }; + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX + .sample_rate = 44100, + .bits_per_sample = (i2s_bits_per_sample_t)16, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels + .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_MSB, + .intr_alloc_flags = 0, //Default interrupt priority + .dma_buf_count = 8, + .dma_buf_len = 512, + .use_apll = true, + .tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow + .fixed_mclk = 256 * 44100}; + i2s_pin_config_t pin_config = { + .bck_io_num = 5, + .ws_io_num = 25, + .data_out_num = 26, + .data_in_num = -1 //Not used + }; + i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); + i2s_set_pin((i2s_port_t)0, &pin_config); - i2s_pin_config_t pin_config = { - .bck_io_num = 5, - .ws_io_num = 25, - .data_out_num = 26, - .data_in_num = -1 //Not used - }; - i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); - i2s_set_pin((i2s_port_t)0, &pin_config); + // configure i2c + i2c_config = { + .mode = I2C_MODE_MASTER, + .sda_io_num = 21, + .scl_io_num = 23, + .sda_pullup_en = GPIO_PULLUP_DISABLE, + .scl_pullup_en = GPIO_PULLUP_DISABLE, + }; - // configure i2c - i2c_config = { - .mode = I2C_MODE_MASTER, - .sda_io_num = 21, - .scl_io_num = 23, - .sda_pullup_en = GPIO_PULLUP_DISABLE, - .scl_pullup_en = GPIO_PULLUP_DISABLE, - }; + i2c_config.master.clk_speed = 250000; - i2c_config.master.clk_speed = 250000; + i2c_param_config(i2c_port, &i2c_config); + i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false); + i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create(); - i2c_param_config(i2c_port, &i2c_config); - i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false); - i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create(); + uint8_t data, addr = (0x1b); - uint8_t data, addr = (0x1b); + i2c_master_start(i2c_cmd); + i2c_master_write_byte(i2c_cmd, (addr << 1) | I2C_MASTER_WRITE, ACK_CHECK_EN); + i2c_master_write_byte(i2c_cmd, 00, ACK_CHECK_EN); - i2c_master_start(i2c_cmd); - i2c_master_write_byte(i2c_cmd, (addr << 1) | I2C_MASTER_WRITE, ACK_CHECK_EN); - i2c_master_write_byte(i2c_cmd, 00, ACK_CHECK_EN); + i2c_master_start(i2c_cmd); + i2c_master_write_byte(i2c_cmd, (addr << 1) | I2C_MASTER_READ, ACK_CHECK_EN); + i2c_master_read_byte(i2c_cmd, &data, ACK_CHECK_EN); - i2c_master_start(i2c_cmd); - i2c_master_write_byte(i2c_cmd, (addr << 1) | I2C_MASTER_READ, ACK_CHECK_EN); - i2c_master_read_byte(i2c_cmd, &data, ACK_CHECK_EN); + i2c_master_stop(i2c_cmd); + int ret = i2c_master_cmd_begin(i2c_port, i2c_cmd, 50 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(i2c_cmd); - i2c_master_stop(i2c_cmd); - int ret = i2c_master_cmd_begin(i2c_port, i2c_cmd, 50 / portTICK_PERIOD_MS); - i2c_cmd_link_delete(i2c_cmd); + if (ret == ESP_OK) { + ESP_LOGI("RR", "Detected TAS"); + } else { + ESP_LOGI("RR", "Unable to detect dac"); + } - if (ret == ESP_OK) { - ESP_LOGI("RR", "Detected TAS"); - } - else { - ESP_LOGI("RR", "Unable to detect dac"); - } + writeReg(0x1b, 0x00); + vTaskDelay(100 / portTICK_PERIOD_MS); - writeReg(0x1b, 0x00); - vTaskDelay(100 / portTICK_PERIOD_MS); + for (int i = 0; tas5711_init_sequence[i].reg != 0xff; i++) { + writeReg(tas5711_init_sequence[i].reg, tas5711_init_sequence[i].value); + vTaskDelay(1 / portTICK_PERIOD_MS); + } - - for (int i = 0; tas5711_init_sequence[i].reg != 0xff; i++) { - writeReg(tas5711_init_sequence[i].reg, tas5711_init_sequence[i].value); - vTaskDelay(1 / portTICK_PERIOD_MS); - } - - startI2sFeed(); + startI2sFeed(); } -void TAS5711AudioSink::writeReg(uint8_t reg, uint8_t value) -{ - i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create(); +void TAS5711AudioSink::writeReg(uint8_t reg, uint8_t value) { + i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create(); - i2c_master_start(i2c_cmd); - i2c_master_write_byte(i2c_cmd, (0x1b << 1) | I2C_MASTER_WRITE, ACK_CHECK_EN); - i2c_master_write_byte(i2c_cmd, reg, ACK_CHECK_EN); - i2c_master_write_byte(i2c_cmd, value, ACK_CHECK_EN); + i2c_master_start(i2c_cmd); + i2c_master_write_byte(i2c_cmd, (0x1b << 1) | I2C_MASTER_WRITE, ACK_CHECK_EN); + i2c_master_write_byte(i2c_cmd, reg, ACK_CHECK_EN); + i2c_master_write_byte(i2c_cmd, value, ACK_CHECK_EN); + i2c_master_stop(i2c_cmd); + esp_err_t res = + i2c_master_cmd_begin(i2c_port, i2c_cmd, 500 / portTICK_PERIOD_MS); - i2c_master_stop(i2c_cmd); - esp_err_t res = i2c_master_cmd_begin(i2c_port, i2c_cmd, 500 / portTICK_PERIOD_MS); - - if (res != ESP_OK) { - ESP_LOGE("RR", "Unable to write to TAS5711"); - } - i2c_cmd_link_delete(i2c_cmd); - + if (res != ESP_OK) { + ESP_LOGE("RR", "Unable to write to TAS5711"); + } + i2c_cmd_link_delete(i2c_cmd); } -TAS5711AudioSink::~TAS5711AudioSink() -{ -} +TAS5711AudioSink::~TAS5711AudioSink() {} diff --git a/components/spotify/cspot/bell/main/audio-sinks/esp/ac101.c b/components/spotify/cspot/bell/main/audio-sinks/esp/ac101.c index c05abd86..d0fdbd0e 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/esp/ac101.c +++ b/components/spotify/cspot/bell/main/audio-sinks/esp/ac101.c @@ -22,16 +22,16 @@ * */ -#include -#include -#include -#include -#include -#include +#include "ac101.h" #include #include +#include +#include +#include +#include +#include +#include #include "adac.h" -#include "ac101.h" const static char TAG[] = "AC101"; @@ -42,14 +42,13 @@ const static char TAG[] = "AC101"; #define min(a, b) (((a) < (b)) ? (a) : (b)) #define max(a, b) (((a) > (b)) ? (a) : (b)) -#define AC_ASSERT(a, format, b, ...) \ - if ((a) != 0) \ - { \ - ESP_LOGE(TAG, format, ##__VA_ARGS__); \ - return b; \ - } +#define AC_ASSERT(a, format, b, ...) \ + if ((a) != 0) { \ + ESP_LOGE(TAG, format, ##__VA_ARGS__); \ + return b; \ + } -static bool init(int i2c_port_num, int i2s_num, i2s_config_t *config); +static bool init(int i2c_port_num, int i2s_num, i2s_config_t* config); static void deinit(void); static void speaker(bool active); static void headset(bool active); @@ -71,356 +70,357 @@ static int i2c_port; /**************************************************************************************** * init */ -static bool init(int i2c_port_num, int i2s_num, i2s_config_t *i2s_config) -{ - esp_err_t res = ESP_OK; +static bool init(int i2c_port_num, int i2s_num, i2s_config_t* i2s_config) { + esp_err_t res = ESP_OK; - i2c_port = i2c_port_num; + i2c_port = i2c_port_num; - // configure i2c - i2c_config_t i2c_config = { - .mode = I2C_MODE_MASTER, - .sda_io_num = 33, - .sda_pullup_en = GPIO_PULLUP_ENABLE, - .scl_io_num = 32, - .scl_pullup_en = GPIO_PULLUP_ENABLE, - .master.clk_speed = 250000, - }; + // configure i2c + i2c_config_t i2c_config = { + .mode = I2C_MODE_MASTER, + .sda_io_num = 33, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_io_num = 32, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master.clk_speed = 250000, + }; - i2c_param_config(i2c_port, &i2c_config); - i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false); + i2c_param_config(i2c_port, &i2c_config); + i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false); - res = i2c_read_reg(CHIP_AUDIO_RS); + res = i2c_read_reg(CHIP_AUDIO_RS); - if (!res) - { - ESP_LOGW(TAG, "No AC101 detected"); - i2c_driver_delete(i2c_port); - return 0; - } + if (!res) { + ESP_LOGW(TAG, "No AC101 detected"); + i2c_driver_delete(i2c_port); + return 0; + } - ESP_LOGI(TAG, "AC101 DAC using I2C sda:%u, scl:%u", i2c_config.sda_io_num, i2c_config.scl_io_num); + ESP_LOGI(TAG, "AC101 DAC using I2C sda:%u, scl:%u", i2c_config.sda_io_num, + i2c_config.scl_io_num); - res = i2c_write_reg(CHIP_AUDIO_RS, 0x123); - // huh? - vTaskDelay(100 / portTICK_PERIOD_MS); + res = i2c_write_reg(CHIP_AUDIO_RS, 0x123); + // huh? + vTaskDelay(100 / portTICK_PERIOD_MS); - // enable the PLL from BCLK source - i2c_write_reg(PLL_CTRL1, BIN(0000, 0001, 0100, 1111)); // F=1,M=1,PLL,INT=31 (medium) - i2c_write_reg(PLL_CTRL2, BIN(1000, 0110, 0000, 0000)); // PLL, F=96,N_i=1024-96,F=0,N_f=0*0.2; - // i2c_write_reg(PLL_CTRL2, BIN(1000,0011,1100,0000)); + // enable the PLL from BCLK source + i2c_write_reg(PLL_CTRL1, + BIN(0000, 0001, 0100, 1111)); // F=1,M=1,PLL,INT=31 (medium) + i2c_write_reg(PLL_CTRL2, BIN(1000, 0110, 0000, + 0000)); // PLL, F=96,N_i=1024-96,F=0,N_f=0*0.2; + // i2c_write_reg(PLL_CTRL2, BIN(1000,0011,1100,0000)); - // clocking system - i2c_write_reg(SYSCLK_CTRL, BIN(1010, 1010, 0000, 1000)); // PLLCLK, BCLK1, IS1CLK, PLL, SYSCLK - i2c_write_reg(MOD_CLK_ENA, BIN(1000, 0000, 0000, 1100)); // IS21, ADC, DAC - i2c_write_reg(MOD_RST_CTRL, BIN(1000, 0000, 0000, 1100)); // IS21, ADC, DAC - i2c_write_reg(I2S_SR_CTRL, BIN(0111, 0000, 0000, 0000)); // 44.1kHz + // clocking system + i2c_write_reg(SYSCLK_CTRL, BIN(1010, 1010, 0000, + 1000)); // PLLCLK, BCLK1, IS1CLK, PLL, SYSCLK + i2c_write_reg(MOD_CLK_ENA, BIN(1000, 0000, 0000, 1100)); // IS21, ADC, DAC + i2c_write_reg(MOD_RST_CTRL, BIN(1000, 0000, 0000, 1100)); // IS21, ADC, DAC + i2c_write_reg(I2S_SR_CTRL, BIN(0111, 0000, 0000, 0000)); // 44.1kHz - // analogue config - i2c_write_reg(I2S1LCK_CTRL, BIN(1000, 1000, 0101, 0000)); // Slave, BCLK=I2S/8,LRCK=32,16bits,I2Smode, Stereo - i2c_write_reg(I2S1_SDOUT_CTRL, BIN(1100, 0000, 0000, 0000)); // I2S1ADC (R&L) - i2c_write_reg(I2S1_SDIN_CTRL, BIN(1100, 0000, 0000, 0000)); // IS21DAC (R&L) - i2c_write_reg(I2S1_MXR_SRC, BIN(0010, 0010, 0000, 0000)); // ADCL, ADCR - i2c_write_reg(ADC_SRCBST_CTRL, BIN(0100, 0100, 0100, 0000)); // disable all boost (default) + // analogue config + i2c_write_reg(I2S1LCK_CTRL, + BIN(1000, 1000, 0101, + 0000)); // Slave, BCLK=I2S/8,LRCK=32,16bits,I2Smode, Stereo + i2c_write_reg(I2S1_SDOUT_CTRL, BIN(1100, 0000, 0000, 0000)); // I2S1ADC (R&L) + i2c_write_reg(I2S1_SDIN_CTRL, BIN(1100, 0000, 0000, 0000)); // IS21DAC (R&L) + i2c_write_reg(I2S1_MXR_SRC, BIN(0010, 0010, 0000, 0000)); // ADCL, ADCR + i2c_write_reg(ADC_SRCBST_CTRL, + BIN(0100, 0100, 0100, 0000)); // disable all boost (default) #if ENABLE_ADC - i2c_write_reg(ADC_SRC, BIN(0000, 0100, 0000, 1000)); // source=linein(R/L) - i2c_write_reg(ADC_DIG_CTRL, BIN(1000, 0000, 0000, 0000)); // enable digital ADC - i2c_write_reg(ADC_ANA_CTRL, BIN(1011, 1011, 0000, 0000)); // enable analogue R/L, 0dB + i2c_write_reg(ADC_SRC, BIN(0000, 0100, 0000, 1000)); // source=linein(R/L) + i2c_write_reg(ADC_DIG_CTRL, + BIN(1000, 0000, 0000, 0000)); // enable digital ADC + i2c_write_reg(ADC_ANA_CTRL, + BIN(1011, 1011, 0000, 0000)); // enable analogue R/L, 0dB #else - i2c_write_reg(ADC_SRC, BIN(0000, 0000, 0000, 0000)); // source=none - i2c_write_reg(ADC_DIG_CTRL, BIN(0000, 0000, 0000, 0000)); // disable digital ADC - i2c_write_reg(ADC_ANA_CTRL, BIN(0011, 0011, 0000, 0000)); // disable analogue R/L, 0dB + i2c_write_reg(ADC_SRC, BIN(0000, 0000, 0000, 0000)); // source=none + i2c_write_reg(ADC_DIG_CTRL, + BIN(0000, 0000, 0000, 0000)); // disable digital ADC + i2c_write_reg(ADC_ANA_CTRL, + BIN(0011, 0011, 0000, 0000)); // disable analogue R/L, 0dB #endif - //Path Configuration - i2c_write_reg(DAC_MXR_SRC, BIN(1000, 1000, 0000, 0000)); // DAC from I2S - i2c_write_reg(DAC_DIG_CTRL, BIN(1000, 0000, 0000, 0000)); // enable DAC - i2c_write_reg(OMIXER_DACA_CTRL, BIN(1111, 0000, 0000, 0000)); // enable DAC/Analogue (see note on offset removal and PA) - i2c_write_reg(OMIXER_DACA_CTRL, BIN(1111, 1111, 0000, 0000)); // this toggle is needed for headphone PA offset + //Path Configuration + i2c_write_reg(DAC_MXR_SRC, BIN(1000, 1000, 0000, 0000)); // DAC from I2S + i2c_write_reg(DAC_DIG_CTRL, BIN(1000, 0000, 0000, 0000)); // enable DAC + i2c_write_reg( + OMIXER_DACA_CTRL, + BIN(1111, 0000, 0000, + 0000)); // enable DAC/Analogue (see note on offset removal and PA) + i2c_write_reg(OMIXER_DACA_CTRL, + BIN(1111, 1111, 0000, + 0000)); // this toggle is needed for headphone PA offset #if ENABLE_ADC - i2c_write_reg(OMIXER_SR, BIN(0000, 0001, 0000, 0010)); // source=DAC(R/L) (are DACR and DACL really inverted in bitmap?) + i2c_write_reg( + OMIXER_SR, + BIN(0000, 0001, 0000, + 0010)); // source=DAC(R/L) (are DACR and DACL really inverted in bitmap?) #else - i2c_write_reg(OMIXER_SR, BIN(0000, 0101, 0000, 1010)); // source=DAC(R/L) and LINEIN(R/L) + i2c_write_reg(OMIXER_SR, BIN(0000, 0101, 0000, + 1010)); // source=DAC(R/L) and LINEIN(R/L) #endif - // configure I2S pins & install driver - i2s_pin_config_t i2s_pin_config = (i2s_pin_config_t){.bck_io_num = 27, .ws_io_num = 26, .data_out_num = 25, .data_in_num = -1}; - res |= i2s_driver_install(i2s_num, i2s_config, 0, NULL); - res |= i2s_set_pin(i2s_num, &i2s_pin_config); + // configure I2S pins & install driver + i2s_pin_config_t i2s_pin_config = (i2s_pin_config_t){ + .bck_io_num = 27, .ws_io_num = 26, .data_out_num = 25, .data_in_num = -1}; + res |= i2s_driver_install(i2s_num, i2s_config, 0, NULL); + res |= i2s_set_pin(i2s_num, &i2s_pin_config); - // enable earphone & speaker - i2c_write_reg(SPKOUT_CTRL, 0x0220); - i2c_write_reg(HPOUT_CTRL, 0xf801); + // enable earphone & speaker + i2c_write_reg(SPKOUT_CTRL, 0x0220); + i2c_write_reg(HPOUT_CTRL, 0xf801); - // set gain for speaker and earphone - ac101_set_spk_volume(70); - ac101_set_earph_volume(70); + // set gain for speaker and earphone + ac101_set_spk_volume(70); + ac101_set_earph_volume(70); - ESP_LOGI(TAG, "DAC using I2S bck:%d, ws:%d, do:%d", i2s_pin_config.bck_io_num, i2s_pin_config.ws_io_num, i2s_pin_config.data_out_num); + ESP_LOGI(TAG, "DAC using I2S bck:%d, ws:%d, do:%d", i2s_pin_config.bck_io_num, + i2s_pin_config.ws_io_num, i2s_pin_config.data_out_num); - return (res == ESP_OK); + return (res == ESP_OK); } /**************************************************************************************** * init */ -static void deinit(void) -{ - i2c_driver_delete(i2c_port); +static void deinit(void) { + i2c_driver_delete(i2c_port); } /**************************************************************************************** * change volume */ -static void volume(unsigned left, unsigned right) -{ - ac101_set_earph_volume(left); - // nothing at that point, volume is handled by backend +static void volume(unsigned left, unsigned right) { + ac101_set_earph_volume(left); + // nothing at that point, volume is handled by backend } /**************************************************************************************** * power */ -static void power(adac_power_e mode) -{ - switch (mode) - { - case ADAC_STANDBY: - case ADAC_OFF: - ac101_stop(); - break; - case ADAC_ON: - ac101_start(AC_MODULE_DAC); - break; - default: - ESP_LOGW(TAG, "unknown power command"); - break; - } +static void power(adac_power_e mode) { + switch (mode) { + case ADAC_STANDBY: + case ADAC_OFF: + ac101_stop(); + break; + case ADAC_ON: + ac101_start(AC_MODULE_DAC); + break; + default: + ESP_LOGW(TAG, "unknown power command"); + break; + } } /**************************************************************************************** * speaker */ -static void speaker(bool active) -{ - uint16_t value = i2c_read_reg(SPKOUT_CTRL); - if (active) - i2c_write_reg(SPKOUT_CTRL, value | SPKOUT_EN); - else - i2c_write_reg(SPKOUT_CTRL, value & ~SPKOUT_EN); +static void speaker(bool active) { + uint16_t value = i2c_read_reg(SPKOUT_CTRL); + if (active) + i2c_write_reg(SPKOUT_CTRL, value | SPKOUT_EN); + else + i2c_write_reg(SPKOUT_CTRL, value & ~SPKOUT_EN); } /**************************************************************************************** * headset */ -static void headset(bool active) -{ - // there might be aneed to toggle OMIXER_DACA_CTRL 11:8, not sure - uint16_t value = i2c_read_reg(HPOUT_CTRL); - if (active) - i2c_write_reg(HPOUT_CTRL, value | EAROUT_EN); - else - i2c_write_reg(HPOUT_CTRL, value & ~EAROUT_EN); +static void headset(bool active) { + // there might be aneed to toggle OMIXER_DACA_CTRL 11:8, not sure + uint16_t value = i2c_read_reg(HPOUT_CTRL); + if (active) + i2c_write_reg(HPOUT_CTRL, value | EAROUT_EN); + else + i2c_write_reg(HPOUT_CTRL, value & ~EAROUT_EN); } /**************************************************************************************** * */ -static esp_err_t i2c_write_reg(uint8_t reg, uint16_t val) -{ - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - esp_err_t ret = 0; - uint8_t send_buff[4]; - send_buff[0] = (AC101_ADDR << 1); - send_buff[1] = reg; - send_buff[2] = (val >> 8) & 0xff; - send_buff[3] = val & 0xff; - ret |= i2c_master_start(cmd); - ret |= i2c_master_write(cmd, send_buff, 4, ACK_CHECK_EN); - ret |= i2c_master_stop(cmd); - ret |= i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_PERIOD_MS); - i2c_cmd_link_delete(cmd); - return ret; +static esp_err_t i2c_write_reg(uint8_t reg, uint16_t val) { + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + esp_err_t ret = 0; + uint8_t send_buff[4]; + send_buff[0] = (AC101_ADDR << 1); + send_buff[1] = reg; + send_buff[2] = (val >> 8) & 0xff; + send_buff[3] = val & 0xff; + ret |= i2c_master_start(cmd); + ret |= i2c_master_write(cmd, send_buff, 4, ACK_CHECK_EN); + ret |= i2c_master_stop(cmd); + ret |= i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); + return ret; } /**************************************************************************************** * */ -static uint16_t i2c_read_reg(uint8_t reg) -{ - uint8_t data[2] = {0}; +static uint16_t i2c_read_reg(uint8_t reg) { + uint8_t data[2] = {0}; - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - i2c_master_start(cmd); - i2c_master_write_byte(cmd, (AC101_ADDR << 1) | WRITE_BIT, ACK_CHECK_EN); - i2c_master_write_byte(cmd, reg, ACK_CHECK_EN); - i2c_master_start(cmd); - i2c_master_write_byte(cmd, (AC101_ADDR << 1) | READ_BIT, ACK_CHECK_EN); //check or not - i2c_master_read(cmd, data, 2, ACK_VAL); - i2c_master_stop(cmd); - i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_PERIOD_MS); - i2c_cmd_link_delete(cmd); + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (AC101_ADDR << 1) | WRITE_BIT, ACK_CHECK_EN); + i2c_master_write_byte(cmd, reg, ACK_CHECK_EN); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (AC101_ADDR << 1) | READ_BIT, + ACK_CHECK_EN); //check or not + i2c_master_read(cmd, data, 2, ACK_VAL); + i2c_master_stop(cmd); + i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); - return (data[0] << 8) + data[1]; - ; + return (data[0] << 8) + data[1]; + ; } /**************************************************************************************** * */ -void set_sample_rate(int rate) -{ - if (rate == 8000) - rate = SAMPLE_RATE_8000; - else if (rate == 11025) - rate = SAMPLE_RATE_11052; - else if (rate == 12000) - rate = SAMPLE_RATE_12000; - else if (rate == 16000) - rate = SAMPLE_RATE_16000; - else if (rate == 22050) - rate = SAMPLE_RATE_22050; - else if (rate == 24000) - rate = SAMPLE_RATE_24000; - else if (rate == 32000) - rate = SAMPLE_RATE_32000; - else if (rate == 44100) - rate = SAMPLE_RATE_44100; - else if (rate == 48000) - rate = SAMPLE_RATE_48000; - else if (rate == 96000) - rate = SAMPLE_RATE_96000; - else if (rate == 192000) - rate = SAMPLE_RATE_192000; - else - { - ESP_LOGW(TAG, "Unknown sample rate %hu", rate); - rate = SAMPLE_RATE_44100; - } - i2c_write_reg(I2S_SR_CTRL, rate); +void set_sample_rate(int rate) { + if (rate == 8000) + rate = SAMPLE_RATE_8000; + else if (rate == 11025) + rate = SAMPLE_RATE_11052; + else if (rate == 12000) + rate = SAMPLE_RATE_12000; + else if (rate == 16000) + rate = SAMPLE_RATE_16000; + else if (rate == 22050) + rate = SAMPLE_RATE_22050; + else if (rate == 24000) + rate = SAMPLE_RATE_24000; + else if (rate == 32000) + rate = SAMPLE_RATE_32000; + else if (rate == 44100) + rate = SAMPLE_RATE_44100; + else if (rate == 48000) + rate = SAMPLE_RATE_48000; + else if (rate == 96000) + rate = SAMPLE_RATE_96000; + else if (rate == 192000) + rate = SAMPLE_RATE_192000; + else { + ESP_LOGW(TAG, "Unknown sample rate %hu", rate); + rate = SAMPLE_RATE_44100; + } + i2c_write_reg(I2S_SR_CTRL, rate); } /**************************************************************************************** * Get normalized (0..100) speaker volume */ -static int ac101_get_spk_volume(void) -{ - return ((i2c_read_reg(SPKOUT_CTRL) & 0x1f) * 100) / 0x1f; +static int ac101_get_spk_volume(void) { + return ((i2c_read_reg(SPKOUT_CTRL) & 0x1f) * 100) / 0x1f; } /**************************************************************************************** * Set normalized (0..100) volume */ -static void ac101_set_spk_volume(uint8_t volume) -{ - uint16_t value = min(volume, 100); - value = ((int)value * 0x1f) / 100; - value |= i2c_read_reg(SPKOUT_CTRL) & ~0x1f; - i2c_write_reg(SPKOUT_CTRL, value); +static void ac101_set_spk_volume(uint8_t volume) { + uint16_t value = min(volume, 100); + value = ((int)value * 0x1f) / 100; + value |= i2c_read_reg(SPKOUT_CTRL) & ~0x1f; + i2c_write_reg(SPKOUT_CTRL, value); } /**************************************************************************************** * Get normalized (0..100) earphone volume */ -static int ac101_get_earph_volume(void) -{ - return (((i2c_read_reg(HPOUT_CTRL) >> 4) & 0x3f) * 100) / 0x3f; +static int ac101_get_earph_volume(void) { + return (((i2c_read_reg(HPOUT_CTRL) >> 4) & 0x3f) * 100) / 0x3f; } /**************************************************************************************** * Set normalized (0..100) earphone volume */ -static void ac101_set_earph_volume(uint8_t volume) -{ - uint16_t value = min(volume, 255); - value = (((int)value * 0x3f) / 255) << 4; - value |= i2c_read_reg(HPOUT_CTRL) & ~(0x3f << 4); - i2c_write_reg(HPOUT_CTRL, value); +static void ac101_set_earph_volume(uint8_t volume) { + uint16_t value = min(volume, 255); + value = (((int)value * 0x3f) / 255) << 4; + value |= i2c_read_reg(HPOUT_CTRL) & ~(0x3f << 4); + i2c_write_reg(HPOUT_CTRL, value); } /**************************************************************************************** * */ -static void ac101_set_output_mixer_gain(ac_output_mixer_gain_t gain, ac_output_mixer_source_t source) -{ - uint16_t regval, temp, clrbit; - regval = i2c_read_reg(OMIXER_BST1_CTRL); - switch (source) - { - case SRC_MIC1: - temp = (gain & 0x7) << 6; - clrbit = ~(0x7 << 6); - break; - case SRC_MIC2: - temp = (gain & 0x7) << 3; - clrbit = ~(0x7 << 3); - break; - case SRC_LINEIN: - temp = (gain & 0x7); - clrbit = ~0x7; - break; - default: - return; - } - regval &= clrbit; - regval |= temp; - i2c_write_reg(OMIXER_BST1_CTRL, regval); +static void ac101_set_output_mixer_gain(ac_output_mixer_gain_t gain, + ac_output_mixer_source_t source) { + uint16_t regval, temp, clrbit; + regval = i2c_read_reg(OMIXER_BST1_CTRL); + switch (source) { + case SRC_MIC1: + temp = (gain & 0x7) << 6; + clrbit = ~(0x7 << 6); + break; + case SRC_MIC2: + temp = (gain & 0x7) << 3; + clrbit = ~(0x7 << 3); + break; + case SRC_LINEIN: + temp = (gain & 0x7); + clrbit = ~0x7; + break; + default: + return; + } + regval &= clrbit; + regval |= temp; + i2c_write_reg(OMIXER_BST1_CTRL, regval); } /**************************************************************************************** * */ -static void ac101_start(ac_module_t mode) -{ - if (mode == AC_MODULE_LINE) - { - i2c_write_reg(0x51, 0x0408); - i2c_write_reg(0x40, 0x8000); - i2c_write_reg(0x50, 0x3bc0); - } - if (mode == AC_MODULE_ADC || mode == AC_MODULE_ADC_DAC || mode == AC_MODULE_LINE) - { - // I2S1_SDOUT_CTRL - // i2c_write_reg(PLL_CTRL2, 0x8120); - i2c_write_reg(0x04, 0x800c); - i2c_write_reg(0x05, 0x800c); - // res |= i2c_write_reg(0x06, 0x3000); - } - if (mode == AC_MODULE_DAC || mode == AC_MODULE_ADC_DAC || mode == AC_MODULE_LINE) - { - uint16_t value = i2c_read_reg(PLL_CTRL2); - value |= 0x8000; - i2c_write_reg(PLL_CTRL2, value); - } +static void ac101_start(ac_module_t mode) { + if (mode == AC_MODULE_LINE) { + i2c_write_reg(0x51, 0x0408); + i2c_write_reg(0x40, 0x8000); + i2c_write_reg(0x50, 0x3bc0); + } + if (mode == AC_MODULE_ADC || mode == AC_MODULE_ADC_DAC || + mode == AC_MODULE_LINE) { + // I2S1_SDOUT_CTRL + // i2c_write_reg(PLL_CTRL2, 0x8120); + i2c_write_reg(0x04, 0x800c); + i2c_write_reg(0x05, 0x800c); + // res |= i2c_write_reg(0x06, 0x3000); + } + if (mode == AC_MODULE_DAC || mode == AC_MODULE_ADC_DAC || + mode == AC_MODULE_LINE) { + uint16_t value = i2c_read_reg(PLL_CTRL2); + value |= 0x8000; + i2c_write_reg(PLL_CTRL2, value); + } } /**************************************************************************************** * */ -static void ac101_stop(void) -{ - uint16_t value = i2c_read_reg(PLL_CTRL2); - value &= ~0x8000; - i2c_write_reg(PLL_CTRL2, value); +static void ac101_stop(void) { + uint16_t value = i2c_read_reg(PLL_CTRL2); + value &= ~0x8000; + i2c_write_reg(PLL_CTRL2, value); } /**************************************************************************************** * */ -static void ac101_deinit(void) -{ - i2c_write_reg(CHIP_AUDIO_RS, 0x123); //soft reset +static void ac101_deinit(void) { + i2c_write_reg(CHIP_AUDIO_RS, 0x123); //soft reset } /**************************************************************************************** * Don't know when this one is supposed to be called */ -static void ac101_i2s_config_clock(ac_i2s_clock_t *cfg) -{ - uint16_t regval = 0; - regval = i2c_read_reg(I2S1LCK_CTRL); - regval &= 0xe03f; - regval |= (cfg->bclk_div << 9); - regval |= (cfg->lclk_div << 6); - i2c_write_reg(I2S1LCK_CTRL, regval); +static void ac101_i2s_config_clock(ac_i2s_clock_t* cfg) { + uint16_t regval = 0; + regval = i2c_read_reg(I2S1LCK_CTRL); + regval &= 0xe03f; + regval |= (cfg->bclk_div << 9); + regval |= (cfg->lclk_div << 6); + i2c_write_reg(I2S1LCK_CTRL, regval); } \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/esp/es8311.c b/components/spotify/cspot/bell/main/audio-sinks/esp/es8311.c index 7bf5622c..50e3415f 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/esp/es8311.c +++ b/components/spotify/cspot/bell/main/audio-sinks/esp/es8311.c @@ -22,374 +22,434 @@ * */ +#include "es8311.h" #include #include "esp_log.h" -#include "es8311.h" // #include "board.h" /* ES8311 address * 0x32:CE=1;0x30:CE=0 */ -#define ES8311_ADDR 0x32 +#define ES8311_ADDR 0x32 -#define ES7243_ADDR 0x26 +#define ES7243_ADDR 0x26 /* * to define the clock soure of MCLK */ -#define FROM_MCLK_PIN 0 -#define FROM_SCLK_PIN 1 +#define FROM_MCLK_PIN 0 +#define FROM_SCLK_PIN 1 /* * to define work mode(master or slave) */ -#define MASTER_MODE 0 -#define SLAVE_MODE 1 +#define MASTER_MODE 0 +#define SLAVE_MODE 1 /* * to define serial digital audio format */ -#define I2S_FMT 0 -#define LEFT_JUSTIFIED_FMT 1 -#define DPS_PCM_A_FMT 2 -#define DPS_PCM_B_FMT 3 +#define I2S_FMT 0 +#define LEFT_JUSTIFIED_FMT 1 +#define DPS_PCM_A_FMT 2 +#define DPS_PCM_B_FMT 3 /* * to define resolution of PCM interface */ -#define LENGTH_16BIT 0 -#define LENGTH_24BIT 1 -#define LENGTH_32BIT 2 +#define LENGTH_16BIT 0 +#define LENGTH_24BIT 1 +#define LENGTH_32BIT 2 /* * codec private data */ -struct es8311_private { - bool dmic_enable; - bool mclkinv; - bool sclkinv; - uint8_t master_slave_mode; - uint8_t pcm_format; - uint8_t pcm_resolution; - uint8_t mclk_src; +struct es8311_private { + bool dmic_enable; + bool mclkinv; + bool sclkinv; + uint8_t master_slave_mode; + uint8_t pcm_format; + uint8_t pcm_resolution; + uint8_t mclk_src; }; -static struct es8311_private *es8311_priv; +static struct es8311_private* es8311_priv; /* * Clock coefficient structer */ struct _coeff_div { - uint32_t mclk; /* mclk frequency */ - uint32_t rate; /* sample rate */ - uint8_t prediv; /* the pre divider with range from 1 to 8 */ - uint8_t premulti; /* the pre multiplier with x1, x2, x4 and x8 selection */ - uint8_t adcdiv; /* adcclk divider */ - uint8_t dacdiv; /* dacclk divider */ - uint8_t fsmode; /* double speed or single speed, =0, ss, =1, ds */ - uint8_t lrck_h; /* adclrck divider and daclrck divider */ - uint8_t lrck_l; - uint8_t bclkdiv; /* sclk divider */ - uint8_t adcosr; /* adc osr */ - uint8_t dacosr; /* dac osr */ + uint32_t mclk; /* mclk frequency */ + uint32_t rate; /* sample rate */ + uint8_t prediv; /* the pre divider with range from 1 to 8 */ + uint8_t premulti; /* the pre multiplier with x1, x2, x4 and x8 selection */ + uint8_t adcdiv; /* adcclk divider */ + uint8_t dacdiv; /* dacclk divider */ + uint8_t fsmode; /* double speed or single speed, =0, ss, =1, ds */ + uint8_t lrck_h; /* adclrck divider and daclrck divider */ + uint8_t lrck_l; + uint8_t bclkdiv; /* sclk divider */ + uint8_t adcosr; /* adc osr */ + uint8_t dacosr; /* dac osr */ }; /* codec hifi mclk clock divider coefficients */ static const struct _coeff_div coeff_div[] = { //mclk rate prediv mult adcdiv dacdiv fsmode lrch lrcl bckdiv osr /* 8k */ - {12288000, 8000 , 0x06, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {18432000, 8000 , 0x03, 0x02, 0x03, 0x03, 0x00, 0x05, 0xff, 0x18, 0x10, 0x10}, - {16384000, 8000 , 0x08, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {8192000 , 8000 , 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {6144000 , 8000 , 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {4096000 , 8000 , 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {3072000 , 8000 , 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {2048000 , 8000 , 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1536000 , 8000 , 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1024000 , 8000 , 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {12288000, 8000, 0x06, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {18432000, 8000, 0x03, 0x02, 0x03, 0x03, 0x00, 0x05, 0xff, 0x18, 0x10, + 0x10}, + {16384000, 8000, 0x08, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {8192000, 8000, 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {6144000, 8000, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {4096000, 8000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {3072000, 8000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {2048000, 8000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {1536000, 8000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {1024000, 8000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, /* 11.025k */ - {11289600, 11025, 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {5644800 , 11025, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {2822400 , 11025, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1411200 , 11025, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {11289600, 11025, 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {5644800, 11025, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {2822400, 11025, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {1411200, 11025, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, /* 12k */ - {12288000, 12000, 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {6144000 , 12000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {3072000 , 12000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1536000 , 12000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {12288000, 12000, 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {6144000, 12000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {3072000, 12000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {1536000, 12000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, /* 16k */ - {12288000, 16000, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {18432000, 16000, 0x03, 0x02, 0x03, 0x03, 0x00, 0x02, 0xff, 0x0c, 0x10, 0x10}, - {16384000, 16000, 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {8192000 , 16000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {6144000 , 16000, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {4096000 , 16000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {3072000 , 16000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {2048000 , 16000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1536000 , 16000, 0x03, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1024000 , 16000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {12288000, 16000, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {18432000, 16000, 0x03, 0x02, 0x03, 0x03, 0x00, 0x02, 0xff, 0x0c, 0x10, + 0x10}, + {16384000, 16000, 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {8192000, 16000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {6144000, 16000, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {4096000, 16000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {3072000, 16000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {2048000, 16000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {1536000, 16000, 0x03, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {1024000, 16000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, /* 22.05k */ - {11289600, 22050, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {5644800 , 22050, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {2822400 , 22050, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1411200 , 22050, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {11289600, 22050, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {5644800, 22050, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {2822400, 22050, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {1411200, 22050, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, /* 24k */ - {12288000, 24000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {18432000, 24000, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {6144000 , 24000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {3072000 , 24000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1536000 , 24000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {12288000, 24000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {18432000, 24000, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {6144000, 24000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {3072000, 24000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {1536000, 24000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, /* 32k */ - {12288000, 32000, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {18432000, 32000, 0x03, 0x04, 0x03, 0x03, 0x00, 0x02, 0xff, 0x0c, 0x10, 0x10}, - {16384000, 32000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {8192000 , 32000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {6144000 , 32000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {4096000 , 32000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {3072000 , 32000, 0x03, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {2048000 , 32000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1536000 , 32000, 0x03, 0x08, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, 0x10}, - {1024000 , 32000, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {12288000, 32000, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {18432000, 32000, 0x03, 0x04, 0x03, 0x03, 0x00, 0x02, 0xff, 0x0c, 0x10, + 0x10}, + {16384000, 32000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {8192000, 32000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {6144000, 32000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {4096000, 32000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {3072000, 32000, 0x03, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {2048000, 32000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {1536000, 32000, 0x03, 0x08, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, + 0x10}, + {1024000, 32000, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, /* 44.1k */ - {11289600, 44100, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {5644800 , 44100, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {2822400 , 44100, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1411200 , 44100, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {11289600, 44100, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {5644800, 44100, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {2822400, 44100, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {1411200, 44100, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, /* 48k */ - {12288000, 48000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {18432000, 48000, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {6144000 , 48000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {3072000 , 48000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1536000 , 48000, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {12288000, 48000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {18432000, 48000, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {6144000, 48000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {3072000, 48000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {1536000, 48000, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, /* 64k */ - {12288000, 64000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {18432000, 64000, 0x03, 0x04, 0x03, 0x03, 0x01, 0x01, 0x7f, 0x06, 0x10, 0x10}, - {16384000, 64000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {8192000 , 64000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {6144000 , 64000, 0x01, 0x04, 0x03, 0x03, 0x01, 0x01, 0x7f, 0x06, 0x10, 0x10}, - {4096000 , 64000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {3072000 , 64000, 0x01, 0x08, 0x03, 0x03, 0x01, 0x01, 0x7f, 0x06, 0x10, 0x10}, - {2048000 , 64000, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1536000 , 64000, 0x01, 0x08, 0x01, 0x01, 0x01, 0x00, 0xbf, 0x03, 0x18, 0x18}, - {1024000 , 64000, 0x01, 0x08, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, 0x10}, + {12288000, 64000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {18432000, 64000, 0x03, 0x04, 0x03, 0x03, 0x01, 0x01, 0x7f, 0x06, 0x10, + 0x10}, + {16384000, 64000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {8192000, 64000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {6144000, 64000, 0x01, 0x04, 0x03, 0x03, 0x01, 0x01, 0x7f, 0x06, 0x10, + 0x10}, + {4096000, 64000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {3072000, 64000, 0x01, 0x08, 0x03, 0x03, 0x01, 0x01, 0x7f, 0x06, 0x10, + 0x10}, + {2048000, 64000, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {1536000, 64000, 0x01, 0x08, 0x01, 0x01, 0x01, 0x00, 0xbf, 0x03, 0x18, + 0x18}, + {1024000, 64000, 0x01, 0x08, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, + 0x10}, /* 88.2k */ - {11289600, 88200, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {5644800 , 88200, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {2822400 , 88200, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1411200 , 88200, 0x01, 0x08, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, 0x10}, + {11289600, 88200, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {5644800, 88200, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {2822400, 88200, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {1411200, 88200, 0x01, 0x08, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, + 0x10}, /* 96k */ - {12288000, 96000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {18432000, 96000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {6144000 , 96000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {3072000 , 96000, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1536000 , 96000, 0x01, 0x08, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, 0x10}, + {12288000, 96000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {18432000, 96000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {6144000, 96000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {3072000, 96000, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {1536000, 96000, 0x01, 0x08, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, + 0x10}, }; -static char *TAG = "DRV8311"; +static char* TAG = "DRV8311"; -#define ES_ASSERT(a, format, b, ...) \ - if ((a) != 0) { \ - ESP_LOGE(TAG, format, ##__VA_ARGS__); \ - return b;\ - } +#define ES_ASSERT(a, format, b, ...) \ + if ((a) != 0) { \ + ESP_LOGE(TAG, format, ##__VA_ARGS__); \ + return b; \ + } -static int Es8311WriteReg(uint8_t regAdd, uint8_t data) -{ - int res = 0; - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - res |= i2c_master_start(cmd); - res |= i2c_master_write_byte(cmd, ES8311_ADDR, 1 /*ACK_CHECK_EN*/); - res |= i2c_master_write_byte(cmd, regAdd, 1 /*ACK_CHECK_EN*/); - res |= i2c_master_write_byte(cmd, data, 1 /*ACK_CHECK_EN*/); - res |= i2c_master_stop(cmd); - res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); - i2c_cmd_link_delete(cmd); - ES_ASSERT(res, "Es8311 Write Reg error", -1); - return res; +static int Es8311WriteReg(uint8_t regAdd, uint8_t data) { + int res = 0; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + res |= i2c_master_start(cmd); + res |= i2c_master_write_byte(cmd, ES8311_ADDR, 1 /*ACK_CHECK_EN*/); + res |= i2c_master_write_byte(cmd, regAdd, 1 /*ACK_CHECK_EN*/); + res |= i2c_master_write_byte(cmd, data, 1 /*ACK_CHECK_EN*/); + res |= i2c_master_stop(cmd); + res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); + ES_ASSERT(res, "Es8311 Write Reg error", -1); + return res; } -int Es8311ReadReg(uint8_t regAdd) -{ - uint8_t data; - int res = 0; - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); +int Es8311ReadReg(uint8_t regAdd) { + uint8_t data; + int res = 0; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - res |= i2c_master_start(cmd); - res |= i2c_master_write_byte(cmd, ES8311_ADDR, 1 /*ACK_CHECK_EN*/); - res |= i2c_master_write_byte(cmd, regAdd, 1 /*ACK_CHECK_EN*/); - res |= i2c_master_stop(cmd); - res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); - i2c_cmd_link_delete(cmd); + res |= i2c_master_start(cmd); + res |= i2c_master_write_byte(cmd, ES8311_ADDR, 1 /*ACK_CHECK_EN*/); + res |= i2c_master_write_byte(cmd, regAdd, 1 /*ACK_CHECK_EN*/); + res |= i2c_master_stop(cmd); + res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); - cmd = i2c_cmd_link_create(); - res |= i2c_master_start(cmd); - res |= i2c_master_write_byte(cmd, ES8311_ADDR | 0x01, 1 /*ACK_CHECK_EN*/); - res |= i2c_master_read_byte(cmd, &data, 0x01 /*NACK_VAL*/); - res |= i2c_master_stop(cmd); - res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); - i2c_cmd_link_delete(cmd); + cmd = i2c_cmd_link_create(); + res |= i2c_master_start(cmd); + res |= i2c_master_write_byte(cmd, ES8311_ADDR | 0x01, 1 /*ACK_CHECK_EN*/); + res |= i2c_master_read_byte(cmd, &data, 0x01 /*NACK_VAL*/); + res |= i2c_master_stop(cmd); + res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); - ES_ASSERT(res, "Es8311 Read Reg error", -1); - return (int)data; + ES_ASSERT(res, "Es8311 Read Reg error", -1); + return (int)data; } -static int Es7243WriteReg(uint8_t regAdd, uint8_t data) -{ - int res = 0; - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - res |= i2c_master_start(cmd); - res |= i2c_master_write_byte(cmd, ES7243_ADDR, 1 /*ACK_CHECK_EN*/); - res |= i2c_master_write_byte(cmd, regAdd, 1 /*ACK_CHECK_EN*/); - res |= i2c_master_write_byte(cmd, data, 1 /*ACK_CHECK_EN*/); - res |= i2c_master_stop(cmd); - res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); - i2c_cmd_link_delete(cmd); - ES_ASSERT(res, "Es7243 Write Reg error", -1); - return res; +static int Es7243WriteReg(uint8_t regAdd, uint8_t data) { + int res = 0; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + res |= i2c_master_start(cmd); + res |= i2c_master_write_byte(cmd, ES7243_ADDR, 1 /*ACK_CHECK_EN*/); + res |= i2c_master_write_byte(cmd, regAdd, 1 /*ACK_CHECK_EN*/); + res |= i2c_master_write_byte(cmd, data, 1 /*ACK_CHECK_EN*/); + res |= i2c_master_stop(cmd); + res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); + ES_ASSERT(res, "Es7243 Write Reg error", -1); + return res; } +int Es7243ReadReg(uint8_t regAdd) { + uint8_t data; + int res = 0; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); -int Es7243ReadReg(uint8_t regAdd) -{ - uint8_t data; - int res = 0; - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + res |= i2c_master_start(cmd); + res |= i2c_master_write_byte(cmd, ES7243_ADDR, 1 /*ACK_CHECK_EN*/); + res |= i2c_master_write_byte(cmd, regAdd, 1 /*ACK_CHECK_EN*/); + res |= i2c_master_stop(cmd); + res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); - res |= i2c_master_start(cmd); - res |= i2c_master_write_byte(cmd, ES7243_ADDR, 1 /*ACK_CHECK_EN*/); - res |= i2c_master_write_byte(cmd, regAdd, 1 /*ACK_CHECK_EN*/); - res |= i2c_master_stop(cmd); - res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); - i2c_cmd_link_delete(cmd); + cmd = i2c_cmd_link_create(); + res |= i2c_master_start(cmd); + res |= i2c_master_write_byte(cmd, ES7243_ADDR | 0x01, 1 /*ACK_CHECK_EN*/); + res |= i2c_master_read_byte(cmd, &data, 0x01 /*NACK_VAL*/); + res |= i2c_master_stop(cmd); + res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); - cmd = i2c_cmd_link_create(); - res |= i2c_master_start(cmd); - res |= i2c_master_write_byte(cmd, ES7243_ADDR | 0x01, 1 /*ACK_CHECK_EN*/); - res |= i2c_master_read_byte(cmd, &data, 0x01 /*NACK_VAL*/); - res |= i2c_master_stop(cmd); - res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); - i2c_cmd_link_delete(cmd); - - ES_ASSERT(res, "Es7243 Read Reg error", -1); - return (int)data; + ES_ASSERT(res, "Es7243 Read Reg error", -1); + return (int)data; } -esp_err_t Es7243Init(void) -{ - esp_err_t ret = ESP_OK; - ret |= Es7243WriteReg(0x00, 0x01); - ret |= Es7243WriteReg(0x06, 0x00); - ret |= Es7243WriteReg(0x05, 0x1B); - ret |= Es7243WriteReg(0x01, 0x0C); - ret |= Es7243WriteReg(0x08, 0x43); - ret |= Es7243WriteReg(0x05, 0x13); - if (ret) { - ESP_LOGE(TAG, "Es7243 initialize failed!"); - return ESP_FAIL; - } - return ret; +esp_err_t Es7243Init(void) { + esp_err_t ret = ESP_OK; + ret |= Es7243WriteReg(0x00, 0x01); + ret |= Es7243WriteReg(0x06, 0x00); + ret |= Es7243WriteReg(0x05, 0x1B); + ret |= Es7243WriteReg(0x01, 0x0C); + ret |= Es7243WriteReg(0x08, 0x43); + ret |= Es7243WriteReg(0x05, 0x13); + if (ret) { + ESP_LOGE(TAG, "Es7243 initialize failed!"); + return ESP_FAIL; + } + return ret; } -static int I2cInit(i2c_config_t *conf, int i2cMasterPort) -{ - int res; - res = i2c_param_config(i2cMasterPort, conf); - res |= i2c_driver_install(i2cMasterPort, conf->mode, 0, 0, 0); - ES_ASSERT(res, "I2cInit error", -1); - return res; +static int I2cInit(i2c_config_t* conf, int i2cMasterPort) { + int res; + res = i2c_param_config(i2cMasterPort, conf); + res |= i2c_driver_install(i2cMasterPort, conf->mode, 0, 0, 0); + ES_ASSERT(res, "I2cInit error", -1); + return res; } /* * look for the coefficient in coeff_div[] table */ -static int get_coeff(uint32_t mclk, uint32_t rate) -{ - for (int i = 0; i < (sizeof(coeff_div) / sizeof(coeff_div[0])); i++) { - if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) - return i; - } - return -1; +static int get_coeff(uint32_t mclk, uint32_t rate) { + for (int i = 0; i < (sizeof(coeff_div) / sizeof(coeff_div[0])); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + return -1; } /* * set es8311 clock parameter and PCM/I2S interface */ -static void es8311_pcm_hw_params(uint32_t mclk, uint32_t lrck) -{ - int coeff; - uint8_t regv, datmp; - ESP_LOGI(TAG, "Enter into es8311_pcm_hw_params()\n"); - coeff = get_coeff(mclk, lrck); - if (coeff < 0) { - ESP_LOGE(TAG, "Unable to configure sample rate %dHz with %dHz MCLK\n", lrck, mclk); - return; - } +static void es8311_pcm_hw_params(uint32_t mclk, uint32_t lrck) { + int coeff; + uint8_t regv, datmp; + ESP_LOGI(TAG, "Enter into es8311_pcm_hw_params()\n"); + coeff = get_coeff(mclk, lrck); + if (coeff < 0) { + ESP_LOGE(TAG, "Unable to configure sample rate %dHz with %dHz MCLK\n", lrck, + mclk); + return; + } - /* + /* * set clock parammeters */ - if (coeff >= 0) { - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG02) & 0x07; - regv |= (coeff_div[coeff].prediv - 1) << 5; + if (coeff >= 0) { + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG02) & 0x07; + regv |= (coeff_div[coeff].prediv - 1) << 5; + datmp = 0; + switch (coeff_div[coeff].premulti) { + case 1: datmp = 0; - switch (coeff_div[coeff].premulti) { - case 1: - datmp = 0; - break; - case 2: - datmp = 1; - break; - case 4: - datmp = 2; - break; - case 8: - datmp = 3; - break; - default: - break; - } -#if CONFIG_ESP32_KORVO_V1_1_BOARD + break; + case 2: + datmp = 1; + break; + case 4: + datmp = 2; + break; + case 8: datmp = 3; -#endif - regv |= (datmp) << 3; - Es8311WriteReg(ES8311_CLK_MANAGER_REG02, regv); - - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG05) & 0x00; - regv |= (coeff_div[coeff].adcdiv - 1) << 4; - regv |= (coeff_div[coeff].dacdiv - 1) << 0; - Es8311WriteReg(ES8311_CLK_MANAGER_REG05, regv); - - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG03) & 0x80; - regv |= coeff_div[coeff].fsmode << 6; - regv |= coeff_div[coeff].adcosr << 0; - Es8311WriteReg(ES8311_CLK_MANAGER_REG03, regv); - - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG04) & 0x80; - regv |= coeff_div[coeff].dacosr << 0; - Es8311WriteReg(ES8311_CLK_MANAGER_REG04, regv); - - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG07) & 0xC0; - regv |= coeff_div[coeff].lrck_h << 0; - Es8311WriteReg(ES8311_CLK_MANAGER_REG07, regv); - - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG08) & 0x00; - regv |= coeff_div[coeff].lrck_l << 0; - Es8311WriteReg(ES8311_CLK_MANAGER_REG08, regv); - - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG06) & 0xE0; - if (coeff_div[coeff].bclkdiv < 19) { - regv |= (coeff_div[coeff].bclkdiv - 1) << 0; - } else { - regv |= (coeff_div[coeff].bclkdiv) << 0; - } - Es8311WriteReg(ES8311_CLK_MANAGER_REG06, regv); + break; + default: + break; } +#if CONFIG_ESP32_KORVO_V1_1_BOARD + datmp = 3; +#endif + regv |= (datmp) << 3; + Es8311WriteReg(ES8311_CLK_MANAGER_REG02, regv); + + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG05) & 0x00; + regv |= (coeff_div[coeff].adcdiv - 1) << 4; + regv |= (coeff_div[coeff].dacdiv - 1) << 0; + Es8311WriteReg(ES8311_CLK_MANAGER_REG05, regv); + + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG03) & 0x80; + regv |= coeff_div[coeff].fsmode << 6; + regv |= coeff_div[coeff].adcosr << 0; + Es8311WriteReg(ES8311_CLK_MANAGER_REG03, regv); + + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG04) & 0x80; + regv |= coeff_div[coeff].dacosr << 0; + Es8311WriteReg(ES8311_CLK_MANAGER_REG04, regv); + + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG07) & 0xC0; + regv |= coeff_div[coeff].lrck_h << 0; + Es8311WriteReg(ES8311_CLK_MANAGER_REG07, regv); + + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG08) & 0x00; + regv |= coeff_div[coeff].lrck_l << 0; + Es8311WriteReg(ES8311_CLK_MANAGER_REG08, regv); + + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG06) & 0xE0; + if (coeff_div[coeff].bclkdiv < 19) { + regv |= (coeff_div[coeff].bclkdiv - 1) << 0; + } else { + regv |= (coeff_div[coeff].bclkdiv) << 0; + } + Es8311WriteReg(ES8311_CLK_MANAGER_REG06, regv); + } } /* * set data and clock in tri-state mode @@ -412,20 +472,19 @@ static void es8311_pcm_hw_params(uint32_t mclk, uint32_t lrck) * if mute = 0, dac un-mute * if mute = 1, dac mute */ -static void es8311_mute(int mute) -{ - uint8_t regv; - ESP_LOGI(TAG, "Enter into es8311_mute(), mute = %d\n", mute); - regv = Es8311ReadReg(ES8311_DAC_REG31) & 0x9f; - if (mute) { - Es8311WriteReg(ES8311_SYSTEM_REG12, 0x02); - Es8311WriteReg(ES8311_DAC_REG31, regv | 0x60); - Es8311WriteReg(ES8311_DAC_REG32, 0x00); - Es8311WriteReg(ES8311_DAC_REG37, 0x08); - } else { - Es8311WriteReg(ES8311_DAC_REG31, regv); - Es8311WriteReg(ES8311_SYSTEM_REG12, 0x00); - } +static void es8311_mute(int mute) { + uint8_t regv; + ESP_LOGI(TAG, "Enter into es8311_mute(), mute = %d\n", mute); + regv = Es8311ReadReg(ES8311_DAC_REG31) & 0x9f; + if (mute) { + Es8311WriteReg(ES8311_SYSTEM_REG12, 0x02); + Es8311WriteReg(ES8311_DAC_REG31, regv | 0x60); + Es8311WriteReg(ES8311_DAC_REG32, 0x00); + Es8311WriteReg(ES8311_DAC_REG37, 0x08); + } else { + Es8311WriteReg(ES8311_DAC_REG31, regv); + Es8311WriteReg(ES8311_SYSTEM_REG12, 0x00); + } } /* * set es8311 into suspend mode @@ -450,135 +509,133 @@ static void es8311_mute(int mute) /* * initialize es8311 codec */ -static void es8311_init(uint32_t mclk_freq, uint32_t lrck_freq) -{ - int regv; - Es8311WriteReg(ES8311_GP_REG45, 0x00); - Es8311WriteReg(ES8311_CLK_MANAGER_REG01, 0x30); - Es8311WriteReg(ES8311_CLK_MANAGER_REG02, 0x00); - Es8311WriteReg(ES8311_CLK_MANAGER_REG03, 0x10); - Es8311WriteReg(ES8311_ADC_REG16, 0x24); - Es8311WriteReg(ES8311_CLK_MANAGER_REG04, 0x10); - Es8311WriteReg(ES8311_CLK_MANAGER_REG05, 0x00); - Es8311WriteReg(ES8311_SYSTEM_REG0B, 0x00); - Es8311WriteReg(ES8311_SYSTEM_REG0C, 0x00); - Es8311WriteReg(ES8311_SYSTEM_REG10, 0x1F); - Es8311WriteReg(ES8311_SYSTEM_REG11, 0x7F); - Es8311WriteReg(ES8311_RESET_REG00, 0x80); - /* +static void es8311_init(uint32_t mclk_freq, uint32_t lrck_freq) { + int regv; + Es8311WriteReg(ES8311_GP_REG45, 0x00); + Es8311WriteReg(ES8311_CLK_MANAGER_REG01, 0x30); + Es8311WriteReg(ES8311_CLK_MANAGER_REG02, 0x00); + Es8311WriteReg(ES8311_CLK_MANAGER_REG03, 0x10); + Es8311WriteReg(ES8311_ADC_REG16, 0x24); + Es8311WriteReg(ES8311_CLK_MANAGER_REG04, 0x10); + Es8311WriteReg(ES8311_CLK_MANAGER_REG05, 0x00); + Es8311WriteReg(ES8311_SYSTEM_REG0B, 0x00); + Es8311WriteReg(ES8311_SYSTEM_REG0C, 0x00); + Es8311WriteReg(ES8311_SYSTEM_REG10, 0x1F); + Es8311WriteReg(ES8311_SYSTEM_REG11, 0x7F); + Es8311WriteReg(ES8311_RESET_REG00, 0x80); + /* * Set Codec into Master or Slave mode */ - regv = Es8311ReadReg(ES8311_RESET_REG00); - /* set master/slave audio interface */ - switch (es8311_priv->master_slave_mode) { - case MASTER_MODE: /* MASTER MODE */ - ESP_LOGI(TAG, "ES8311 in Master mode\n"); - regv |= 0x40; - break; - case SLAVE_MODE: /* SLAVE MODE */ - ESP_LOGI(TAG, "ES8311 in Slave mode\n"); - regv &= 0xBF; - break; - default: - regv &= 0xBF; - } - Es8311WriteReg(ES8311_RESET_REG00, regv); - Es8311WriteReg(ES8311_SYSTEM_REG0D, 0x01); - Es8311WriteReg(ES8311_CLK_MANAGER_REG01, 0x3F); - /* + regv = Es8311ReadReg(ES8311_RESET_REG00); + /* set master/slave audio interface */ + switch (es8311_priv->master_slave_mode) { + case MASTER_MODE: /* MASTER MODE */ + ESP_LOGI(TAG, "ES8311 in Master mode\n"); + regv |= 0x40; + break; + case SLAVE_MODE: /* SLAVE MODE */ + ESP_LOGI(TAG, "ES8311 in Slave mode\n"); + regv &= 0xBF; + break; + default: + regv &= 0xBF; + } + Es8311WriteReg(ES8311_RESET_REG00, regv); + Es8311WriteReg(ES8311_SYSTEM_REG0D, 0x01); + Es8311WriteReg(ES8311_CLK_MANAGER_REG01, 0x3F); + /* * select clock source for internal mclk */ - switch (es8311_priv->mclk_src) { - case FROM_MCLK_PIN: - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG01); - regv &= 0x7F; - Es8311WriteReg(ES8311_CLK_MANAGER_REG01, regv); - break; - case FROM_SCLK_PIN: - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG01); - regv |= 0x80; - Es8311WriteReg(ES8311_CLK_MANAGER_REG01, regv); - break; - default: - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG01); - regv &= 0x7F; - Es8311WriteReg(ES8311_CLK_MANAGER_REG01, regv); - break; - } - es8311_pcm_hw_params(lrck_freq * 256, lrck_freq); + switch (es8311_priv->mclk_src) { + case FROM_MCLK_PIN: + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG01); + regv &= 0x7F; + Es8311WriteReg(ES8311_CLK_MANAGER_REG01, regv); + break; + case FROM_SCLK_PIN: + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG01); + regv |= 0x80; + Es8311WriteReg(ES8311_CLK_MANAGER_REG01, regv); + break; + default: + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG01); + regv &= 0x7F; + Es8311WriteReg(ES8311_CLK_MANAGER_REG01, regv); + break; + } + es8311_pcm_hw_params(lrck_freq * 256, lrck_freq); - /* + /* * mclk inverted or not */ - if (es8311_priv->mclkinv == true) { - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG01); - regv |= 0x40; - Es8311WriteReg(ES8311_CLK_MANAGER_REG01, regv); - } else { - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG01); - regv &= ~(0x40); - Es8311WriteReg(ES8311_CLK_MANAGER_REG01, regv); - } - /* + if (es8311_priv->mclkinv == true) { + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG01); + regv |= 0x40; + Es8311WriteReg(ES8311_CLK_MANAGER_REG01, regv); + } else { + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG01); + regv &= ~(0x40); + Es8311WriteReg(ES8311_CLK_MANAGER_REG01, regv); + } + /* * sclk inverted or not */ - if (es8311_priv->sclkinv == true) { - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG06); - regv |= 0x20; - Es8311WriteReg(ES8311_CLK_MANAGER_REG06, regv); - } else { - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG06); - regv &= ~(0x20); - Es8311WriteReg(ES8311_CLK_MANAGER_REG06, regv); - } - Es8311WriteReg(ES8311_SYSTEM_REG14, 0x1A); - /* + if (es8311_priv->sclkinv == true) { + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG06); + regv |= 0x20; + Es8311WriteReg(ES8311_CLK_MANAGER_REG06, regv); + } else { + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG06); + regv &= ~(0x20); + Es8311WriteReg(ES8311_CLK_MANAGER_REG06, regv); + } + Es8311WriteReg(ES8311_SYSTEM_REG14, 0x1A); + /* * pdm dmic enable or disable */ - if (es8311_priv->dmic_enable == true) { - regv = Es8311ReadReg(ES8311_SYSTEM_REG14); - regv |= 0x40; - Es8311WriteReg(ES8311_SYSTEM_REG14, regv); - } else { - regv = Es8311ReadReg(ES8311_SYSTEM_REG14); - regv &= ~(0x40); - Es8311WriteReg(ES8311_SYSTEM_REG14, regv); - } + if (es8311_priv->dmic_enable == true) { + regv = Es8311ReadReg(ES8311_SYSTEM_REG14); + regv |= 0x40; + Es8311WriteReg(ES8311_SYSTEM_REG14, regv); + } else { + regv = Es8311ReadReg(ES8311_SYSTEM_REG14); + regv &= ~(0x40); + Es8311WriteReg(ES8311_SYSTEM_REG14, regv); + } - Es8311WriteReg(ES8311_SYSTEM_REG13, 0x10); - Es8311WriteReg(ES8311_SYSTEM_REG0E, 0x02); - Es8311WriteReg(ES8311_ADC_REG15, 0x40); - Es8311WriteReg(ES8311_ADC_REG1B, 0x0A); - Es8311WriteReg(ES8311_ADC_REG1C, 0x6A); - Es8311WriteReg(ES8311_DAC_REG37, 0x48); - Es8311WriteReg(ES8311_GPIO_REG44, 0x08); - Es8311WriteReg(ES8311_DAC_REG32, 0xBF); + Es8311WriteReg(ES8311_SYSTEM_REG13, 0x10); + Es8311WriteReg(ES8311_SYSTEM_REG0E, 0x02); + Es8311WriteReg(ES8311_ADC_REG15, 0x40); + Es8311WriteReg(ES8311_ADC_REG1B, 0x0A); + Es8311WriteReg(ES8311_ADC_REG1C, 0x6A); + Es8311WriteReg(ES8311_DAC_REG37, 0x48); + Es8311WriteReg(ES8311_GPIO_REG44, 0x08); + Es8311WriteReg(ES8311_DAC_REG32, 0xBF); #ifdef CONFIG_USE_ES7243 - Es7243Init(); + Es7243Init(); #endif } /* * set codec private data and initialize codec */ -void es8311_Codec_Startup(uint32_t mclk_freq, uint32_t lrck_freq) -{ - ESP_LOGI(TAG, "Enter into es8311_Codec_Startup()\n"); - es8311_priv->dmic_enable = false; - es8311_priv->mclkinv = false; - es8311_priv->sclkinv = false; - es8311_priv->pcm_format = I2S_FMT; - es8311_priv->pcm_resolution = LENGTH_16BIT; - es8311_priv->master_slave_mode = SLAVE_MODE; +void es8311_Codec_Startup(uint32_t mclk_freq, uint32_t lrck_freq) { + ESP_LOGI(TAG, "Enter into es8311_Codec_Startup()\n"); + es8311_priv->dmic_enable = false; + es8311_priv->mclkinv = false; + es8311_priv->sclkinv = false; + es8311_priv->pcm_format = I2S_FMT; + es8311_priv->pcm_resolution = LENGTH_16BIT; + es8311_priv->master_slave_mode = SLAVE_MODE; #ifdef CONFIG_ESP32_KORVO_V1_1_BOARD - es8311_priv->mclk_src = FROM_SCLK_PIN; + es8311_priv->mclk_src = FROM_SCLK_PIN; #else - es8311_priv->mclk_src = FROM_MCLK_PIN; + es8311_priv->mclk_src = FROM_MCLK_PIN; #endif - es8311_init(mclk_freq, lrck_freq); + es8311_init(mclk_freq, lrck_freq); - ESP_LOGI(TAG, "Exit es8311_Codec_Startup()\n"); + ESP_LOGI(TAG, "Exit es8311_Codec_Startup()\n"); } // static int Es8311SetAdcDacVolume(int mode, int volume, int dot) @@ -596,201 +653,186 @@ void es8311_Codec_Startup(uint32_t mclk_freq, uint32_t lrck_freq) // return res; // } -esp_err_t Es8311GetRef(bool flag) -{ - esp_err_t ret = ESP_OK; - uint8_t regv = 0; - if (flag) { - regv = Es8311ReadReg(ES8311_GPIO_REG44); - regv |= 0x50; - ret |= Es8311WriteReg(ES8311_GPIO_REG44, regv); - } else { - ret |= Es8311WriteReg(ES8311_GPIO_REG44, 0x08); - } - return ret; +esp_err_t Es8311GetRef(bool flag) { + esp_err_t ret = ESP_OK; + uint8_t regv = 0; + if (flag) { + regv = Es8311ReadReg(ES8311_GPIO_REG44); + regv |= 0x50; + ret |= Es8311WriteReg(ES8311_GPIO_REG44, regv); + } else { + ret |= Es8311WriteReg(ES8311_GPIO_REG44, 0x08); + } + return ret; } -int Es8311Init(Es8311Config *cfg) -{ - es8311_priv = calloc(1, sizeof(struct es8311_private)); - I2cInit(&cfg->i2c_cfg, cfg->i2c_port_num); // ESP32 in master mode - es8311_Codec_Startup(11289600, 44100); - return 0; +int Es8311Init(Es8311Config* cfg) { + es8311_priv = calloc(1, sizeof(struct es8311_private)); + I2cInit(&cfg->i2c_cfg, cfg->i2c_port_num); // ESP32 in master mode + es8311_Codec_Startup(11289600, 44100); + return 0; } -void Es8311Uninit() -{ - Es8311WriteReg(ES8311_RESET_REG00, 0x3f); - free(es8311_priv); - es8311_priv = NULL; +void Es8311Uninit() { + Es8311WriteReg(ES8311_RESET_REG00, 0x3f); + free(es8311_priv); + es8311_priv = NULL; } -int Es8311ConfigFmt(ESCodecModule mode, ESCodecI2SFmt fmt) -{ - int res = 0; - uint8_t regAdc = 0, regDac = 0; - if (mode == ES_MODULE_ADC || mode == ES_MODULE_ADC_DAC) { - res |= Es8311WriteReg(ES8311_ADC_REG17, 0xBF); - } - if (mode == ES_MODULE_DAC || mode == ES_MODULE_ADC_DAC) { - res |= Es8311WriteReg(ES8311_SYSTEM_REG12, 0x00); - } - regAdc = Es8311ReadReg(ES8311_SDPIN_REG09); - regDac = Es8311ReadReg(ES8311_SDPOUT_REG0A); - switch (fmt) { - case ES_I2S_NORMAL: - ESP_LOGI(TAG, "ES8311 in I2S Format"); - regAdc &= ~0x03; - regDac &= ~0x03; - break; - case ES_I2S_LEFT: - case ES_I2S_RIGHT: - ESP_LOGI(TAG, "ES8311 in LJ Format"); - regAdc &= ~0x03; - regAdc |= 0x01; - regDac &= ~0x03; - regDac |= 0x01; - break; - case ES_I2S_DSP: - ESP_LOGI(TAG, "ES8311 in DSP Format"); - regAdc |= 0x03; - regDac |= 0x03; - break; - default: - ESP_LOGE(TAG, "Not Supported Format"); - break; - } - res |= Es8311WriteReg(ES8311_SDPIN_REG09, regAdc); - res |= Es8311WriteReg(ES8311_SDPOUT_REG0A, regDac); - return res; +int Es8311ConfigFmt(ESCodecModule mode, ESCodecI2SFmt fmt) { + int res = 0; + uint8_t regAdc = 0, regDac = 0; + if (mode == ES_MODULE_ADC || mode == ES_MODULE_ADC_DAC) { + res |= Es8311WriteReg(ES8311_ADC_REG17, 0xBF); + } + if (mode == ES_MODULE_DAC || mode == ES_MODULE_ADC_DAC) { + res |= Es8311WriteReg(ES8311_SYSTEM_REG12, 0x00); + } + regAdc = Es8311ReadReg(ES8311_SDPIN_REG09); + regDac = Es8311ReadReg(ES8311_SDPOUT_REG0A); + switch (fmt) { + case ES_I2S_NORMAL: + ESP_LOGI(TAG, "ES8311 in I2S Format"); + regAdc &= ~0x03; + regDac &= ~0x03; + break; + case ES_I2S_LEFT: + case ES_I2S_RIGHT: + ESP_LOGI(TAG, "ES8311 in LJ Format"); + regAdc &= ~0x03; + regAdc |= 0x01; + regDac &= ~0x03; + regDac |= 0x01; + break; + case ES_I2S_DSP: + ESP_LOGI(TAG, "ES8311 in DSP Format"); + regAdc |= 0x03; + regDac |= 0x03; + break; + default: + ESP_LOGE(TAG, "Not Supported Format"); + break; + } + res |= Es8311WriteReg(ES8311_SDPIN_REG09, regAdc); + res |= Es8311WriteReg(ES8311_SDPOUT_REG0A, regDac); + return res; } -int Es8311I2sConfigClock(ESCodecI2sClock cfg) -{ - int res = 0; - return res; +int Es8311I2sConfigClock(ESCodecI2sClock cfg) { + int res = 0; + return res; } -int Es8311SetBitsPerSample(ESCodecModule mode, BitsLength bitPerSample) -{ - int res = 0; - uint8_t reg = 0; - int bits = (int)bitPerSample; +int Es8311SetBitsPerSample(ESCodecModule mode, BitsLength bitPerSample) { + int res = 0; + uint8_t reg = 0; + int bits = (int)bitPerSample; - if (mode == ES_MODULE_ADC || mode == ES_MODULE_ADC_DAC) { - reg = Es8311ReadReg(ES8311_SDPIN_REG09); - reg = reg & 0xe3; - res |= Es8311WriteReg(ES8311_SDPIN_REG09, reg | (bits << 2)); - } - if (mode == ES_MODULE_DAC || mode == ES_MODULE_ADC_DAC) { - reg = Es8311ReadReg(ES8311_SDPOUT_REG0A); - reg = reg & 0xe3; - res |= Es8311WriteReg(ES8311_SDPOUT_REG0A, reg | (bits << 2)); - } - return res; + if (mode == ES_MODULE_ADC || mode == ES_MODULE_ADC_DAC) { + reg = Es8311ReadReg(ES8311_SDPIN_REG09); + reg = reg & 0xe3; + res |= Es8311WriteReg(ES8311_SDPIN_REG09, reg | (bits << 2)); + } + if (mode == ES_MODULE_DAC || mode == ES_MODULE_ADC_DAC) { + reg = Es8311ReadReg(ES8311_SDPOUT_REG0A); + reg = reg & 0xe3; + res |= Es8311WriteReg(ES8311_SDPOUT_REG0A, reg | (bits << 2)); + } + return res; } -int Es8311Start(ESCodecModule mode) -{ - int res = 0; - if (mode == ES_MODULE_ADC || mode == ES_MODULE_ADC_DAC) { - res |= Es8311WriteReg(ES8311_ADC_REG17, 0xBF); - } - if (mode == ES_MODULE_DAC || mode == ES_MODULE_ADC_DAC) { - res |= Es8311WriteReg(ES8311_SYSTEM_REG12, Es8311ReadReg(ES8311_SYSTEM_REG12) & 0xfd); - } - return res; +int Es8311Start(ESCodecModule mode) { + int res = 0; + if (mode == ES_MODULE_ADC || mode == ES_MODULE_ADC_DAC) { + res |= Es8311WriteReg(ES8311_ADC_REG17, 0xBF); + } + if (mode == ES_MODULE_DAC || mode == ES_MODULE_ADC_DAC) { + res |= Es8311WriteReg(ES8311_SYSTEM_REG12, + Es8311ReadReg(ES8311_SYSTEM_REG12) & 0xfd); + } + return res; } -int Es8311Stop(ESCodecModule mode) -{ - int res = 0; - if (mode == ES_MODULE_ADC || mode == ES_MODULE_ADC_DAC) { - res |= Es8311WriteReg(ES8311_ADC_REG17, 0x00); - } - if (mode == ES_MODULE_DAC || mode == ES_MODULE_ADC_DAC) { - res |= Es8311WriteReg(ES8311_SYSTEM_REG12, Es8311ReadReg(ES8311_SYSTEM_REG12) | 0x02); - } - return res; +int Es8311Stop(ESCodecModule mode) { + int res = 0; + if (mode == ES_MODULE_ADC || mode == ES_MODULE_ADC_DAC) { + res |= Es8311WriteReg(ES8311_ADC_REG17, 0x00); + } + if (mode == ES_MODULE_DAC || mode == ES_MODULE_ADC_DAC) { + res |= Es8311WriteReg(ES8311_SYSTEM_REG12, + Es8311ReadReg(ES8311_SYSTEM_REG12) | 0x02); + } + return res; } -int Es8311SetVoiceVolume(int volume) -{ - int res = 0; +int Es8311SetVoiceVolume(int volume) { + int res = 0; - if (volume == 0) { - volume = 1; - } + if (volume == 0) { + volume = 1; + } - Es8311WriteReg(ES8311_DAC_REG32, volume); - return res; + Es8311WriteReg(ES8311_DAC_REG32, volume); + return res; } -int Es8311GetVoiceVolume(int *volume) -{ - int res = ESP_OK; - int regv = Es8311ReadReg(ES8311_DAC_REG32); - if (regv == ESP_FAIL) { - *volume = 0; - res = ESP_FAIL; - } else { - *volume = regv * 100 / 256; - } - ESP_LOGI(TAG, "GET: res:%d, volume:%d\n", regv, *volume); - return res; +int Es8311GetVoiceVolume(int* volume) { + int res = ESP_OK; + int regv = Es8311ReadReg(ES8311_DAC_REG32); + if (regv == ESP_FAIL) { + *volume = 0; + res = ESP_FAIL; + } else { + *volume = regv * 100 / 256; + } + ESP_LOGI(TAG, "GET: res:%d, volume:%d\n", regv, *volume); + return res; } -int Es8311SetVoiceMute(int enable) -{ - int res = 0; - ESP_LOGI(TAG, "Es8311SetVoiceMute volume:%d\n", enable); - es8311_mute(enable); - return res; +int Es8311SetVoiceMute(int enable) { + int res = 0; + ESP_LOGI(TAG, "Es8311SetVoiceMute volume:%d\n", enable); + es8311_mute(enable); + return res; } -int Es8311GetVoiceMute(int *mute) -{ - int res = -1; - uint8_t reg = 0; - res = Es8311ReadReg(ES8311_DAC_REG31); - if (res != ESP_FAIL) { - reg = (res & 0x20) >> 5; - } - *mute = reg; - return res; +int Es8311GetVoiceMute(int* mute) { + int res = -1; + uint8_t reg = 0; + res = Es8311ReadReg(ES8311_DAC_REG31); + if (res != ESP_FAIL) { + reg = (res & 0x20) >> 5; + } + *mute = reg; + return res; } -int Es8311SetMicGain(MicGain gain) -{ - int res = 0; - uint8_t gain_n = Es8311ReadReg(ES8311_ADC_REG16) & 0x07; - gain_n |= gain / 6; - res = Es8311WriteReg(ES8311_ADC_REG16, gain_n); // MIC gain scale - return res; +int Es8311SetMicGain(MicGain gain) { + int res = 0; + uint8_t gain_n = Es8311ReadReg(ES8311_ADC_REG16) & 0x07; + gain_n |= gain / 6; + res = Es8311WriteReg(ES8311_ADC_REG16, gain_n); // MIC gain scale + return res; } -int Es8311ConfigAdcInput(AdcInput input) -{ - int res = 0; - return res; +int Es8311ConfigAdcInput(AdcInput input) { + int res = 0; + return res; } -int Es8311SetAdcVolume(uint8_t adc_vol) -{ - int res = 0; - res = Es8311WriteReg(ES8311_ADC_REG17, adc_vol); // MIC ADC Volume - return res; +int Es8311SetAdcVolume(uint8_t adc_vol) { + int res = 0; + res = Es8311WriteReg(ES8311_ADC_REG17, adc_vol); // MIC ADC Volume + return res; } -int ES8311WriteReg(uint8_t regAdd, uint8_t data) -{ - return Es8311WriteReg(regAdd, data); +int ES8311WriteReg(uint8_t regAdd, uint8_t data) { + return Es8311WriteReg(regAdd, data); } -void Es8311ReadAll() -{ - for (int i = 0; i < 0x4A; i++) { - uint8_t reg = Es8311ReadReg(i); - // ets_printf("REG:%02x, %02x\n", reg, i); - } +void Es8311ReadAll() { + for (int i = 0; i < 0x4A; i++) { + uint8_t reg = Es8311ReadReg(i); + // ets_printf("REG:%02x, %02x\n", reg, i); + } } \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/AudioSink.h b/components/spotify/cspot/bell/main/audio-sinks/include/AudioSink.h index 96432513..65b4fd9c 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/AudioSink.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/AudioSink.h @@ -5,21 +5,23 @@ #include #include -class AudioSink -{ - public: - AudioSink() {} - virtual ~AudioSink() {} - virtual void feedPCMFrames(const uint8_t *buffer, size_t bytes) = 0; - virtual void volumeChanged(uint16_t volume) {} - // Return false if the sink doesn't support reconfiguration. - virtual bool setParams(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) { return false; } - // Deprecated. Implement/use setParams() instead. - virtual inline bool setRate(uint16_t sampleRate) { - return setParams(sampleRate, 2, 16); - } - bool softwareVolumeControl = true; - bool usign = false; +class AudioSink { + public: + AudioSink() {} + virtual ~AudioSink() {} + virtual void feedPCMFrames(const uint8_t* buffer, size_t bytes) = 0; + virtual void volumeChanged(uint16_t volume) {} + // Return false if the sink doesn't support reconfiguration. + virtual bool setParams(uint32_t sampleRate, uint8_t channelCount, + uint8_t bitDepth) { + return false; + } + // Deprecated. Implement/use setParams() instead. + virtual inline bool setRate(uint16_t sampleRate) { + return setParams(sampleRate, 2, 16); + } + bool softwareVolumeControl = true; + bool usign = false; }; #endif \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/AC101AudioSink.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/AC101AudioSink.h index 98c1c0ff..695bcf8a 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/AC101AudioSink.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/AC101AudioSink.h @@ -1,26 +1,26 @@ #ifndef AC101AUDIOSINK_H #define AC101AUDIOSINK_H -#include -#include -#include "BufferedAudioSink.h" #include #include -#include #include -#include "esp_err.h" -#include "esp_log.h" +#include +#include +#include +#include "BufferedAudioSink.h" #include "ac101.h" #include "adac.h" +#include "esp_err.h" +#include "esp_log.h" -class AC101AudioSink : public BufferedAudioSink -{ -public: - AC101AudioSink(); - ~AC101AudioSink(); - void volumeChanged(uint16_t volume); -private: - adac_s *dac; +class AC101AudioSink : public BufferedAudioSink { + public: + AC101AudioSink(); + ~AC101AudioSink(); + void volumeChanged(uint16_t volume); + + private: + adac_s* dac; }; #endif \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/BufferedAudioSink.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/BufferedAudioSink.h index eb63caf9..f5dc2319 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/BufferedAudioSink.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/BufferedAudioSink.h @@ -1,25 +1,27 @@ #ifndef BUFFEREDAUDIOSINK_H #define BUFFEREDAUDIOSINK_H -#include -#include -#include "AudioSink.h" #include #include -#include #include +#include +#include +#include +#include "AudioSink.h" #include "esp_err.h" #include "esp_log.h" -class BufferedAudioSink : public AudioSink -{ -public: - void feedPCMFrames(const uint8_t *buffer, size_t bytes) override; - bool setParams(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) override; -protected: - void startI2sFeed(size_t buf_size = 4096 * 8); - void feedPCMFramesInternal(const void *pvItem, size_t xItemSize); -private: +class BufferedAudioSink : public AudioSink { + public: + void feedPCMFrames(const uint8_t* buffer, size_t bytes) override; + bool setParams(uint32_t sampleRate, uint8_t channelCount, + uint8_t bitDepth) override; + + protected: + void startI2sFeed(size_t buf_size = 4096 * 8); + void feedPCMFramesInternal(const void* pvItem, size_t xItemSize); + + private: }; #endif \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/ES8311AudioSink.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/ES8311AudioSink.h index 697e81b7..6e756ab9 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/ES8311AudioSink.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/ES8311AudioSink.h @@ -1,28 +1,28 @@ #ifndef ES8311AUDIOSINK_H #define ES8311AUDIOSINK_H -#include "driver/i2s.h" -#include -#include -#include "BufferedAudioSink.h" #include #include +#include +#include +#include +#include +#include "BufferedAudioSink.h" #include "driver/gpio.h" #include "driver/i2c.h" -#include -#include +#include "driver/i2s.h" #include "esp_err.h" #include "esp_log.h" -class ES8311AudioSink : public BufferedAudioSink -{ -public: - ES8311AudioSink(); - ~ES8311AudioSink(); - void writeReg(uint8_t reg_add, uint8_t data); - void volumeChanged(uint16_t volume); - void setSampleRate(uint32_t sampleRate); -private: +class ES8311AudioSink : public BufferedAudioSink { + public: + ES8311AudioSink(); + ~ES8311AudioSink(); + void writeReg(uint8_t reg_add, uint8_t data); + void volumeChanged(uint16_t volume); + void setSampleRate(uint32_t sampleRate); + + private: }; #endif \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/ES8388AudioSink.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/ES8388AudioSink.h index 77c917c8..92743647 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/ES8388AudioSink.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/ES8388AudioSink.h @@ -1,23 +1,22 @@ #ifndef ES8388AUDIOSINK_H #define ES8388AUDIOSINK_H -#include "driver/i2s.h" #include -#include -#include -#include "BufferedAudioSink.h" +#include #include #include -#include -#include #include +#include +#include +#include +#include "BufferedAudioSink.h" +#include "driver/i2s.h" #include "esp_err.h" #include "esp_log.h" - #define ES8388_ADDR 0x20 -#define ACK_CHECK_EN 0x1 +#define ACK_CHECK_EN 0x1 /* ES8388 register */ #define ES8388_CONTROL1 0x00 @@ -78,28 +77,27 @@ #define ES8388_DACCONTROL29 0x33 #define ES8388_DACCONTROL30 0x34 -class ES8388AudioSink : public BufferedAudioSink -{ -public: - ES8388AudioSink(); - ~ES8388AudioSink(); - - bool begin(int sda = -1, int scl = -1, uint32_t frequency = 400000U); +class ES8388AudioSink : public BufferedAudioSink { + public: + ES8388AudioSink(); + ~ES8388AudioSink(); - enum ES8388_OUT - { - ES_MAIN, // this is the DAC output volume (both outputs) - ES_OUT1, // this is the additional gain for OUT1 - ES_OUT2 // this is the additional gain for OUT2 - }; + bool begin(int sda = -1, int scl = -1, uint32_t frequency = 400000U); - void mute(const ES8388_OUT out, const bool muted); - void volume(const ES8388_OUT out, const uint8_t vol); + enum ES8388_OUT { + ES_MAIN, // this is the DAC output volume (both outputs) + ES_OUT1, // this is the additional gain for OUT1 + ES_OUT2 // this is the additional gain for OUT2 + }; - void writeReg(uint8_t reg_add, uint8_t data); -private: - i2c_config_t i2c_config; - i2c_port_t i2c_port = 0; + void mute(const ES8388_OUT out, const bool muted); + void volume(const ES8388_OUT out, const uint8_t vol); + + void writeReg(uint8_t reg_add, uint8_t data); + + private: + i2c_config_t i2c_config; + i2c_port_t i2c_port = 0; }; #endif \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/ES9018AudioSink.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/ES9018AudioSink.h index 986b53fa..a0ec5317 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/ES9018AudioSink.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/ES9018AudioSink.h @@ -1,22 +1,22 @@ #ifndef ES9018AUDIOSINK_H #define ES9018AUDIOSINK_H -#include -#include -#include "BufferedAudioSink.h" #include #include -#include #include +#include +#include +#include +#include "BufferedAudioSink.h" #include "esp_err.h" #include "esp_log.h" -class ES9018AudioSink : public BufferedAudioSink -{ -public: - ES9018AudioSink(); - ~ES9018AudioSink(); -private: +class ES9018AudioSink : public BufferedAudioSink { + public: + ES9018AudioSink(); + ~ES9018AudioSink(); + + private: }; #endif \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/InternalAudioSink.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/InternalAudioSink.h index 9c98523c..c4622bf4 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/InternalAudioSink.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/InternalAudioSink.h @@ -1,22 +1,22 @@ #ifndef INTERNALAUDIOSINK_H #define INTERNALAUDIOSINK_H -#include -#include -#include "BufferedAudioSink.h" #include #include -#include #include +#include +#include +#include +#include "BufferedAudioSink.h" #include "esp_err.h" #include "esp_log.h" -class InternalAudioSink : public BufferedAudioSink -{ -public: - InternalAudioSink(); - ~InternalAudioSink(); -private: +class InternalAudioSink : public BufferedAudioSink { + public: + InternalAudioSink(); + ~InternalAudioSink(); + + private: }; #endif diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/PCM5102AudioSink.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/PCM5102AudioSink.h index 77096a23..4f751064 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/PCM5102AudioSink.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/PCM5102AudioSink.h @@ -1,22 +1,22 @@ #ifndef PCM5102AUDIOSINK_H #define PCM5102AUDIOSINK_H -#include -#include -#include "BufferedAudioSink.h" #include #include -#include #include +#include +#include +#include +#include "BufferedAudioSink.h" #include "esp_err.h" #include "esp_log.h" -class PCM5102AudioSink : public BufferedAudioSink -{ -public: - PCM5102AudioSink(); - ~PCM5102AudioSink(); -private: +class PCM5102AudioSink : public BufferedAudioSink { + public: + PCM5102AudioSink(); + ~PCM5102AudioSink(); + + private: }; #endif \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/SPDIFAudioSink.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/SPDIFAudioSink.h index 91ca110b..c92d983c 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/SPDIFAudioSink.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/SPDIFAudioSink.h @@ -1,26 +1,28 @@ #ifndef SPDIFAUDIOSINK_H #define SPDIFAUDIOSINK_H -#include -#include -#include "BufferedAudioSink.h" #include #include -#include #include +#include +#include +#include +#include "BufferedAudioSink.h" #include "esp_err.h" #include "esp_log.h" -class SPDIFAudioSink : public BufferedAudioSink -{ -private: - uint8_t spdifPin; -public: - explicit SPDIFAudioSink(uint8_t spdifPin); - ~SPDIFAudioSink() override; - void feedPCMFrames(const uint8_t *buffer, size_t bytes) override; - bool setParams(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) override; -private: +class SPDIFAudioSink : public BufferedAudioSink { + private: + uint8_t spdifPin; + + public: + explicit SPDIFAudioSink(uint8_t spdifPin); + ~SPDIFAudioSink() override; + void feedPCMFrames(const uint8_t* buffer, size_t bytes) override; + bool setParams(uint32_t sampleRate, uint8_t channelCount, + uint8_t bitDepth) override; + + private: }; #endif \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/TAS5711AudioSink.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/TAS5711AudioSink.h index 23a705d8..b4319c3a 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/TAS5711AudioSink.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/TAS5711AudioSink.h @@ -1,30 +1,28 @@ #ifndef TAS5711AUDIOSINK_H #define TAS5711AUDIOSINK_H - -#include "driver/i2s.h" #include -#include -#include -#include "BufferedAudioSink.h" #include #include -#include #include +#include +#include +#include +#include "BufferedAudioSink.h" +#include "driver/i2s.h" #include "esp_err.h" #include "esp_log.h" -class TAS5711AudioSink : public BufferedAudioSink -{ -public: - TAS5711AudioSink(); - ~TAS5711AudioSink(); +class TAS5711AudioSink : public BufferedAudioSink { + public: + TAS5711AudioSink(); + ~TAS5711AudioSink(); + void writeReg(uint8_t reg, uint8_t value); - void writeReg(uint8_t reg, uint8_t value); -private: - i2c_config_t i2c_config; - i2c_port_t i2c_port = 0; + private: + i2c_config_t i2c_config; + i2c_port_t i2c_port = 0; }; #endif \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/ac101.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/ac101.h index 39c17379..2ef3900b 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/ac101.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/ac101.h @@ -21,156 +21,156 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * */ - + #ifndef __AC101_H__ #define __AC101_H__ #include "esp_types.h" -#define AC101_ADDR 0x1a /*!< Device address*/ +#define AC101_ADDR 0x1a /*!< Device address*/ -#define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */ -#define READ_BIT I2C_MASTER_READ /*!< I2C master read */ -#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/ -#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */ -#define ACK_VAL 0x0 /*!< I2C ack value */ -#define NACK_VAL 0x1 /*!< I2C nack value */ +#define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */ +#define READ_BIT I2C_MASTER_READ /*!< I2C master read */ +#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/ +#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */ +#define ACK_VAL 0x0 /*!< I2C ack value */ +#define NACK_VAL 0x1 /*!< I2C nack value */ -#define CHIP_AUDIO_RS 0x00 -#define PLL_CTRL1 0x01 -#define PLL_CTRL2 0x02 -#define SYSCLK_CTRL 0x03 -#define MOD_CLK_ENA 0x04 -#define MOD_RST_CTRL 0x05 -#define I2S_SR_CTRL 0x06 -#define I2S1LCK_CTRL 0x10 -#define I2S1_SDOUT_CTRL 0x11 -#define I2S1_SDIN_CTRL 0x12 -#define I2S1_MXR_SRC 0x13 -#define I2S1_VOL_CTRL1 0x14 -#define I2S1_VOL_CTRL2 0x15 -#define I2S1_VOL_CTRL3 0x16 -#define I2S1_VOL_CTRL4 0x17 -#define I2S1_MXR_GAIN 0x18 -#define ADC_DIG_CTRL 0x40 -#define ADC_VOL_CTRL 0x41 -#define HMIC_CTRL1 0x44 -#define HMIC_CTRL2 0x45 -#define HMIC_STATUS 0x46 -#define DAC_DIG_CTRL 0x48 -#define DAC_VOL_CTRL 0x49 -#define DAC_MXR_SRC 0x4c -#define DAC_MXR_GAIN 0x4d -#define ADC_ANA_CTRL 0x50 -#define ADC_SRC 0x51 -#define ADC_SRCBST_CTRL 0x52 -#define OMIXER_DACA_CTRL 0x53 -#define OMIXER_SR 0x54 -#define OMIXER_BST1_CTRL 0x55 -#define HPOUT_CTRL 0x56 -#define SPKOUT_CTRL 0x58 -#define AC_DAC_DAPCTRL 0xa0 -#define AC_DAC_DAPHHPFC 0xa1 -#define AC_DAC_DAPLHPFC 0xa2 -#define AC_DAC_DAPLHAVC 0xa3 -#define AC_DAC_DAPLLAVC 0xa4 -#define AC_DAC_DAPRHAVC 0xa5 -#define AC_DAC_DAPRLAVC 0xa6 -#define AC_DAC_DAPHGDEC 0xa7 -#define AC_DAC_DAPLGDEC 0xa8 -#define AC_DAC_DAPHGATC 0xa9 -#define AC_DAC_DAPLGATC 0xaa -#define AC_DAC_DAPHETHD 0xab -#define AC_DAC_DAPLETHD 0xac -#define AC_DAC_DAPHGKPA 0xad -#define AC_DAC_DAPLGKPA 0xae -#define AC_DAC_DAPHGOPA 0xaf -#define AC_DAC_DAPLGOPA 0xb0 -#define AC_DAC_DAPOPT 0xb1 -#define DAC_DAP_ENA 0xb5 +#define CHIP_AUDIO_RS 0x00 +#define PLL_CTRL1 0x01 +#define PLL_CTRL2 0x02 +#define SYSCLK_CTRL 0x03 +#define MOD_CLK_ENA 0x04 +#define MOD_RST_CTRL 0x05 +#define I2S_SR_CTRL 0x06 +#define I2S1LCK_CTRL 0x10 +#define I2S1_SDOUT_CTRL 0x11 +#define I2S1_SDIN_CTRL 0x12 +#define I2S1_MXR_SRC 0x13 +#define I2S1_VOL_CTRL1 0x14 +#define I2S1_VOL_CTRL2 0x15 +#define I2S1_VOL_CTRL3 0x16 +#define I2S1_VOL_CTRL4 0x17 +#define I2S1_MXR_GAIN 0x18 +#define ADC_DIG_CTRL 0x40 +#define ADC_VOL_CTRL 0x41 +#define HMIC_CTRL1 0x44 +#define HMIC_CTRL2 0x45 +#define HMIC_STATUS 0x46 +#define DAC_DIG_CTRL 0x48 +#define DAC_VOL_CTRL 0x49 +#define DAC_MXR_SRC 0x4c +#define DAC_MXR_GAIN 0x4d +#define ADC_ANA_CTRL 0x50 +#define ADC_SRC 0x51 +#define ADC_SRCBST_CTRL 0x52 +#define OMIXER_DACA_CTRL 0x53 +#define OMIXER_SR 0x54 +#define OMIXER_BST1_CTRL 0x55 +#define HPOUT_CTRL 0x56 +#define SPKOUT_CTRL 0x58 +#define AC_DAC_DAPCTRL 0xa0 +#define AC_DAC_DAPHHPFC 0xa1 +#define AC_DAC_DAPLHPFC 0xa2 +#define AC_DAC_DAPLHAVC 0xa3 +#define AC_DAC_DAPLLAVC 0xa4 +#define AC_DAC_DAPRHAVC 0xa5 +#define AC_DAC_DAPRLAVC 0xa6 +#define AC_DAC_DAPHGDEC 0xa7 +#define AC_DAC_DAPLGDEC 0xa8 +#define AC_DAC_DAPHGATC 0xa9 +#define AC_DAC_DAPLGATC 0xaa +#define AC_DAC_DAPHETHD 0xab +#define AC_DAC_DAPLETHD 0xac +#define AC_DAC_DAPHGKPA 0xad +#define AC_DAC_DAPLGKPA 0xae +#define AC_DAC_DAPHGOPA 0xaf +#define AC_DAC_DAPLGOPA 0xb0 +#define AC_DAC_DAPOPT 0xb1 +#define DAC_DAP_ENA 0xb5 -typedef enum{ - SAMPLE_RATE_8000 = 0x0000, - SAMPLE_RATE_11052 = 0x1000, - SAMPLE_RATE_12000 = 0x2000, - SAMPLE_RATE_16000 = 0x3000, - SAMPLE_RATE_22050 = 0x4000, - SAMPLE_RATE_24000 = 0x5000, - SAMPLE_RATE_32000 = 0x6000, - SAMPLE_RATE_44100 = 0x7000, - SAMPLE_RATE_48000 = 0x8000, - SAMPLE_RATE_96000 = 0x9000, - SAMPLE_RATE_192000 = 0xa000, +typedef enum { + SAMPLE_RATE_8000 = 0x0000, + SAMPLE_RATE_11052 = 0x1000, + SAMPLE_RATE_12000 = 0x2000, + SAMPLE_RATE_16000 = 0x3000, + SAMPLE_RATE_22050 = 0x4000, + SAMPLE_RATE_24000 = 0x5000, + SAMPLE_RATE_32000 = 0x6000, + SAMPLE_RATE_44100 = 0x7000, + SAMPLE_RATE_48000 = 0x8000, + SAMPLE_RATE_96000 = 0x9000, + SAMPLE_RATE_192000 = 0xa000, } ac_adda_fs_i2s1_t; -typedef enum{ - BCLK_DIV_1 = 0x0, - BCLK_DIV_2 = 0x1, - BCLK_DIV_4 = 0x2, - BCLK_DIV_6 = 0x3, - BCLK_DIV_8 = 0x4, - BCLK_DIV_12 = 0x5, - BCLK_DIV_16 = 0x6, - BCLK_DIV_24 = 0x7, - BCLK_DIV_32 = 0x8, - BCLK_DIV_48 = 0x9, - BCLK_DIV_64 = 0xa, - BCLK_DIV_96 = 0xb, - BCLK_DIV_128 = 0xc, - BCLK_DIV_192 = 0xd, +typedef enum { + BCLK_DIV_1 = 0x0, + BCLK_DIV_2 = 0x1, + BCLK_DIV_4 = 0x2, + BCLK_DIV_6 = 0x3, + BCLK_DIV_8 = 0x4, + BCLK_DIV_12 = 0x5, + BCLK_DIV_16 = 0x6, + BCLK_DIV_24 = 0x7, + BCLK_DIV_32 = 0x8, + BCLK_DIV_48 = 0x9, + BCLK_DIV_64 = 0xa, + BCLK_DIV_96 = 0xb, + BCLK_DIV_128 = 0xc, + BCLK_DIV_192 = 0xd, } ac_i2s1_bclk_div_t; -typedef enum{ - LRCK_DIV_16 =0x0, - LRCK_DIV_32 =0x1, - LRCK_DIV_64 =0x2, - LRCK_DIV_128 =0x3, - LRCK_DIV_256 =0x4, +typedef enum { + LRCK_DIV_16 = 0x0, + LRCK_DIV_32 = 0x1, + LRCK_DIV_64 = 0x2, + LRCK_DIV_128 = 0x3, + LRCK_DIV_256 = 0x4, } ac_i2s1_lrck_div_t; typedef enum { - BIT_LENGTH_8_BITS = 0x00, - BIT_LENGTH_16_BITS = 0x01, - BIT_LENGTH_20_BITS = 0x02, - BIT_LENGTH_24_BITS = 0x03, + BIT_LENGTH_8_BITS = 0x00, + BIT_LENGTH_16_BITS = 0x01, + BIT_LENGTH_20_BITS = 0x02, + BIT_LENGTH_24_BITS = 0x03, } ac_bits_length_t; typedef enum { - AC_MODE_MIN = -1, - AC_MODE_SLAVE = 0x00, - AC_MODE_MASTER = 0x01, - AC_MODE_MAX, + AC_MODE_MIN = -1, + AC_MODE_SLAVE = 0x00, + AC_MODE_MASTER = 0x01, + AC_MODE_MAX, } ac_mode_sm_t; typedef enum { - AC_MODULE_MIN = -1, - AC_MODULE_ADC = 0x01, - AC_MODULE_DAC = 0x02, - AC_MODULE_ADC_DAC = 0x03, - AC_MODULE_LINE = 0x04, - AC_MODULE_MAX + AC_MODULE_MIN = -1, + AC_MODULE_ADC = 0x01, + AC_MODULE_DAC = 0x02, + AC_MODULE_ADC_DAC = 0x03, + AC_MODULE_LINE = 0x04, + AC_MODULE_MAX } ac_module_t; -typedef enum{ - SRC_MIC1 = 1, - SRC_MIC2 = 2, - SRC_LINEIN = 3, -}ac_output_mixer_source_t; +typedef enum { + SRC_MIC1 = 1, + SRC_MIC2 = 2, + SRC_LINEIN = 3, +} ac_output_mixer_source_t; typedef enum { - GAIN_N45DB = 0, - GAIN_N30DB = 1, - GAIN_N15DB = 2, - GAIN_0DB = 3, - GAIN_15DB = 4, - GAIN_30DB = 5, - GAIN_45DB = 6, - GAIN_60DB = 7, + GAIN_N45DB = 0, + GAIN_N30DB = 1, + GAIN_N15DB = 2, + GAIN_0DB = 3, + GAIN_15DB = 4, + GAIN_30DB = 5, + GAIN_45DB = 6, + GAIN_60DB = 7, } ac_output_mixer_gain_t; typedef struct { - ac_i2s1_bclk_div_t bclk_div; /*!< bits clock divide */ - ac_i2s1_lrck_div_t lclk_div; /*!< WS clock divide */ + ac_i2s1_bclk_div_t bclk_div; /*!< bits clock divide */ + ac_i2s1_lrck_div_t lclk_div; /*!< WS clock divide */ } ac_i2s_clock_t; #endif \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/adac.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/adac.h index 1b1a2668..53d5a2bb 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/adac.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/adac.h @@ -9,18 +9,18 @@ * */ -#include "freertos/FreeRTOS.h" #include "driver/i2s.h" +#include "freertos/FreeRTOS.h" typedef enum { ADAC_ON = 0, ADAC_STANDBY, ADAC_OFF } adac_power_e; struct adac_s { - bool (*init)(int i2c_port_num, int i2s_num, i2s_config_t *config); - void (*deinit)(void); - void (*power)(adac_power_e mode); - void (*speaker)(bool active); - void (*headset)(bool active); - void (*volume)(unsigned left, unsigned right); + bool (*init)(int i2c_port_num, int i2s_num, i2s_config_t* config); + void (*deinit)(void); + void (*power)(adac_power_e mode); + void (*speaker)(bool active); + void (*headset)(bool active); + void (*volume)(unsigned left, unsigned right); }; extern struct adac_s dac_tas57xx; diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/es8311.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/es8311.h index e576dedd..17913d73 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/es8311.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/es8311.h @@ -18,80 +18,80 @@ /* * ES8311_REGISTER NAME_REG_REGISTER ADDRESS */ -#define ES8311_RESET_REG00 0x00 /*reset digital,csm,clock manager etc.*/ +#define ES8311_RESET_REG00 0x00 /*reset digital,csm,clock manager etc.*/ /* * Clock Scheme Register definition */ -#define ES8311_CLK_MANAGER_REG01 0x01 /* select clk src for mclk, enable clock for codec */ -#define ES8311_CLK_MANAGER_REG02 0x02 /* clk divider and clk multiplier */ -#define ES8311_CLK_MANAGER_REG03 0x03 /* adc fsmode and osr */ -#define ES8311_CLK_MANAGER_REG04 0x04 /* dac osr */ -#define ES8311_CLK_MANAGER_REG05 0x05 /* clk divier for adc and dac */ -#define ES8311_CLK_MANAGER_REG06 0x06 /* bclk inverter and divider */ -#define ES8311_CLK_MANAGER_REG07 0x07 /* tri-state, lrck divider */ -#define ES8311_CLK_MANAGER_REG08 0x08 /* lrck divider */ -#define ES8311_SDPIN_REG09 0x09 /* dac serial digital port */ -#define ES8311_SDPOUT_REG0A 0x0A /* adc serial digital port */ -#define ES8311_SYSTEM_REG0B 0x0B /* system */ -#define ES8311_SYSTEM_REG0C 0x0C /* system */ -#define ES8311_SYSTEM_REG0D 0x0D /* system, power up/down */ -#define ES8311_SYSTEM_REG0E 0x0E /* system, power up/down */ -#define ES8311_SYSTEM_REG0F 0x0F /* system, low power */ -#define ES8311_SYSTEM_REG10 0x10 /* system */ -#define ES8311_SYSTEM_REG11 0x11 /* system */ -#define ES8311_SYSTEM_REG12 0x12 /* system, Enable DAC */ -#define ES8311_SYSTEM_REG13 0x13 /* system */ -#define ES8311_SYSTEM_REG14 0x14 /* system, select DMIC, select analog pga gain */ -#define ES8311_ADC_REG15 0x15 /* ADC, adc ramp rate, dmic sense */ -#define ES8311_ADC_REG16 0x16 /* ADC */ -#define ES8311_ADC_REG17 0x17 /* ADC, volume */ -#define ES8311_ADC_REG18 0x18 /* ADC, alc enable and winsize */ -#define ES8311_ADC_REG19 0x19 /* ADC, alc maxlevel */ -#define ES8311_ADC_REG1A 0x1A /* ADC, alc automute */ -#define ES8311_ADC_REG1B 0x1B /* ADC, alc automute, adc hpf s1 */ -#define ES8311_ADC_REG1C 0x1C /* ADC, equalizer, hpf s2 */ -#define ES8311_DAC_REG31 0x31 /* DAC, mute */ -#define ES8311_DAC_REG32 0x32 /* DAC, volume */ -#define ES8311_DAC_REG33 0x33 /* DAC, offset */ -#define ES8311_DAC_REG34 0x34 /* DAC, drc enable, drc winsize */ -#define ES8311_DAC_REG35 0x35 /* DAC, drc maxlevel, minilevel */ -#define ES8311_DAC_REG37 0x37 /* DAC, ramprate */ -#define ES8311_GPIO_REG44 0x44 /* GPIO, dac2adc for test */ -#define ES8311_GP_REG45 0x45 /* GP CONTROL */ -#define ES8311_CHD1_REGFD 0xFD /* CHIP ID1 */ -#define ES8311_CHD2_REGFE 0xFE /* CHIP ID2 */ -#define ES8311_CHVER_REGFF 0xFF /* VERSION */ -#define ES8311_CHD1_REGFD 0xFD /* CHIP ID1 */ - -#define ES8311_MAX_REGISTER 0xFF +#define ES8311_CLK_MANAGER_REG01 \ + 0x01 /* select clk src for mclk, enable clock for codec */ +#define ES8311_CLK_MANAGER_REG02 0x02 /* clk divider and clk multiplier */ +#define ES8311_CLK_MANAGER_REG03 0x03 /* adc fsmode and osr */ +#define ES8311_CLK_MANAGER_REG04 0x04 /* dac osr */ +#define ES8311_CLK_MANAGER_REG05 0x05 /* clk divier for adc and dac */ +#define ES8311_CLK_MANAGER_REG06 0x06 /* bclk inverter and divider */ +#define ES8311_CLK_MANAGER_REG07 0x07 /* tri-state, lrck divider */ +#define ES8311_CLK_MANAGER_REG08 0x08 /* lrck divider */ +#define ES8311_SDPIN_REG09 0x09 /* dac serial digital port */ +#define ES8311_SDPOUT_REG0A 0x0A /* adc serial digital port */ +#define ES8311_SYSTEM_REG0B 0x0B /* system */ +#define ES8311_SYSTEM_REG0C 0x0C /* system */ +#define ES8311_SYSTEM_REG0D 0x0D /* system, power up/down */ +#define ES8311_SYSTEM_REG0E 0x0E /* system, power up/down */ +#define ES8311_SYSTEM_REG0F 0x0F /* system, low power */ +#define ES8311_SYSTEM_REG10 0x10 /* system */ +#define ES8311_SYSTEM_REG11 0x11 /* system */ +#define ES8311_SYSTEM_REG12 0x12 /* system, Enable DAC */ +#define ES8311_SYSTEM_REG13 0x13 /* system */ +#define ES8311_SYSTEM_REG14 \ + 0x14 /* system, select DMIC, select analog pga gain */ +#define ES8311_ADC_REG15 0x15 /* ADC, adc ramp rate, dmic sense */ +#define ES8311_ADC_REG16 0x16 /* ADC */ +#define ES8311_ADC_REG17 0x17 /* ADC, volume */ +#define ES8311_ADC_REG18 0x18 /* ADC, alc enable and winsize */ +#define ES8311_ADC_REG19 0x19 /* ADC, alc maxlevel */ +#define ES8311_ADC_REG1A 0x1A /* ADC, alc automute */ +#define ES8311_ADC_REG1B 0x1B /* ADC, alc automute, adc hpf s1 */ +#define ES8311_ADC_REG1C 0x1C /* ADC, equalizer, hpf s2 */ +#define ES8311_DAC_REG31 0x31 /* DAC, mute */ +#define ES8311_DAC_REG32 0x32 /* DAC, volume */ +#define ES8311_DAC_REG33 0x33 /* DAC, offset */ +#define ES8311_DAC_REG34 0x34 /* DAC, drc enable, drc winsize */ +#define ES8311_DAC_REG35 0x35 /* DAC, drc maxlevel, minilevel */ +#define ES8311_DAC_REG37 0x37 /* DAC, ramprate */ +#define ES8311_GPIO_REG44 0x44 /* GPIO, dac2adc for test */ +#define ES8311_GP_REG45 0x45 /* GP CONTROL */ +#define ES8311_CHD1_REGFD 0xFD /* CHIP ID1 */ +#define ES8311_CHD2_REGFE 0xFE /* CHIP ID2 */ +#define ES8311_CHVER_REGFF 0xFF /* VERSION */ +#define ES8311_CHD1_REGFD 0xFD /* CHIP ID1 */ +#define ES8311_MAX_REGISTER 0xFF typedef struct { - ESCodecMode esMode; - i2c_port_t i2c_port_num; - i2c_config_t i2c_cfg; - DacOutput dacOutput; - AdcInput adcInput; + ESCodecMode esMode; + i2c_port_t i2c_port_num; + i2c_config_t i2c_cfg; + DacOutput dacOutput; + AdcInput adcInput; } Es8311Config; +#define AUDIO_CODEC_ES8311_DEFAULT() \ + { \ + .esMode = ES_MODE_SLAVE, \ + .i2c_port_num = I2C_NUM_0, \ + .i2c_cfg = {.mode = I2C_MODE_MASTER, \ + .sda_io_num = IIC_DATA, \ + .scl_io_num = IIC_CLK, \ + .sda_pullup_en = GPIO_PULLUP_ENABLE, \ + .scl_pullup_en = GPIO_PULLUP_ENABLE, \ + .master.clk_speed = 100000}, \ + .adcInput = ADC_INPUT_LINPUT1_RINPUT1, \ + .dacOutput = DAC_OUTPUT_LOUT1 | DAC_OUTPUT_LOUT2 | DAC_OUTPUT_ROUT1 | \ + DAC_OUTPUT_ROUT2, \ + }; -#define AUDIO_CODEC_ES8311_DEFAULT(){ \ - .esMode = ES_MODE_SLAVE, \ - .i2c_port_num = I2C_NUM_0, \ - .i2c_cfg = { \ - .mode = I2C_MODE_MASTER, \ - .sda_io_num = IIC_DATA, \ - .scl_io_num = IIC_CLK, \ - .sda_pullup_en = GPIO_PULLUP_ENABLE,\ - .scl_pullup_en = GPIO_PULLUP_ENABLE,\ - .master.clk_speed = 100000\ - }, \ - .adcInput = ADC_INPUT_LINPUT1_RINPUT1,\ - .dacOutput = DAC_OUTPUT_LOUT1 | DAC_OUTPUT_LOUT2 | DAC_OUTPUT_ROUT1 | DAC_OUTPUT_ROUT2,\ -}; - -int Es8311Init(Es8311Config *cfg); +int Es8311Init(Es8311Config* cfg); void Es8311Uninit(); esp_err_t Es8311GetRef(bool flag); esp_err_t Es7243Init(void); @@ -107,9 +107,9 @@ int Es8311Start(ESCodecModule mode); int Es8311Stop(ESCodecModule mode); int Es8311SetVoiceVolume(int volume); -int Es8311GetVoiceVolume(int *volume); +int Es8311GetVoiceVolume(int* volume); int Es8311SetVoiceMute(int enable); -int Es8311GetVoiceMute(int *mute); +int Es8311GetVoiceMute(int* mute); int Es8311SetMicGain(MicGain gain); int Es8311ConfigAdcInput(AdcInput input); diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/esxxx_common.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/esxxx_common.h index bf8b35aa..3b4624ba 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/esxxx_common.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/esxxx_common.h @@ -2,165 +2,165 @@ #define __ESCODEC_COMMON_H__ typedef enum BitsLength { - BIT_LENGTH_MIN = -1, - BIT_LENGTH_16BITS = 0x03, - BIT_LENGTH_18BITS = 0x02, - BIT_LENGTH_20BITS = 0x01, - BIT_LENGTH_24BITS = 0x00, - BIT_LENGTH_32BITS = 0x04, - BIT_LENGTH_MAX, + BIT_LENGTH_MIN = -1, + BIT_LENGTH_16BITS = 0x03, + BIT_LENGTH_18BITS = 0x02, + BIT_LENGTH_20BITS = 0x01, + BIT_LENGTH_24BITS = 0x00, + BIT_LENGTH_32BITS = 0x04, + BIT_LENGTH_MAX, } BitsLength; typedef enum { - SAMPLE_RATE_MIN = -1, - SAMPLE_RATE_16K, - SAMPLE_RATE_32K, - SAMPLE_RATE_44_1K, - SAMPLE_RATE_MAX, + SAMPLE_RATE_MIN = -1, + SAMPLE_RATE_16K, + SAMPLE_RATE_32K, + SAMPLE_RATE_44_1K, + SAMPLE_RATE_MAX, } SampleRate; typedef enum { - MclkDiv_MIN = -1, - MclkDiv_1 = 1, - MclkDiv_2 = 2, - MclkDiv_3 = 3, - MclkDiv_4 = 4, - MclkDiv_6 = 5, - MclkDiv_8 = 6, - MclkDiv_9 = 7, - MclkDiv_11 = 8, - MclkDiv_12 = 9, - MclkDiv_16 = 10, - MclkDiv_18 = 11, - MclkDiv_22 = 12, - MclkDiv_24 = 13, - MclkDiv_33 = 14, - MclkDiv_36 = 15, - MclkDiv_44 = 16, - MclkDiv_48 = 17, - MclkDiv_66 = 18, - MclkDiv_72 = 19, - MclkDiv_5 = 20, - MclkDiv_10 = 21, - MclkDiv_15 = 22, - MclkDiv_17 = 23, - MclkDiv_20 = 24, - MclkDiv_25 = 25, - MclkDiv_30 = 26, - MclkDiv_32 = 27, - MclkDiv_34 = 28, - MclkDiv_7 = 29, - MclkDiv_13 = 30, - MclkDiv_14 = 31, - MclkDiv_MAX, + MclkDiv_MIN = -1, + MclkDiv_1 = 1, + MclkDiv_2 = 2, + MclkDiv_3 = 3, + MclkDiv_4 = 4, + MclkDiv_6 = 5, + MclkDiv_8 = 6, + MclkDiv_9 = 7, + MclkDiv_11 = 8, + MclkDiv_12 = 9, + MclkDiv_16 = 10, + MclkDiv_18 = 11, + MclkDiv_22 = 12, + MclkDiv_24 = 13, + MclkDiv_33 = 14, + MclkDiv_36 = 15, + MclkDiv_44 = 16, + MclkDiv_48 = 17, + MclkDiv_66 = 18, + MclkDiv_72 = 19, + MclkDiv_5 = 20, + MclkDiv_10 = 21, + MclkDiv_15 = 22, + MclkDiv_17 = 23, + MclkDiv_20 = 24, + MclkDiv_25 = 25, + MclkDiv_30 = 26, + MclkDiv_32 = 27, + MclkDiv_34 = 28, + MclkDiv_7 = 29, + MclkDiv_13 = 30, + MclkDiv_14 = 31, + MclkDiv_MAX, } SclkDiv; typedef enum { - LclkDiv_MIN = -1, - LclkDiv_128 = 0, - LclkDiv_192 = 1, - LclkDiv_256 = 2, - LclkDiv_384 = 3, - LclkDiv_512 = 4, - LclkDiv_576 = 5, - LclkDiv_768 = 6, - LclkDiv_1024 = 7, - LclkDiv_1152 = 8, - LclkDiv_1408 = 9, - LclkDiv_1536 = 10, - LclkDiv_2112 = 11, - LclkDiv_2304 = 12, + LclkDiv_MIN = -1, + LclkDiv_128 = 0, + LclkDiv_192 = 1, + LclkDiv_256 = 2, + LclkDiv_384 = 3, + LclkDiv_512 = 4, + LclkDiv_576 = 5, + LclkDiv_768 = 6, + LclkDiv_1024 = 7, + LclkDiv_1152 = 8, + LclkDiv_1408 = 9, + LclkDiv_1536 = 10, + LclkDiv_2112 = 11, + LclkDiv_2304 = 12, - LclkDiv_125 = 16, - LclkDiv_136 = 17, - LclkDiv_250 = 18, - LclkDiv_272 = 19, - LclkDiv_375 = 20, - LclkDiv_500 = 21, - LclkDiv_544 = 22, - LclkDiv_750 = 23, - LclkDiv_1000 = 24, - LclkDiv_1088 = 25, - LclkDiv_1496 = 26, - LclkDiv_1500 = 27, - LclkDiv_MAX, + LclkDiv_125 = 16, + LclkDiv_136 = 17, + LclkDiv_250 = 18, + LclkDiv_272 = 19, + LclkDiv_375 = 20, + LclkDiv_500 = 21, + LclkDiv_544 = 22, + LclkDiv_750 = 23, + LclkDiv_1000 = 24, + LclkDiv_1088 = 25, + LclkDiv_1496 = 26, + LclkDiv_1500 = 27, + LclkDiv_MAX, } LclkDiv; typedef enum { - ADC_INPUT_MIN = -1, - ADC_INPUT_LINPUT1_RINPUT1 = 0x00, - ADC_INPUT_MIC1 = 0x05, - ADC_INPUT_MIC2 = 0x06, - ADC_INPUT_LINPUT2_RINPUT2 = 0x50, - ADC_INPUT_DIFFERENCE = 0xf0, - ADC_INPUT_MAX, + ADC_INPUT_MIN = -1, + ADC_INPUT_LINPUT1_RINPUT1 = 0x00, + ADC_INPUT_MIC1 = 0x05, + ADC_INPUT_MIC2 = 0x06, + ADC_INPUT_LINPUT2_RINPUT2 = 0x50, + ADC_INPUT_DIFFERENCE = 0xf0, + ADC_INPUT_MAX, } AdcInput; typedef enum { - DAC_OUTPUT_MIN = -1, - DAC_OUTPUT_LOUT1 = 0x04, - DAC_OUTPUT_LOUT2 = 0x08, - DAC_OUTPUT_SPK = 0x09, - DAC_OUTPUT_ROUT1 = 0x10, - DAC_OUTPUT_ROUT2 = 0x20, - DAC_OUTPUT_ALL = 0x3c, - DAC_OUTPUT_MAX, + DAC_OUTPUT_MIN = -1, + DAC_OUTPUT_LOUT1 = 0x04, + DAC_OUTPUT_LOUT2 = 0x08, + DAC_OUTPUT_SPK = 0x09, + DAC_OUTPUT_ROUT1 = 0x10, + DAC_OUTPUT_ROUT2 = 0x20, + DAC_OUTPUT_ALL = 0x3c, + DAC_OUTPUT_MAX, } DacOutput; typedef enum { - D2SE_PGA_GAIN_MIN = -1, - D2SE_PGA_GAIN_DIS = 0, - D2SE_PGA_GAIN_EN = 1, - D2SE_PGA_GAIN_MAX = 2, + D2SE_PGA_GAIN_MIN = -1, + D2SE_PGA_GAIN_DIS = 0, + D2SE_PGA_GAIN_EN = 1, + D2SE_PGA_GAIN_MAX = 2, } D2SEPGA; typedef enum { - MIC_GAIN_MIN = -1, - MIC_GAIN_0DB = 0, - MIC_GAIN_3DB = 3, - MIC_GAIN_6DB = 6, - MIC_GAIN_9DB = 9, - MIC_GAIN_12DB = 12, - MIC_GAIN_15DB = 15, - MIC_GAIN_18DB = 18, - MIC_GAIN_21DB = 21, - MIC_GAIN_24DB = 24, + MIC_GAIN_MIN = -1, + MIC_GAIN_0DB = 0, + MIC_GAIN_3DB = 3, + MIC_GAIN_6DB = 6, + MIC_GAIN_9DB = 9, + MIC_GAIN_12DB = 12, + MIC_GAIN_15DB = 15, + MIC_GAIN_18DB = 18, + MIC_GAIN_21DB = 21, + MIC_GAIN_24DB = 24, #if defined CONFIG_CODEC_CHIP_IS_ES8311 - MIC_GAIN_30DB = 30, - MIC_GAIN_36DB = 36, - MIC_GAIN_42DB = 42, + MIC_GAIN_30DB = 30, + MIC_GAIN_36DB = 36, + MIC_GAIN_42DB = 42, #endif - MIC_GAIN_MAX, + MIC_GAIN_MAX, } MicGain; typedef enum { - ES_MODULE_MIN = -1, - ES_MODULE_ADC = 0x01, - ES_MODULE_DAC = 0x02, - ES_MODULE_ADC_DAC = 0x03, - ES_MODULE_LINE = 0x04, - ES_MODULE_MAX + ES_MODULE_MIN = -1, + ES_MODULE_ADC = 0x01, + ES_MODULE_DAC = 0x02, + ES_MODULE_ADC_DAC = 0x03, + ES_MODULE_LINE = 0x04, + ES_MODULE_MAX } ESCodecModule; typedef enum { - ES_MODE_MIN = -1, - ES_MODE_SLAVE = 0x00, - ES_MODE_MASTER = 0x01, - ES_MODE_MAX, + ES_MODE_MIN = -1, + ES_MODE_SLAVE = 0x00, + ES_MODE_MASTER = 0x01, + ES_MODE_MAX, } ESCodecMode; typedef enum { - ES_ = -1, - ES_I2S_NORMAL = 0, - ES_I2S_LEFT = 1, - ES_I2S_RIGHT = 2, - ES_I2S_DSP = 3, - ES_I2S_MAX + ES_ = -1, + ES_I2S_NORMAL = 0, + ES_I2S_LEFT = 1, + ES_I2S_RIGHT = 2, + ES_I2S_DSP = 3, + ES_I2S_MAX } ESCodecI2SFmt; typedef struct { - SclkDiv sclkDiv; - LclkDiv lclkDiv; + SclkDiv sclkDiv; + LclkDiv lclkDiv; } ESCodecI2sClock; -#endif //__ESCODEC_COMMON_H__ +#endif //__ESCODEC_COMMON_H__ diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/unix/ALSAAudioSink.h b/components/spotify/cspot/bell/main/audio-sinks/include/unix/ALSAAudioSink.h index 1b1bd548..3a553e14 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/unix/ALSAAudioSink.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/unix/ALSAAudioSink.h @@ -1,124 +1,106 @@ #pragma once -#include -#include -#include "AudioSink.h" +#include #include #include -#include #include +#include #include #include +#include +#include "AudioSink.h" #define PCM_DEVICE "default" template -class RingbufferPointer -{ - typedef std::unique_ptr TPointer; +class RingbufferPointer { + typedef std::unique_ptr TPointer; -public: - explicit RingbufferPointer() - { - // create objects - for (int i = 0; i < SIZE; i++) - { - buf_[i] = std::make_unique(); - } + public: + explicit RingbufferPointer() { + // create objects + for (int i = 0; i < SIZE; i++) { + buf_[i] = std::make_unique(); + } + } + + bool push(TPointer& item) { + std::lock_guard lock(mutex_); + if (full()) + return false; + + std::swap(buf_[head_], item); + + if (full_) + tail_ = (tail_ + 1) % max_size_; + + head_ = (head_ + 1) % max_size_; + full_ = head_ == tail_; + + return true; + } + + bool pop(TPointer& item) { + std::lock_guard lock(mutex_); + if (empty()) + return false; + + std::swap(buf_[tail_], item); + + full_ = false; + tail_ = (tail_ + 1) % max_size_; + + return true; + } + + void reset() { + std::lock_guard lock(mutex_); + head_ = tail_; + full_ = false; + } + + bool empty() const { return (!full_ && (head_ == tail_)); } + + bool full() const { return full_; } + + int capacity() const { return max_size_; } + + int size() const { + int size = max_size_; + + if (!full_) { + if (head_ >= tail_) + size = head_ - tail_; + else + size = max_size_ + head_ - tail_; } - bool push(TPointer &item) - { - std::lock_guard lock(mutex_); - if (full()) - return false; + return size; + } - std::swap(buf_[head_], item); + private: + TPointer buf_[SIZE]; - if (full_) - tail_ = (tail_ + 1) % max_size_; - - head_ = (head_ + 1) % max_size_; - full_ = head_ == tail_; - - return true; - } - - bool pop(TPointer &item) - { - std::lock_guard lock(mutex_); - if (empty()) - return false; - - std::swap(buf_[tail_], item); - - full_ = false; - tail_ = (tail_ + 1) % max_size_; - - return true; - } - - void reset() - { - std::lock_guard lock(mutex_); - head_ = tail_; - full_ = false; - } - - bool empty() const - { - return (!full_ && (head_ == tail_)); - } - - bool full() const - { - return full_; - } - - int capacity() const - { - return max_size_; - } - - int size() const - { - int size = max_size_; - - if (!full_) - { - if (head_ >= tail_) - size = head_ - tail_; - else - size = max_size_ + head_ - tail_; - } - - return size; - } - -private: - TPointer buf_[SIZE]; - - std::mutex mutex_; - int head_ = 0; - int tail_ = 0; - const int max_size_ = SIZE; - bool full_ = 0; + std::mutex mutex_; + int head_ = 0; + int tail_ = 0; + const int max_size_ = SIZE; + bool full_ = 0; }; -class ALSAAudioSink : public AudioSink, public bell::Task -{ -public: - ALSAAudioSink(); - ~ALSAAudioSink(); - void feedPCMFrames(const uint8_t *buffer, size_t bytes); - void runTask(); +class ALSAAudioSink : public AudioSink, public bell::Task { + public: + ALSAAudioSink(); + ~ALSAAudioSink(); + void feedPCMFrames(const uint8_t* buffer, size_t bytes); + void runTask(); -private: - RingbufferPointer, 3> ringbuffer; - unsigned int pcm; - snd_pcm_t *pcm_handle; - snd_pcm_hw_params_t *params; - snd_pcm_uframes_t frames; - int buff_size; - std::vector buff; + private: + RingbufferPointer, 3> ringbuffer; + unsigned int pcm; + snd_pcm_t* pcm_handle; + snd_pcm_hw_params_t* params; + snd_pcm_uframes_t frames; + int buff_size; + std::vector buff; }; diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/unix/NamedPipeAudioSink.h b/components/spotify/cspot/bell/main/audio-sinks/include/unix/NamedPipeAudioSink.h index cbce8884..f987df91 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/unix/NamedPipeAudioSink.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/unix/NamedPipeAudioSink.h @@ -1,16 +1,17 @@ #pragma once -#include -#include -#include "AudioSink.h" +#include // for size_t +#include // for uint8_t +#include // for ofstream -class NamedPipeAudioSink : public AudioSink -{ -public: - NamedPipeAudioSink(); - ~NamedPipeAudioSink(); - void feedPCMFrames(const uint8_t *buffer, size_t bytes); - -private: - std::ofstream namedPipeFile; +#include "AudioSink.h" // for AudioSink + +class NamedPipeAudioSink : public AudioSink { + public: + NamedPipeAudioSink(); + ~NamedPipeAudioSink(); + void feedPCMFrames(const uint8_t* buffer, size_t bytes); + + private: + std::ofstream namedPipeFile; }; diff --git a/components/spotify/cspot/bell/main/audio-sinks/unix/ALSAAudioSink.cpp b/components/spotify/cspot/bell/main/audio-sinks/unix/ALSAAudioSink.cpp index 0219fbbe..7b63d848 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/unix/ALSAAudioSink.cpp +++ b/components/spotify/cspot/bell/main/audio-sinks/unix/ALSAAudioSink.cpp @@ -1,101 +1,92 @@ #include "ALSAAudioSink.h" -ALSAAudioSink::ALSAAudioSink() : Task("", 0, 0, 0) -{ - /* Open the PCM device in playback mode */ - if (pcm = snd_pcm_open(&pcm_handle, PCM_DEVICE, - SND_PCM_STREAM_PLAYBACK, 0) < 0) - { - printf("ERROR: Can't open \"%s\" PCM device. %s\n", - PCM_DEVICE, snd_strerror(pcm)); +ALSAAudioSink::ALSAAudioSink() : Task("", 0, 0, 0) { + /* Open the PCM device in playback mode */ + if (pcm = snd_pcm_open(&pcm_handle, PCM_DEVICE, SND_PCM_STREAM_PLAYBACK, 0) < + 0) { + printf("ERROR: Can't open \"%s\" PCM device. %s\n", PCM_DEVICE, + snd_strerror(pcm)); + } + + /* Allocate parameters object and fill it with default values*/ + snd_pcm_hw_params_alloca(¶ms); + + snd_pcm_hw_params_any(pcm_handle, params); + + /* Set parameters */ + if (pcm = snd_pcm_hw_params_set_access(pcm_handle, params, + SND_PCM_ACCESS_RW_INTERLEAVED) < 0) + printf("ERROR: Can't set interleaved mode. %s\n", snd_strerror(pcm)); + + if (pcm = snd_pcm_hw_params_set_format(pcm_handle, params, + SND_PCM_FORMAT_S16_LE) < 0) + printf("ERROR: Can't set format. %s\n", snd_strerror(pcm)); + + if (pcm = snd_pcm_hw_params_set_channels(pcm_handle, params, 2) < 0) + printf("ERROR: Can't set channels number. %s\n", snd_strerror(pcm)); + unsigned int rate = 44100; + if (pcm = snd_pcm_hw_params_set_rate_near(pcm_handle, params, &rate, 0) < 0) + printf("ERROR: Can't set rate. %s\n", snd_strerror(pcm)); + unsigned int periodTime = 800; + int dir = -1; + snd_pcm_hw_params_set_period_time_near(pcm_handle, params, &periodTime, &dir); + /* Write parameters */ + if (pcm = snd_pcm_hw_params(pcm_handle, params) < 0) + printf("ERROR: Can't set harware parameters. %s\n", snd_strerror(pcm)); + + /* Resume information */ + printf("PCM name: '%s'\n", snd_pcm_name(pcm_handle)); + + printf("PCM state: %s\n", snd_pcm_state_name(snd_pcm_state(pcm_handle))); + unsigned int tmp; + snd_pcm_hw_params_get_channels(params, &tmp); + printf("channels: %i ", tmp); + if (tmp == 1) + printf("(mono)\n"); + else if (tmp == 2) + printf("(stereo)\n"); + + snd_pcm_hw_params_get_period_time(params, &tmp, NULL); + printf("period_time = %d\n", tmp); + snd_pcm_hw_params_get_period_size(params, &frames, 0); + + this->buff_size = frames * 2 * 2 /* 2 -> sample size */; + printf("required buff_size: %d\n", buff_size); + this->startTask(); +} + +ALSAAudioSink::~ALSAAudioSink() { + snd_pcm_drain(pcm_handle); + snd_pcm_close(pcm_handle); +} + +void ALSAAudioSink::runTask() { + std::unique_ptr> dataPtr; + while (true) { + if (!this->ringbuffer.pop(dataPtr)) { + usleep(100); + continue; } + if (pcm = snd_pcm_writei(pcm_handle, dataPtr->data(), this->frames) == + -EPIPE) { - /* Allocate parameters object and fill it with default values*/ - snd_pcm_hw_params_alloca(¶ms); - - snd_pcm_hw_params_any(pcm_handle, params); - - /* Set parameters */ - if (pcm = snd_pcm_hw_params_set_access(pcm_handle, params, - SND_PCM_ACCESS_RW_INTERLEAVED) < 0) - printf("ERROR: Can't set interleaved mode. %s\n", snd_strerror(pcm)); - - if (pcm = snd_pcm_hw_params_set_format(pcm_handle, params, - SND_PCM_FORMAT_S16_LE) < 0) - printf("ERROR: Can't set format. %s\n", snd_strerror(pcm)); - - if (pcm = snd_pcm_hw_params_set_channels(pcm_handle, params, 2) < 0) - printf("ERROR: Can't set channels number. %s\n", snd_strerror(pcm)); - unsigned int rate = 44100; - if (pcm = snd_pcm_hw_params_set_rate_near(pcm_handle, params, &rate, 0) < 0) - printf("ERROR: Can't set rate. %s\n", snd_strerror(pcm)); - unsigned int periodTime = 800; - int dir = -1; - snd_pcm_hw_params_set_period_time_near(pcm_handle, params, &periodTime, &dir); - /* Write parameters */ - if (pcm = snd_pcm_hw_params(pcm_handle, params) < 0) - printf("ERROR: Can't set harware parameters. %s\n", snd_strerror(pcm)); - - /* Resume information */ - printf("PCM name: '%s'\n", snd_pcm_name(pcm_handle)); - - printf("PCM state: %s\n", snd_pcm_state_name(snd_pcm_state(pcm_handle))); - unsigned int tmp; - snd_pcm_hw_params_get_channels(params, &tmp); - printf("channels: %i ", tmp); - if (tmp == 1) - printf("(mono)\n"); - else if (tmp == 2) - printf("(stereo)\n"); - - snd_pcm_hw_params_get_period_time(params, &tmp, NULL); - printf("period_time = %d\n", tmp); - snd_pcm_hw_params_get_period_size(params, &frames, 0); - - this->buff_size = frames * 2 * 2 /* 2 -> sample size */; - printf("required buff_size: %d\n", buff_size); - this->startTask(); -} - -ALSAAudioSink::~ALSAAudioSink() -{ - snd_pcm_drain(pcm_handle); - snd_pcm_close(pcm_handle); -} - -void ALSAAudioSink::runTask() -{ - std::unique_ptr> dataPtr; - while (true) - { - if (!this->ringbuffer.pop(dataPtr)) - { - usleep(100); - continue; - } - if (pcm = snd_pcm_writei(pcm_handle, dataPtr->data(), this->frames) == -EPIPE) - { - - snd_pcm_prepare(pcm_handle); - } - else if (pcm < 0) - { - printf("ERROR. Can't write to PCM device. %s\n", snd_strerror(pcm)); - } + snd_pcm_prepare(pcm_handle); + } else if (pcm < 0) { + printf("ERROR. Can't write to PCM device. %s\n", snd_strerror(pcm)); } + } } -void ALSAAudioSink::feedPCMFrames(const uint8_t *buffer, size_t bytes) -{ +void ALSAAudioSink::feedPCMFrames(const uint8_t* buffer, size_t bytes) { - buff.insert(buff.end(), buffer, buffer + bytes); - while (buff.size() > this->buff_size) - { - auto ptr = std::make_unique>(this->buff.begin(), this->buff.begin() + this->buff_size); - this->buff = std::vector(this->buff.begin() + this->buff_size, this->buff.end()); - while (!this->ringbuffer.push(ptr)) - { - usleep(100); - }; - } + buff.insert(buff.end(), buffer, buffer + bytes); + while (buff.size() > this->buff_size) { + auto ptr = std::make_unique>( + this->buff.begin(), this->buff.begin() + this->buff_size); + this->buff = std::vector(this->buff.begin() + this->buff_size, + this->buff.end()); + while (!this->ringbuffer.push(ptr)) { + usleep(100); + }; + } } diff --git a/components/spotify/cspot/bell/main/audio-sinks/unix/NamedPipeAudioSink.cpp b/components/spotify/cspot/bell/main/audio-sinks/unix/NamedPipeAudioSink.cpp index 2c310786..90bb1422 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/unix/NamedPipeAudioSink.cpp +++ b/components/spotify/cspot/bell/main/audio-sinks/unix/NamedPipeAudioSink.cpp @@ -1,21 +1,19 @@ #include "NamedPipeAudioSink.h" -NamedPipeAudioSink::NamedPipeAudioSink() -{ - printf("Start\n"); - this->namedPipeFile = std::ofstream("outputFifo", std::ios::binary); - printf("stop\n"); +#include // for printf +NamedPipeAudioSink::NamedPipeAudioSink() { + printf("Start\n"); + this->namedPipeFile = std::ofstream("outputFifo", std::ios::binary); + printf("stop\n"); } -NamedPipeAudioSink::~NamedPipeAudioSink() -{ - this->namedPipeFile.close(); +NamedPipeAudioSink::~NamedPipeAudioSink() { + this->namedPipeFile.close(); } -void NamedPipeAudioSink::feedPCMFrames(const uint8_t *buffer, size_t bytes) -{ - // Write the actual data - this->namedPipeFile.write((char*)buffer, (long)bytes); - this->namedPipeFile.flush(); +void NamedPipeAudioSink::feedPCMFrames(const uint8_t* buffer, size_t bytes) { + // Write the actual data + this->namedPipeFile.write((char*)buffer, (long)bytes); + this->namedPipeFile.flush(); } diff --git a/components/spotify/cspot/bell/main/audio-sinks/unix/PortAudioSink.cpp b/components/spotify/cspot/bell/main/audio-sinks/unix/PortAudioSink.cpp index 51ff7ed3..4fec893f 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/unix/PortAudioSink.cpp +++ b/components/spotify/cspot/bell/main/audio-sinks/unix/PortAudioSink.cpp @@ -1,65 +1,57 @@ #include "PortAudioSink.h" -PortAudioSink::PortAudioSink() -{ - Pa_Initialize(); - this->setParams(44100, 2, 16); +PortAudioSink::PortAudioSink() { + Pa_Initialize(); + this->setParams(44100, 2, 16); } -bool PortAudioSink::setParams(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) { - if (stream) { - Pa_StopStream(stream); - } - PaStreamParameters outputParameters; - outputParameters.device = Pa_GetDefaultOutputDevice(); - if (outputParameters.device == paNoDevice) { - printf("PortAudio: Default audio device not found!\n"); - // exit(0); - } - printf("PortAudio: Default audio device not found!\n"); - - outputParameters.channelCount = channelCount; - switch (bitDepth) { - case 32: - outputParameters.sampleFormat = paInt32; - break; - case 24: - outputParameters.sampleFormat = paInt24; - break; - case 16: - outputParameters.sampleFormat = paInt16; - break; - case 8: - outputParameters.sampleFormat = paInt8; - break; - default: - outputParameters.sampleFormat = paInt16; - break; - } - outputParameters.suggestedLatency = 0.050; - outputParameters.hostApiSpecificStreamInfo = NULL; - - PaError err = Pa_OpenStream( - &stream, - NULL, - &outputParameters, - sampleRate, - 4096 / (channelCount * bitDepth / 8), - paClipOff, - NULL, // blocking api - NULL - ); - Pa_StartStream(stream); - return !err; -} - -PortAudioSink::~PortAudioSink() -{ +bool PortAudioSink::setParams(uint32_t sampleRate, uint8_t channelCount, + uint8_t bitDepth) { + if (stream) { Pa_StopStream(stream); - Pa_Terminate(); + } + PaStreamParameters outputParameters; + outputParameters.device = Pa_GetDefaultOutputDevice(); + if (outputParameters.device == paNoDevice) { + printf("PortAudio: Default audio device not found!\n"); + // exit(0); + } + printf("PortAudio: Default audio device not found!\n"); + + outputParameters.channelCount = channelCount; + switch (bitDepth) { + case 32: + outputParameters.sampleFormat = paInt32; + break; + case 24: + outputParameters.sampleFormat = paInt24; + break; + case 16: + outputParameters.sampleFormat = paInt16; + break; + case 8: + outputParameters.sampleFormat = paInt8; + break; + default: + outputParameters.sampleFormat = paInt16; + break; + } + outputParameters.suggestedLatency = 0.050; + outputParameters.hostApiSpecificStreamInfo = NULL; + + PaError err = Pa_OpenStream(&stream, NULL, &outputParameters, sampleRate, + 4096 / (channelCount * bitDepth / 8), paClipOff, + NULL, // blocking api + NULL); + Pa_StartStream(stream); + return !err; } -void PortAudioSink::feedPCMFrames(const uint8_t *buffer, size_t bytes) -{ - Pa_WriteStream(stream, buffer, bytes / 4); +PortAudioSink::~PortAudioSink() { + Pa_StopStream(stream); + Pa_Terminate(); +} + +void PortAudioSink::feedPCMFrames(const uint8_t* buffer, size_t bytes) { + Pa_WriteStream(stream, buffer, bytes / 4); } diff --git a/components/spotify/cspot/bell/main/io/BellHTTPServer.cpp b/components/spotify/cspot/bell/main/io/BellHTTPServer.cpp index d6c7d4eb..2b24c82d 100644 --- a/components/spotify/cspot/bell/main/io/BellHTTPServer.cpp +++ b/components/spotify/cspot/bell/main/io/BellHTTPServer.cpp @@ -1,8 +1,14 @@ #include "BellHTTPServer.h" -#include -#include -#include "CivetServer.h" -#include "civetweb.h" + +#include // for memcpy +#include // for assert +#include // for exception +#include // for scoped_lock +#include // for sregex_token_iterator, regex + +#include "BellLogger.h" // for AbstractLogger, BELL_LOG, bell +#include "CivetServer.h" // for CivetServer, CivetWebSocketHandler +#include "civetweb.h" // for mg_get_request_info, mg_printf, mg_set_user... using namespace bell; @@ -195,7 +201,8 @@ std::unique_ptr BellHTTPServer::makeJsonResponse( return response; } -std::unique_ptr BellHTTPServer::makeEmptyResponse() { +std::unique_ptr +BellHTTPServer::makeEmptyResponse() { auto response = std::make_unique(); return response; } @@ -225,8 +232,10 @@ void BellHTTPServer::registerNotFound(HTTPHandler handler) { std::unordered_map BellHTTPServer::extractParams( struct mg_connection* conn) { + void* data = mg_get_user_connection_data(conn); + assert(data != nullptr); std::unordered_map& params = - *(std::unordered_map*) - mg_get_user_connection_data(conn); + *(std::unordered_map*)data; + return params; } diff --git a/components/spotify/cspot/bell/main/io/BellTar.cpp b/components/spotify/cspot/bell/main/io/BellTar.cpp index edebfc50..4ab0237a 100644 --- a/components/spotify/cspot/bell/main/io/BellTar.cpp +++ b/components/spotify/cspot/bell/main/io/BellTar.cpp @@ -1,14 +1,18 @@ #include "BellTar.h" -#include -#include + +#include // for mkdir using namespace bell::BellTar; -#include -#include // for sprintf, snprintf and sscanf -#include // for rand -#include // for strlen and memset -#include // for time +#include // for min +#include // for assert +#include // for uint8_t +#include // for sprintf, size_t, sscanf, EOF, NULL +#include // for rand +#include // for memset, strlen +#include // for time +#include // for ofstream +#include // for vector #ifdef _WIN32 #include #endif @@ -59,7 +63,7 @@ void header_set_metadata(tar_header* header) { std::memset(header, 0, sizeof(tar_header)); std::sprintf(header->magic, "ustar"); - std::sprintf(header->mtime, "%011lo", (unsigned long) std::time(NULL)); + std::sprintf(header->mtime, "%011lo", (unsigned long)std::time(NULL)); std::sprintf(header->mode, "%07o", 0644); std::sprintf(header->uname, "unkown"); // ... a bit random std::sprintf(header->gname, "users"); diff --git a/components/spotify/cspot/bell/main/io/BinaryReader.cpp b/components/spotify/cspot/bell/main/io/BinaryReader.cpp index f7ef1f29..13aab639 100644 --- a/components/spotify/cspot/bell/main/io/BinaryReader.cpp +++ b/components/spotify/cspot/bell/main/io/BinaryReader.cpp @@ -1,72 +1,69 @@ #include "BinaryReader.h" -#include + +#include // for size_t +#include // for uint8_t +#include // for remove_extent_t + +#include "ByteStream.h" // for ByteStream bell::BinaryReader::BinaryReader(std::shared_ptr stream) { - this->stream = stream; + this->stream = stream; } size_t bell::BinaryReader::position() { - return stream->position(); + return stream->position(); } size_t bell::BinaryReader::size() { - return stream->size(); + return stream->size(); } void bell::BinaryReader::close() { - stream->close(); + stream->close(); } void bell::BinaryReader::skip(size_t pos) { - std::vector b(pos); - stream->read(&b[0], pos); + std::vector b(pos); + stream->read(&b[0], pos); } int32_t bell::BinaryReader::readInt() { - uint8_t b[4]; - if (stream->read((uint8_t *) b,4) != 4) - return 0; - - return static_cast( - (b[3]) | - (b[2] << 8) | - (b[1] << 16)| - (b[0] << 24) ); + uint8_t b[4]; + if (stream->read((uint8_t*)b, 4) != 4) + return 0; + + return static_cast((b[3]) | (b[2] << 8) | (b[1] << 16) | + (b[0] << 24)); } int16_t bell::BinaryReader::readShort() { - uint8_t b[2]; - if (stream->read((uint8_t *) b,2) != 2) - return 0; - - return static_cast( - (b[1]) | - (b[0] << 8)); + uint8_t b[2]; + if (stream->read((uint8_t*)b, 2) != 2) + return 0; + + return static_cast((b[1]) | (b[0] << 8)); } - uint32_t bell::BinaryReader::readUInt() { - return readInt() & 0xffffffffL; + return readInt() & 0xffffffffL; } uint8_t bell::BinaryReader::readByte() { - uint8_t b[1]; - if (stream->read((uint8_t *) b,1) != 1) - return 0; - return b[0]; + uint8_t b[1]; + if (stream->read((uint8_t*)b, 1) != 1) + return 0; + return b[0]; } std::vector bell::BinaryReader::readBytes(size_t size) { - std::vector data(size); - stream->read(&data[0], size); - return data; + std::vector data(size); + stream->read(&data[0], size); + return data; } long long bell::BinaryReader::readLong() { - long high = readInt(); - long low = readInt(); + long high = readInt(); + long low = readInt(); - return static_cast( - ((long long) high << 32) | low ); + return static_cast(((long long)high << 32) | low); } - diff --git a/components/spotify/cspot/bell/main/io/BinaryStream.cpp b/components/spotify/cspot/bell/main/io/BinaryStream.cpp index a5a445ee..38a7a9ff 100644 --- a/components/spotify/cspot/bell/main/io/BinaryStream.cpp +++ b/components/spotify/cspot/bell/main/io/BinaryStream.cpp @@ -1,5 +1,5 @@ #include -#include +#include // for runtime_error using namespace bell; diff --git a/components/spotify/cspot/bell/main/io/BufferedStream.cpp b/components/spotify/cspot/bell/main/io/BufferedStream.cpp index a41f2439..4af2c3dd 100644 --- a/components/spotify/cspot/bell/main/io/BufferedStream.cpp +++ b/components/spotify/cspot/bell/main/io/BufferedStream.cpp @@ -1,172 +1,182 @@ #include "BufferedStream.h" -#include -BufferedStream::BufferedStream( - const std::string &taskName, - uint32_t bufferSize, - uint32_t readThreshold, - uint32_t readSize, - uint32_t readyThreshold, - uint32_t notReadyThreshold, - bool waitForReady) - : bell::Task(taskName, 4096, 5, 0) { - this->bufferSize = bufferSize; - this->readAt = bufferSize - readThreshold; - this->readSize = readSize; - this->readyThreshold = readyThreshold; - this->notReadyThreshold = notReadyThreshold; - this->waitForReady = waitForReady; - this->buf = static_cast(malloc(bufferSize)); - this->bufEnd = buf + bufferSize; - reset(); +#include // for free, malloc +#include // for min +#include // for uint32_t +#include // for memcpy +#include // for remove_extent_t + +BufferedStream::BufferedStream(const std::string& taskName, uint32_t bufferSize, + uint32_t readThreshold, uint32_t readSize, + uint32_t readyThreshold, + uint32_t notReadyThreshold, bool waitForReady) + : bell::Task(taskName, 4096, 5, 0) { + this->bufferSize = bufferSize; + this->readAt = bufferSize - readThreshold; + this->readSize = readSize; + this->readyThreshold = readyThreshold; + this->notReadyThreshold = notReadyThreshold; + this->waitForReady = waitForReady; + this->buf = static_cast(malloc(bufferSize)); + this->bufEnd = buf + bufferSize; + reset(); } BufferedStream::~BufferedStream() { - this->close(); - free(buf); + this->close(); + free(buf); } void BufferedStream::close() { - this->terminate = true; - this->readSem.give(); // force a read operation - const std::lock_guard lock(runningMutex); - if (this->source) - this->source->close(); - this->source = nullptr; + this->terminate = true; + this->readSem.give(); // force a read operation + const std::lock_guard lock(runningMutex); + if (this->source) + this->source->close(); + this->source = nullptr; } void BufferedStream::reset() { - this->bufReadPtr = this->buf; - this->bufWritePtr = this->buf; - this->readTotal = 0; - this->bufferTotal = 0; - this->readAvailable = 0; - this->terminate = false; + this->bufReadPtr = this->buf; + this->bufWritePtr = this->buf; + this->readTotal = 0; + this->bufferTotal = 0; + this->readAvailable = 0; + this->terminate = false; } -bool BufferedStream::open(const std::shared_ptr &stream) { - if (this->running) - this->close(); - reset(); - this->source = stream; - startTask(); - return source.get(); +bool BufferedStream::open(const std::shared_ptr& stream) { + if (this->running) + this->close(); + reset(); + this->source = stream; + startTask(); + return source.get(); } -bool BufferedStream::open(const StreamReader &newReader, uint32_t initialOffset) { - if (this->running) - this->close(); - reset(); - this->reader = newReader; - this->bufferTotal = initialOffset; - startTask(); - return source.get(); +bool BufferedStream::open(const StreamReader& newReader, + uint32_t initialOffset) { + if (this->running) + this->close(); + reset(); + this->reader = newReader; + this->bufferTotal = initialOffset; + startTask(); + return source.get(); } bool BufferedStream::isReady() const { - return readAvailable >= readyThreshold; + return readAvailable >= readyThreshold; } bool BufferedStream::isNotReady() const { - return readAvailable < notReadyThreshold; + return readAvailable < notReadyThreshold; } size_t BufferedStream::skip(size_t len) { - return read(nullptr, len); + return read(nullptr, len); } size_t BufferedStream::position() { - return readTotal; + return readTotal; } size_t BufferedStream::size() { - return source->size(); + return source->size(); } -uint32_t BufferedStream::lengthBetween(uint8_t *me, uint8_t *other) { - const std::lock_guard lock(readMutex); - if (other <= me) { - // buf .... other ...... me ........ bufEnd - // buf .... me/other ........ bufEnd - return bufEnd - me; - } else { - // buf ........ me ........ other .... bufEnd - return other - me; - } +uint32_t BufferedStream::lengthBetween(uint8_t* me, uint8_t* other) { + const std::lock_guard lock(readMutex); + if (other <= me) { + // buf .... other ...... me ........ bufEnd + // buf .... me/other ........ bufEnd + return bufEnd - me; + } else { + // buf ........ me ........ other .... bufEnd + return other - me; + } } -size_t BufferedStream::read(uint8_t *dst, size_t len) { - if (waitForReady && isNotReady()) { - while ((source || reader) && !isReady()) {} // end waiting after termination - } - if (!running && !readAvailable) { - reset(); - return 0; - } - uint32_t read = 0; - uint32_t toReadTotal = std::min(readAvailable.load(), static_cast(len)); - while (toReadTotal > 0) { - uint32_t toRead = std::min(toReadTotal, lengthBetween(bufReadPtr, bufWritePtr)); - if (dst) { - memcpy(dst, bufReadPtr, toRead); - dst += toRead; - } - readAvailable -= toRead; - bufReadPtr += toRead; - if (bufReadPtr >= bufEnd) - bufReadPtr = buf; - toReadTotal -= toRead; - read += toRead; - readTotal += toRead; - } - this->readSem.give(); - return read; +size_t BufferedStream::read(uint8_t* dst, size_t len) { + if (waitForReady && isNotReady()) { + while ((source || reader) && !isReady()) { + } // end waiting after termination + } + if (!running && !readAvailable) { + reset(); + return 0; + } + uint32_t read = 0; + uint32_t toReadTotal = + std::min(readAvailable.load(), static_cast(len)); + while (toReadTotal > 0) { + uint32_t toRead = + std::min(toReadTotal, lengthBetween(bufReadPtr, bufWritePtr)); + if (dst) { + memcpy(dst, bufReadPtr, toRead); + dst += toRead; + } + readAvailable -= toRead; + bufReadPtr += toRead; + if (bufReadPtr >= bufEnd) + bufReadPtr = buf; + toReadTotal -= toRead; + read += toRead; + readTotal += toRead; + } + this->readSem.give(); + return read; } void BufferedStream::runTask() { - const std::lock_guard lock(runningMutex); - running = true; - if (!source && reader) { - // get the initial request on the task's thread - source = reader(this->bufferTotal); - } - while (!terminate) { - if (!source) - break; - if (isReady()) { - // buffer ready, wait for any read operations - this->readSem.wait(); - } - if (terminate) - break; - if (readAvailable > readAt) - continue; - // here, the buffer needs re-filling - uint32_t len; - bool wasReady = isReady(); - do { - uint32_t toRead = std::min(readSize, lengthBetween(bufWritePtr, bufReadPtr)); - if (!source) { - len = 0; - break; - } - len = source->read(bufWritePtr, toRead); - readAvailable += len; - bufferTotal += len; - bufWritePtr += len; - if (bufWritePtr >= bufEnd) // TODO is == enough here? - bufWritePtr = buf; - } while (len && readSize < bufferSize - readAvailable); // loop until there's no more free space in the buffer - if (!len && reader) - source = reader(bufferTotal); - else if (!len) - terminate = true; - // signal that buffer is ready for reading - if (!wasReady && isReady()) { - this->readySem.give(); - } - } - source = nullptr; - reader = nullptr; - running = false; + const std::lock_guard lock(runningMutex); + running = true; + if (!source && reader) { + // get the initial request on the task's thread + source = reader(this->bufferTotal); + } + while (!terminate) { + if (!source) + break; + if (isReady()) { + // buffer ready, wait for any read operations + this->readSem.wait(); + } + if (terminate) + break; + if (readAvailable > readAt) + continue; + // here, the buffer needs re-filling + uint32_t len; + bool wasReady = isReady(); + do { + uint32_t toRead = + std::min(readSize, lengthBetween(bufWritePtr, bufReadPtr)); + if (!source) { + len = 0; + break; + } + len = source->read(bufWritePtr, toRead); + readAvailable += len; + bufferTotal += len; + bufWritePtr += len; + if (bufWritePtr >= bufEnd) // TODO is == enough here? + bufWritePtr = buf; + } while ( + len && + readSize < + bufferSize - + readAvailable); // loop until there's no more free space in the buffer + if (!len && reader) + source = reader(bufferTotal); + else if (!len) + terminate = true; + // signal that buffer is ready for reading + if (!wasReady && isReady()) { + this->readySem.give(); + } + } + source = nullptr; + reader = nullptr; + running = false; } diff --git a/components/spotify/cspot/bell/main/io/CircularBuffer.cpp b/components/spotify/cspot/bell/main/io/CircularBuffer.cpp index 700bf0e4..2e5eff2b 100644 --- a/components/spotify/cspot/bell/main/io/CircularBuffer.cpp +++ b/components/spotify/cspot/bell/main/io/CircularBuffer.cpp @@ -1,89 +1,85 @@ #include "CircularBuffer.h" +#include // for min + using namespace bell; -CircularBuffer::CircularBuffer(size_t dataCapacity) -{ - this->dataCapacity = dataCapacity; - buffer = std::vector(dataCapacity); - this->dataSemaphore = std::make_unique(5); +CircularBuffer::CircularBuffer(size_t dataCapacity) { + this->dataCapacity = dataCapacity; + buffer = std::vector(dataCapacity); + this->dataSemaphore = std::make_unique(5); }; -size_t CircularBuffer::write(const uint8_t *data, size_t bytes) -{ - if (bytes == 0) - return 0; +size_t CircularBuffer::write(const uint8_t* data, size_t bytes) { + if (bytes == 0) + return 0; - std::lock_guard guard(bufferMutex); - size_t bytesToWrite = std::min(bytes, dataCapacity - dataSize); - // Write in a single step - if (bytesToWrite <= dataCapacity - endIndex) - { - memcpy(buffer.data() + endIndex, data, bytesToWrite); - endIndex += bytesToWrite; - if (endIndex == dataCapacity) - endIndex = 0; - } + std::lock_guard guard(bufferMutex); + size_t bytesToWrite = std::min(bytes, dataCapacity - dataSize); + // Write in a single step + if (bytesToWrite <= dataCapacity - endIndex) { + memcpy(buffer.data() + endIndex, data, bytesToWrite); + endIndex += bytesToWrite; + if (endIndex == dataCapacity) + endIndex = 0; + } - // Write in two steps - else { - size_t firstChunkSize = dataCapacity - endIndex; - memcpy(buffer.data() + endIndex, data, firstChunkSize); - size_t secondChunkSize = bytesToWrite - firstChunkSize; - memcpy(buffer.data(), data + firstChunkSize, secondChunkSize); - endIndex = secondChunkSize; - } + // Write in two steps + else { + size_t firstChunkSize = dataCapacity - endIndex; + memcpy(buffer.data() + endIndex, data, firstChunkSize); + size_t secondChunkSize = bytesToWrite - firstChunkSize; + memcpy(buffer.data(), data + firstChunkSize, secondChunkSize); + endIndex = secondChunkSize; + } - dataSize += bytesToWrite; + dataSize += bytesToWrite; - // this->dataSemaphore->give(); - return bytesToWrite; + // this->dataSemaphore->give(); + return bytesToWrite; } void CircularBuffer::emptyBuffer() { - std::lock_guard guard(bufferMutex); - begIndex = 0; - dataSize = 0; - endIndex = 0; + std::lock_guard guard(bufferMutex); + begIndex = 0; + dataSize = 0; + endIndex = 0; } void CircularBuffer::emptyExcept(size_t sizeToSet) { - std::lock_guard guard(bufferMutex); - if (sizeToSet > dataSize) - sizeToSet = dataSize; - dataSize = sizeToSet; - endIndex = begIndex + sizeToSet; - if (endIndex > dataCapacity) { - endIndex -= dataCapacity; - } + std::lock_guard guard(bufferMutex); + if (sizeToSet > dataSize) + sizeToSet = dataSize; + dataSize = sizeToSet; + endIndex = begIndex + sizeToSet; + if (endIndex > dataCapacity) { + endIndex -= dataCapacity; + } } -size_t CircularBuffer::read(uint8_t *data, size_t bytes) -{ - if (bytes == 0) - return 0; +size_t CircularBuffer::read(uint8_t* data, size_t bytes) { + if (bytes == 0) + return 0; - std::lock_guard guard(bufferMutex); - size_t bytesToRead = std::min(bytes, dataSize); + std::lock_guard guard(bufferMutex); + size_t bytesToRead = std::min(bytes, dataSize); - // Read in a single step - if (bytesToRead <= dataCapacity - begIndex) - { - memcpy(data, buffer.data() + begIndex, bytesToRead); - begIndex += bytesToRead; - if (begIndex == dataCapacity) - begIndex = 0; - } - // Read in two steps - else - { - size_t firstChunkSize = dataCapacity - begIndex; - memcpy(data, buffer.data() + begIndex, firstChunkSize); - size_t secondChunkSize = bytesToRead - firstChunkSize; - memcpy(data + firstChunkSize, buffer.data(), secondChunkSize); - begIndex = secondChunkSize; - } + // Read in a single step + if (bytesToRead <= dataCapacity - begIndex) { + memcpy(data, buffer.data() + begIndex, bytesToRead); + begIndex += bytesToRead; + if (begIndex == dataCapacity) + begIndex = 0; + } + // Read in two steps + else { + size_t firstChunkSize = dataCapacity - begIndex; + memcpy(data, buffer.data() + begIndex, firstChunkSize); + size_t secondChunkSize = bytesToRead - firstChunkSize; + memcpy(data + firstChunkSize, buffer.data(), secondChunkSize); + begIndex = secondChunkSize; + } - dataSize -= bytesToRead; - return bytesToRead; + dataSize -= bytesToRead; + return bytesToRead; } diff --git a/components/spotify/cspot/bell/main/io/EncodedAudioStream.cpp b/components/spotify/cspot/bell/main/io/EncodedAudioStream.cpp index d8e2e99d..6d80eb8e 100644 --- a/components/spotify/cspot/bell/main/io/EncodedAudioStream.cpp +++ b/components/spotify/cspot/bell/main/io/EncodedAudioStream.cpp @@ -1,6 +1,14 @@ #include "EncodedAudioStream.h" -#include +#include // for memcpy, memmove +#include // for runtime_error +#include // for remove_extent_t +#include // for move + +#include "BellLogger.h" // for AbstractLogger, BELL_LOG, bell +#include "ByteStream.h" // for ByteStream +#include "DecoderGlobals.h" // for DecodersInstance, decodersInstance, AAC_... + using namespace bell; EncodedAudioStream::EncodedAudioStream() { @@ -171,5 +179,4 @@ void EncodedAudioStream::guessDataFormat() { } } -void EncodedAudioStream::readFully(uint8_t* dst, size_t nbytes) { -} +void EncodedAudioStream::readFully(uint8_t* dst, size_t nbytes) {} diff --git a/components/spotify/cspot/bell/main/io/FileStream.cpp b/components/spotify/cspot/bell/main/io/FileStream.cpp index 02db8956..e1d3aaa2 100644 --- a/components/spotify/cspot/bell/main/io/FileStream.cpp +++ b/components/spotify/cspot/bell/main/io/FileStream.cpp @@ -1,70 +1,61 @@ #include "FileStream.h" +#include // for runtime_error + +#include "BellLogger.h" // for bell + using namespace bell; -FileStream::FileStream(const std::string& path, std::string read) -{ - file = fopen(path.c_str(), "rb"); - if (file == NULL) - { - throw std::runtime_error("Could not open file: " + path); - } +FileStream::FileStream(const std::string& path, std::string read) { + file = fopen(path.c_str(), "rb"); + if (file == NULL) { + throw std::runtime_error("Could not open file: " + path); + } } -FileStream::~FileStream() -{ - close(); +FileStream::~FileStream() { + close(); } -size_t FileStream::read(uint8_t *buf, size_t nbytes) -{ - if (file == NULL) - { - throw std::runtime_error("Stream is closed"); - } +size_t FileStream::read(uint8_t* buf, size_t nbytes) { + if (file == NULL) { + throw std::runtime_error("Stream is closed"); + } - return fread(buf, 1, nbytes, file); + return fread(buf, 1, nbytes, file); } -size_t FileStream::skip(size_t nbytes) -{ - if (file == NULL) - { - throw std::runtime_error("Stream is closed"); - } +size_t FileStream::skip(size_t nbytes) { + if (file == NULL) { + throw std::runtime_error("Stream is closed"); + } - return fseek(file, nbytes, SEEK_CUR); + return fseek(file, nbytes, SEEK_CUR); } -size_t FileStream::position() -{ - if (file == NULL) - { - throw std::runtime_error("Stream is closed"); - } +size_t FileStream::position() { + if (file == NULL) { + throw std::runtime_error("Stream is closed"); + } - return ftell(file); + return ftell(file); } -size_t FileStream::size() -{ - if (file == NULL) - { - throw std::runtime_error("Stream is closed"); - } +size_t FileStream::size() { + if (file == NULL) { + throw std::runtime_error("Stream is closed"); + } - size_t pos = ftell(file); - fseek(file, 0, SEEK_END); - size_t size = ftell(file); - fseek(file, pos, SEEK_SET); - return size; + size_t pos = ftell(file); + fseek(file, 0, SEEK_END); + size_t size = ftell(file); + fseek(file, pos, SEEK_SET); + return size; } -void FileStream::close() -{ - if (file != NULL) - { - fclose(file); - file = NULL; - } +void FileStream::close() { + if (file != NULL) { + fclose(file); + file = NULL; + } } \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/io/HTTPClient.cpp b/components/spotify/cspot/bell/main/io/HTTPClient.cpp index b78b46cd..f91ad907 100644 --- a/components/spotify/cspot/bell/main/io/HTTPClient.cpp +++ b/components/spotify/cspot/bell/main/io/HTTPClient.cpp @@ -1,5 +1,14 @@ #include "HTTPClient.h" +#include // for memcpy +#include // for transform +#include // for assert +#include // for tolower +#include // for operator<<, basic_ostream +#include // for runtime_error + +#include "BellSocket.h" // for bell + using namespace bell; void HTTPClient::Response::connect(const std::string& url) { diff --git a/components/spotify/cspot/bell/main/io/SocketStream.cpp b/components/spotify/cspot/bell/main/io/SocketStream.cpp index 2a3bc5a6..2b2b2cab 100644 --- a/components/spotify/cspot/bell/main/io/SocketStream.cpp +++ b/components/spotify/cspot/bell/main/io/SocketStream.cpp @@ -1,9 +1,17 @@ #include "SocketStream.h" +#include // for uint8_t +#include // for NULL, ssize_t + +#include "TCPSocket.h" // for TCPSocket +#include "TLSSocket.h" // for TLSSocket + using namespace bell; int SocketBuffer::open(const std::string& hostname, int port, bool isSSL) { - if (internalSocket != nullptr) { close(); } + if (internalSocket != nullptr) { + close(); + } if (isSSL) { internalSocket = std::make_unique(); } else { diff --git a/components/spotify/cspot/bell/main/io/TLSSocket.cpp b/components/spotify/cspot/bell/main/io/TLSSocket.cpp index 96b5e823..489748d3 100644 --- a/components/spotify/cspot/bell/main/io/TLSSocket.cpp +++ b/components/spotify/cspot/bell/main/io/TLSSocket.cpp @@ -1,5 +1,14 @@ #include "TLSSocket.h" -#include "X509Bundle.h" + +#include // for mbedtls_ctr_drbg_free, mbedtls_ctr_... +#include // for mbedtls_entropy_free, mbedtls_entro... +#include // for mbedtls_net_connect, mbedtls_net_free +#include // for mbedtls_ssl_conf_authmode, mbedtls_... +#include // for strlen, NULL +#include // for runtime_error + +#include "BellLogger.h" // for AbstractLogger, BELL_LOG +#include "X509Bundle.h" // for shouldVerify, attach /** * Platform TLSSocket implementation for the mbedtls diff --git a/components/spotify/cspot/bell/main/io/URLParser.cpp b/components/spotify/cspot/bell/main/io/URLParser.cpp index 55fd21c5..4a50ab1e 100644 --- a/components/spotify/cspot/bell/main/io/URLParser.cpp +++ b/components/spotify/cspot/bell/main/io/URLParser.cpp @@ -4,40 +4,50 @@ namespace bell { #ifdef BELL_DISABLE_REGEX void URLParser::parse(const char* url, std::vector& match) { - match[0] = url; - char scratch[512]; + match[0] = url; + char scratch[512]; - /* Parsing the following (http|https://[host][/path][?query]#hash] as in regex + /* Parsing the following (http|https://[host][/path][?query]#hash] as in regex * below. This needs to be changed if you update that regex */ - - // get the schema - if (sscanf(url, "%[^:]:/", scratch) > 0) match[1] = scratch; - - // get the host - if (sscanf(url, "htt%*[^:]://%512[^/#?]", scratch) > 0) match[2] = scratch; - // get the path - url = strstr(url, match[2].c_str()) + match[2].size(); - if (sscanf(url, "/%512[^?]", scratch) > 0) match[3] = scratch; - else if (*url && *url != '?' && *url != '#') url++; - - // get the query - if (match[3].size()) url += match[3].size() + 1; - if (sscanf(url, "?%512[^#]", scratch) > 0) match[4] = scratch; + // get the schema + if (sscanf(url, "%[^:]:/", scratch) > 0) + match[1] = scratch; - // get the hash - if (match[4].size()) url += match[4].size() + 1; - if (sscanf(url, "#%512s", scratch) > 0) match[5] = scratch; + // get the host + if (sscanf(url, "htt%*[^:]://%512[^/#?]", scratch) > 0) + match[2] = scratch; - // fix the acquired items - match[3] = "/" + match[3]; - if (match[4].size()) match[4] = "?" + match[4]; + // get the path + url = strstr(url, match[2].c_str()) + match[2].size(); + if (sscanf(url, "/%512[^?]", scratch) > 0) + match[3] = scratch; + else if (*url && *url != '?' && *url != '#') + url++; - // need at least schema and host - if (match[1].size() == 0 || match[2].size() == 0) match.clear(); -} -#else + // get the query + if (match[3].size()) + url += match[3].size() + 1; + if (sscanf(url, "?%512[^#]", scratch) > 0) + match[4] = scratch; + + // get the hash + if (match[4].size()) + url += match[4].size() + 1; + if (sscanf(url, "#%512s", scratch) > 0) + match[5] = scratch; + + // fix the acquired items + match[3] = "/" + match[3]; + if (match[4].size()) + match[4] = "?" + match[4]; + + // need at least schema and host + if (match[1].size() == 0 || match[2].size() == 0) + match.clear(); +} +#else const std::regex URLParser::urlParseRegex = std::regex( - "^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(\\?(?:[^#]*))?(#(?:.*))?"); + "^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(\\?(?:[^#]*))?(#(?:.*))?"); #endif -} +} // namespace bell diff --git a/components/spotify/cspot/bell/main/io/X509Bundle.cpp b/components/spotify/cspot/bell/main/io/X509Bundle.cpp index 8b7b757b..ffcdd134 100644 --- a/components/spotify/cspot/bell/main/io/X509Bundle.cpp +++ b/components/spotify/cspot/bell/main/io/X509Bundle.cpp @@ -1,5 +1,15 @@ #include "X509Bundle.h" +#include // for mbedtls_md, mbedtls_md_get_size +#include // for mbedtls_pk_can_do, mbedtls_pk_pa... +#include // for mbedtls_ssl_conf_ca_chain, mbedt... +#include // for mbedtls_x509_buf, MBEDTLS_ERR_X5... +#include // for free, calloc +#include // for memcmp, memcpy +#include // for runtime_error + +#include "BellLogger.h" // for AbstractLogger, BELL_LOG + using namespace bell::X509Bundle; static mbedtls_x509_crt s_dummy_crt; @@ -21,7 +31,8 @@ int bell::X509Bundle::crtCheckCertificate(mbedtls_x509_crt* child, if ((ret = mbedtls_pk_parse_public_key(&parent.pk, pub_key_buf, pub_key_len)) != 0) { - BELL_LOG(error, TAG, "PK parse failed with error %X", ret); + BELL_LOG(error, TAG, "PK parse failed with error 0x%04x, key len = %d", ret, + pub_key_len); goto cleanup; } @@ -110,6 +121,8 @@ int bell::X509Bundle::crtVerifyCallback(void* buf, mbedtls_x509_crt* crt, ret = crtCheckCertificate( child, s_crt_bundle.crts[middle] + CRT_HEADER_OFFSET + name_len, key_len); + } else { + BELL_LOG(error, TAG, "Certificate not found in bundle"); } if (ret == 0) { @@ -138,10 +151,13 @@ void bell::X509Bundle::init(const uint8_t* x509_bundle, size_t bundle_size) { throw std::runtime_error("Unable to allocate memory for bundle"); } + bundleBytes.resize(bundle_size); + memcpy(bundleBytes.data(), x509_bundle, bundle_size); + const uint8_t* cur_crt; /* This is the maximum region that is allowed to access */ - const uint8_t* bundle_end = x509_bundle + bundle_size; - cur_crt = x509_bundle + BUNDLE_HEADER_OFFSET; + const uint8_t* bundle_end = bundleBytes.data() + bundle_size; + cur_crt = bundleBytes.data() + BUNDLE_HEADER_OFFSET; for (int i = 0; i < num_certs; i++) { crts[i] = cur_crt; diff --git a/components/spotify/cspot/bell/main/io/include/BellHTTPServer.h b/components/spotify/cspot/bell/main/io/include/BellHTTPServer.h index fc58dd96..c65a3587 100644 --- a/components/spotify/cspot/bell/main/io/include/BellHTTPServer.h +++ b/components/spotify/cspot/bell/main/io/include/BellHTTPServer.h @@ -1,22 +1,18 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "CivetServer.h" -#include "civetweb.h" +#include // for bell +#include // for uint8_t +#include // for free, size_t +#include // for function +#include // for map +#include // for unique_ptr +#include // for mutex +#include // for string, hash, operator==, operator< +#include // for unordered_map +#include // for pair +#include // for vector + +#include "CivetServer.h" // for CivetServer, CivetHandler using namespace bell; namespace bell { @@ -46,7 +42,9 @@ class BellHTTPServer : public CivetHandler { } } }; - typedef std::function(struct mg_connection* conn)> HTTPHandler; + typedef std::function( + struct mg_connection* conn)> + HTTPHandler; typedef std::function WSStateHandler; typedef std::function @@ -79,7 +77,8 @@ class BellHTTPServer : public CivetHandler { std::vector getListeningPorts() { return server->getListeningPorts(); }; void close() { server->close(); } - std::unique_ptr makeJsonResponse(const std::string& json, int status = 200); + std::unique_ptr makeJsonResponse(const std::string& json, + int status = 200); std::unique_ptr makeEmptyResponse(); void registerNotFound(HTTPHandler handler); @@ -88,7 +87,8 @@ class BellHTTPServer : public CivetHandler { void registerWS(const std::string&, WSDataHandler dataHandler, WSStateHandler stateHandler); - static std::unordered_map extractParams(struct mg_connection* conn); + static std::unordered_map extractParams( + struct mg_connection* conn); private: std::unique_ptr server; diff --git a/components/spotify/cspot/bell/main/io/include/BellSocket.h b/components/spotify/cspot/bell/main/io/include/BellSocket.h index 5c887377..5eff94eb 100644 --- a/components/spotify/cspot/bell/main/io/include/BellSocket.h +++ b/components/spotify/cspot/bell/main/io/include/BellSocket.h @@ -14,6 +14,6 @@ class Socket { virtual size_t read(uint8_t* buf, size_t len) = 0; virtual bool isOpen() = 0; virtual void close() = 0; + virtual int getFd() = 0; }; } // namespace bell - diff --git a/components/spotify/cspot/bell/main/io/include/BellTar.h b/components/spotify/cspot/bell/main/io/include/BellTar.h index 1f4af0d4..ef1b7e6a 100644 --- a/components/spotify/cspot/bell/main/io/include/BellTar.h +++ b/components/spotify/cspot/bell/main/io/include/BellTar.h @@ -1,8 +1,7 @@ #pragma once -#include -#include -#include +#include // for istream, ostream +#include // for string namespace bell::BellTar { typedef long long unsigned file_size_t; diff --git a/components/spotify/cspot/bell/main/io/include/BinaryReader.h b/components/spotify/cspot/bell/main/io/include/BinaryReader.h index dd0a4f9a..25befc9a 100644 --- a/components/spotify/cspot/bell/main/io/include/BinaryReader.h +++ b/components/spotify/cspot/bell/main/io/include/BinaryReader.h @@ -1,31 +1,28 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include "ByteStream.h" +#include // for uint8_t, int16_t, int32_t, uint32_t +#include // for size_t +#include // for shared_ptr +#include // for vector -namespace bell -{ - class BinaryReader - { - std::shared_ptr stream; - size_t currentPos = 0; +namespace bell { +class ByteStream; - public: - BinaryReader(std::shared_ptr stream); - int32_t readInt(); - int16_t readShort(); - uint32_t readUInt(); - long long readLong(); - void close(); - uint8_t readByte(); - size_t size(); - size_t position(); - std::vector readBytes(size_t); - void skip(size_t); - }; -} \ No newline at end of file +class BinaryReader { + std::shared_ptr stream; + size_t currentPos = 0; + + public: + BinaryReader(std::shared_ptr stream); + int32_t readInt(); + int16_t readShort(); + uint32_t readUInt(); + long long readLong(); + void close(); + uint8_t readByte(); + size_t size(); + size_t position(); + std::vector readBytes(size_t); + void skip(size_t); +}; +} // namespace bell \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/io/include/BinaryStream.h b/components/spotify/cspot/bell/main/io/include/BinaryStream.h index 1da7677a..78cdd66a 100644 --- a/components/spotify/cspot/bell/main/io/include/BinaryStream.h +++ b/components/spotify/cspot/bell/main/io/include/BinaryStream.h @@ -1,10 +1,11 @@ #pragma once #ifndef ESP_PLATFORM -#include +#include // for endian #endif -#include -#include +#include // for int16_t, int32_t, int64_t, uint16_t, uint32_t +#include // for byte +#include // for istream, ostream namespace bell { class BinaryStream { diff --git a/components/spotify/cspot/bell/main/io/include/BufferedStream.h b/components/spotify/cspot/bell/main/io/include/BufferedStream.h index 94041062..2acce8f8 100644 --- a/components/spotify/cspot/bell/main/io/include/BufferedStream.h +++ b/components/spotify/cspot/bell/main/io/include/BufferedStream.h @@ -1,12 +1,16 @@ #pragma once -#include "ByteStream.h" -#include "BellTask.h" -#include "WrappedSemaphore.h" -#include -#include -#include -#include +#include // for size_t +#include // for uint32_t, uint8_t +#include // for atomic +#include // for function +#include // for shared_ptr +#include // for mutex +#include // for string + +#include "BellTask.h" // for Task +#include "ByteStream.h" // for ByteStream +#include "WrappedSemaphore.h" // for WrappedSemaphore /** * This class implements a wrapper around an arbitrary bell::ByteStream, @@ -26,12 +30,12 @@ * method correctly, such as that 0 is returned if, and only if the stream ends. */ class BufferedStream : public bell::ByteStream, bell::Task { - public: - typedef std::shared_ptr StreamPtr; - typedef std::function StreamReader; + public: + typedef std::shared_ptr StreamPtr; + typedef std::function StreamReader; - public: - /** + public: + /** * @param taskName name to use for the reading task * @param bufferSize total size of the reading buffer * @param readThreshold how much can be read before refilling the buffer @@ -41,22 +45,18 @@ class BufferedStream : public bell::ByteStream, bell::Task { * @param waitForReady whether to wait for the buffer to be ready during reading * @param endWithSource whether to end the streaming as soon as source returns 0 from read() */ - BufferedStream( - const std::string &taskName, - uint32_t bufferSize, - uint32_t readThreshold, - uint32_t readSize, - uint32_t readyThreshold, - uint32_t notReadyThreshold, - bool waitForReady = false); - ~BufferedStream() override; - bool open(const StreamPtr &stream); - bool open(const StreamReader &newReader, uint32_t initialOffset = 0); - void close() override; + BufferedStream(const std::string& taskName, uint32_t bufferSize, + uint32_t readThreshold, uint32_t readSize, + uint32_t readyThreshold, uint32_t notReadyThreshold, + bool waitForReady = false); + ~BufferedStream() override; + bool open(const StreamPtr& stream); + bool open(const StreamReader& newReader, uint32_t initialOffset = 0); + void close() override; - // inherited methods - public: - /** + // inherited methods + public: + /** * Read len bytes from the buffer to dst. If waitForReady is enabled * and readAvailable is lower than notReadyThreshold, the function * will block until readyThreshold bytes is available. @@ -65,61 +65,63 @@ class BufferedStream : public bell::ByteStream, bell::Task { * if the buffer does not contain len bytes available), or 0 if the source * stream is already closed and there is no reader attached. */ - size_t read(uint8_t *dst, size_t len) override; - size_t skip(size_t len) override; - size_t position() override; - size_t size() override; + size_t read(uint8_t* dst, size_t len) override; + size_t skip(size_t len) override; + size_t position() override; + size_t size() override; - // stream status - public: - /** + // stream status + public: + /** * Total amount of bytes served to read(). */ - uint32_t readTotal; - /** + uint32_t readTotal; + /** * Total amount of bytes read from source. */ - uint32_t bufferTotal; - /** + uint32_t bufferTotal; + /** * Amount of bytes available to read from the buffer. */ - std::atomic readAvailable; - /** + std::atomic readAvailable; + /** * Whether the caller should start reading the data. This indicates that a safe * amount (determined by readyThreshold) of data is available in the buffer. */ - bool isReady() const; - /** + bool isReady() const; + /** * Whether the caller should stop reading the data. This indicates that the amount of data * available for reading is decreasing to a non-safe value, as data is being read * faster than it can be buffered. */ - bool isNotReady() const; - /** + bool isNotReady() const; + /** * Semaphore that is given when the buffer becomes ready (isReady() == true). Caller can * wait for the semaphore instead of continuously querying isReady(). */ - bell::WrappedSemaphore readySem; + bell::WrappedSemaphore readySem; - private: - std::mutex runningMutex; - bool running = false; - bool terminate = false; - bell::WrappedSemaphore readSem; // signal to start writing to buffer after reading from it - std::mutex readMutex; // mutex for locking read operations during writing, and vice versa - uint32_t bufferSize; - uint32_t readAt; - uint32_t readSize; - uint32_t readyThreshold; - uint32_t notReadyThreshold; - bool waitForReady; - uint8_t *buf; - uint8_t *bufEnd; - uint8_t *bufReadPtr; - uint8_t *bufWritePtr; - StreamPtr source; - StreamReader reader; - void runTask() override; - void reset(); - uint32_t lengthBetween(uint8_t *me, uint8_t *other); + private: + std::mutex runningMutex; + bool running = false; + bool terminate = false; + bell::WrappedSemaphore + readSem; // signal to start writing to buffer after reading from it + std::mutex + readMutex; // mutex for locking read operations during writing, and vice versa + uint32_t bufferSize; + uint32_t readAt; + uint32_t readSize; + uint32_t readyThreshold; + uint32_t notReadyThreshold; + bool waitForReady; + uint8_t* buf; + uint8_t* bufEnd; + uint8_t* bufReadPtr; + uint8_t* bufWritePtr; + StreamPtr source; + StreamReader reader; + void runTask() override; + void reset(); + uint32_t lengthBetween(uint8_t* me, uint8_t* other); }; diff --git a/components/spotify/cspot/bell/main/io/include/ByteStream.h b/components/spotify/cspot/bell/main/io/include/ByteStream.h index 21b09420..aad1ebf1 100644 --- a/components/spotify/cspot/bell/main/io/include/ByteStream.h +++ b/components/spotify/cspot/bell/main/io/include/ByteStream.h @@ -1,27 +1,25 @@ #ifndef BELL_BYTE_READER_H #define BELL_BYTE_READER_H -#include #include +#include /** * A class for reading bytes from a stream. Further implemented in HTTPStream.h */ -namespace bell -{ - class ByteStream - { - public: - ByteStream(){}; - virtual ~ByteStream() = default; +namespace bell { +class ByteStream { + public: + ByteStream(){}; + virtual ~ByteStream() = default; - virtual size_t read(uint8_t *buf, size_t nbytes) = 0; - virtual size_t skip(size_t nbytes) = 0; + virtual size_t read(uint8_t* buf, size_t nbytes) = 0; + virtual size_t skip(size_t nbytes) = 0; - virtual size_t position() = 0; - virtual size_t size() = 0; - virtual void close() = 0; - }; -} + virtual size_t position() = 0; + virtual size_t size() = 0; + virtual void close() = 0; +}; +} // namespace bell #endif diff --git a/components/spotify/cspot/bell/main/io/include/CircularBuffer.h b/components/spotify/cspot/bell/main/io/include/CircularBuffer.h index df4e4a17..08a66b05 100644 --- a/components/spotify/cspot/bell/main/io/include/CircularBuffer.h +++ b/components/spotify/cspot/bell/main/io/include/CircularBuffer.h @@ -1,39 +1,35 @@ #pragma once -#include -#include -#include -#include -#include -#include "WrappedSemaphore.h" +#include // for uint8_t +#include // for size_t +#include // for unique_ptr +#include // for mutex +#include // for vector +#include "WrappedSemaphore.h" // for WrappedSemaphore namespace bell { class CircularBuffer { - public: - CircularBuffer(size_t dataCapacity); + public: + CircularBuffer(size_t dataCapacity); - std::unique_ptr dataSemaphore; + std::unique_ptr dataSemaphore; - size_t size() const { - return dataSize; - } + size_t size() const { return dataSize; } - size_t capacity() const { - return dataCapacity; - } + size_t capacity() const { return dataCapacity; } - size_t write(const uint8_t *data, size_t bytes); - size_t read(uint8_t *data, size_t bytes); - void emptyBuffer(); - void emptyExcept(size_t size); + size_t write(const uint8_t* data, size_t bytes); + size_t read(uint8_t* data, size_t bytes); + void emptyBuffer(); + void emptyExcept(size_t size); - private: - std::mutex bufferMutex; - size_t begIndex = 0; - size_t endIndex = 0; - size_t dataSize = 0; - size_t dataCapacity = 0; - std::vector buffer; + private: + std::mutex bufferMutex; + size_t begIndex = 0; + size_t endIndex = 0; + size_t dataSize = 0; + size_t dataCapacity = 0; + std::vector buffer; }; -} // namespace bell \ No newline at end of file +} // namespace bell \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/io/include/EncodedAudioStream.h b/components/spotify/cspot/bell/main/io/include/EncodedAudioStream.h index 2938f9d4..ce8a682e 100644 --- a/components/spotify/cspot/bell/main/io/include/EncodedAudioStream.h +++ b/components/spotify/cspot/bell/main/io/include/EncodedAudioStream.h @@ -1,15 +1,17 @@ #pragma once -#include -#include +#include // for size_t +#include // for uint8_t +#include // for shared_ptr, unique_ptr +#include // for basic_string, string +#include // for vector -#include "BellLogger.h" -#include "ByteStream.h" -#include "DecoderGlobals.h" -#include "aacdec.h" -#include "mp3dec.h" +#include "aacdec.h" // for AACFrameInfo +#include "mp3dec.h" // for MP3FrameInfo namespace bell { +class ByteStream; + class EncodedAudioStream { public: EncodedAudioStream(); diff --git a/components/spotify/cspot/bell/main/io/include/FileStream.h b/components/spotify/cspot/bell/main/io/include/FileStream.h index 4456affd..ae3ae514 100644 --- a/components/spotify/cspot/bell/main/io/include/FileStream.h +++ b/components/spotify/cspot/bell/main/io/include/FileStream.h @@ -1,10 +1,9 @@ #pragma once -#include -#include -#include -#include -#include +#include // for ByteStream +#include // for uint8_t +#include // for size_t, FILE +#include // for string /* * FileStream @@ -12,17 +11,15 @@ * A class for reading and writing to files implementing the ByteStream interface. * */ -namespace bell -{ - class FileStream : public ByteStream - { - public: - FileStream(const std::string& path, std::string mode); - ~FileStream(); +namespace bell { +class FileStream : public ByteStream { + public: + FileStream(const std::string& path, std::string mode); + ~FileStream(); - FILE* file; + FILE* file; - /* + /* * Reads data from the stream. * * @param buf The buffer to read data into. @@ -30,18 +27,18 @@ namespace bell * @return The number of bytes read. * @throws std::runtime_error if the stream is closed. */ - size_t read(uint8_t *buf, size_t nbytes); + size_t read(uint8_t* buf, size_t nbytes); - /* + /* * Skips nbytes bytes in the stream. */ - size_t skip(size_t nbytes); + size_t skip(size_t nbytes); - size_t position(); + size_t position(); - size_t size(); + size_t size(); - // Closes the connection - void close(); - }; -} + // Closes the connection + void close(); +}; +} // namespace bell diff --git a/components/spotify/cspot/bell/main/io/include/HTTPClient.h b/components/spotify/cspot/bell/main/io/include/HTTPClient.h index 7ce164d9..052ce6c6 100644 --- a/components/spotify/cspot/bell/main/io/include/HTTPClient.h +++ b/components/spotify/cspot/bell/main/io/include/HTTPClient.h @@ -1,24 +1,20 @@ #pragma once -#include +#include // for size_t +#include // for uint8_t, int32_t +#include // for make_unique, unique_ptr +#include // for string +#include // for string_view +#include // for pair +#include // for vector -#include -#include -#include -#include -#include -#include -#include - -#include "BellSocket.h" -#include "ByteStream.h" -#include "SocketStream.h" -#include "URLParser.h" +#include "SocketStream.h" // for SocketStream +#include "URLParser.h" // for URLParser #ifndef BELL_DISABLE_FMT -#include "fmt/core.h" +#include "fmt/core.h" // for format #endif -#include "picohttpparser.h" +#include "picohttpparser.h" // for phr_header namespace bell { class HTTPClient { @@ -31,19 +27,20 @@ class HTTPClient { // Helper over ValueHeader, formatting a HTTP bytes range struct RangeHeader { static ValueHeader range(int32_t from, int32_t to) { -#ifndef BELL_DISABLE_FMT +#ifndef BELL_DISABLE_FMT return ValueHeader{"Range", fmt::format("bytes={}-{}", from, to)}; -#else - return ValueHeader{"Range", "bytes=" + std::to_string(from) + "-" + std::to_string(to)}; -#endif +#else + return ValueHeader{ + "Range", "bytes=" + std::to_string(from) + "-" + std::to_string(to)}; +#endif } static ValueHeader last(int32_t nbytes) { -#ifndef BELL_DISABLE_FMT +#ifndef BELL_DISABLE_FMT return ValueHeader{"Range", fmt::format("bytes=-{}", nbytes)}; -#else +#else return ValueHeader{"Range", "bytes=-" + std::to_string(nbytes)}; -#endif +#endif } }; diff --git a/components/spotify/cspot/bell/main/io/include/SocketStream.h b/components/spotify/cspot/bell/main/io/include/SocketStream.h index 3b7030f1..7ed6770f 100644 --- a/components/spotify/cspot/bell/main/io/include/SocketStream.h +++ b/components/spotify/cspot/bell/main/io/include/SocketStream.h @@ -1,8 +1,10 @@ #pragma once -#include -#include "TCPSocket.h" -#include "TLSSocket.h" +#include // for streamsize, basic_streambuf<>::int_type, ios... +#include // for unique_ptr, operator!= +#include // for char_traits, string + +#include "BellSocket.h" // for Socket namespace bell { class SocketBuffer : public std::streambuf { diff --git a/components/spotify/cspot/bell/main/io/include/TCPSocket.h b/components/spotify/cspot/bell/main/io/include/TCPSocket.h index e28f0e76..73ae6ebf 100644 --- a/components/spotify/cspot/bell/main/io/include/TCPSocket.h +++ b/components/spotify/cspot/bell/main/io/include/TCPSocket.h @@ -39,6 +39,8 @@ class TCPSocket : public bell::Socket { TCPSocket(){}; ~TCPSocket() { close(); }; + int getFd() { return sockFd; } + void open(const std::string& host, uint16_t port) { int err; int domain = AF_INET; @@ -101,7 +103,9 @@ class TCPSocket : public bell::Socket { #endif return value; } - bool isOpen() { return !isClosed; } + bool isOpen() { + return !isClosed; + } void close() { if (!isClosed) { diff --git a/components/spotify/cspot/bell/main/io/include/TLSSocket.h b/components/spotify/cspot/bell/main/io/include/TLSSocket.h index e3d5f6a0..c3c43a5c 100644 --- a/components/spotify/cspot/bell/main/io/include/TLSSocket.h +++ b/components/spotify/cspot/bell/main/io/include/TLSSocket.h @@ -1,34 +1,21 @@ #ifndef BELL_TLS_SOCKET_H #define BELL_TLS_SOCKET_H -#include -#include -#include -#include -#include -#include "BellLogger.h" -#include "BellSocket.h" +#include // for uint8_t, uint16_t + +#include "BellSocket.h" // for Socket #ifdef _WIN32 #include #include #else -#include -#include -#include -#include -#include #endif -#include -#include -#include -#include -#include +#include // for size_t +#include // for string -#include "mbedtls/ctr_drbg.h" -#include "mbedtls/debug.h" -#include "mbedtls/entropy.h" -#include "mbedtls/net_sockets.h" -#include "mbedtls/ssl.h" +#include "mbedtls/ctr_drbg.h" // for mbedtls_ctr_drbg_context +#include "mbedtls/entropy.h" // for mbedtls_entropy_context +#include "mbedtls/net_sockets.h" // for mbedtls_net_context +#include "mbedtls/ssl.h" // for mbedtls_ssl_config, mbedtls_ssl_con... namespace bell { class TLSSocket : public bell::Socket { @@ -53,6 +40,7 @@ class TLSSocket : public bell::Socket { bool isOpen(); void close(); + int getFd() { return server_fd.fd; } }; } // namespace bell diff --git a/components/spotify/cspot/bell/main/io/include/URLParser.h b/components/spotify/cspot/bell/main/io/include/URLParser.h index 3778c91b..98a59929 100644 --- a/components/spotify/cspot/bell/main/io/include/URLParser.h +++ b/components/spotify/cspot/bell/main/io/include/URLParser.h @@ -1,9 +1,9 @@ #pragma once -#include -#include -#include -#include +#include // for strtol, size_t +#include // for match_results, match_results<>::value_type, sub... +#include // for invalid_argument +#include // for string, allocator, operator+, char_traits, oper... namespace bell { class URLParser { @@ -60,7 +60,7 @@ class URLParser { std::string path; #ifdef BELL_DISABLE_REGEX void parse(const char* url, std::vector& match); -#else +#else static const std::regex urlParseRegex; #endif @@ -71,10 +71,10 @@ class URLParser { #ifdef BELL_DISABLE_REGEX std::vector match(6); parser.parse(url.c_str(), match); -#else +#else std::cmatch match; std::regex_match(url.c_str(), match, parser.urlParseRegex); -#endif +#endif if (match.size() < 3) { throw std::invalid_argument("Invalid URL"); diff --git a/components/spotify/cspot/bell/main/io/include/X509Bundle.h b/components/spotify/cspot/bell/main/io/include/X509Bundle.h index d9ed17cd..118dbcdc 100644 --- a/components/spotify/cspot/bell/main/io/include/X509Bundle.h +++ b/components/spotify/cspot/bell/main/io/include/X509Bundle.h @@ -1,8 +1,11 @@ #pragma once -#include -#include "BellLogger.h" -#include "mbedtls/ssl.h" +#include // for mbedtls_x509_crt +#include // for size_t +#include // for uint8_t, uint16_t, uint32_t +#include // for vector + +#include "mbedtls/ssl.h" // for mbedtls_ssl_config namespace bell::X509Bundle { @@ -13,6 +16,7 @@ typedef struct crt_bundle_t { } crt_bundle_t; static crt_bundle_t s_crt_bundle; +static std::vector bundleBytes; static constexpr auto TAG = "X509Bundle"; static constexpr auto CRT_HEADER_OFFSET = 4; diff --git a/components/spotify/cspot/bell/main/io/include/picohttpparser.h b/components/spotify/cspot/bell/main/io/include/picohttpparser.h index 2c685928..b0f663e6 100644 --- a/components/spotify/cspot/bell/main/io/include/picohttpparser.h +++ b/components/spotify/cspot/bell/main/io/include/picohttpparser.h @@ -40,30 +40,35 @@ extern "C" { /* contains name and value of a header (name == NULL if is a continuing line * of a multiline header */ struct phr_header { - const char *name; - size_t name_len; - const char *value; - size_t value_len; + const char* name; + size_t name_len; + const char* value; + size_t value_len; }; /* returns number of bytes consumed if successful, -2 if request is partial, * -1 if failed */ -int phr_parse_request(const char *buf, size_t len, const char **method, size_t *method_len, const char **path, size_t *path_len, - int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len); +int phr_parse_request(const char* buf, size_t len, const char** method, + size_t* method_len, const char** path, size_t* path_len, + int* minor_version, struct phr_header* headers, + size_t* num_headers, size_t last_len); /* ditto */ -int phr_parse_response(const char *_buf, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len, - struct phr_header *headers, size_t *num_headers, size_t last_len); +int phr_parse_response(const char* _buf, size_t len, int* minor_version, + int* status, const char** msg, size_t* msg_len, + struct phr_header* headers, size_t* num_headers, + size_t last_len); /* ditto */ -int phr_parse_headers(const char *buf, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len); +int phr_parse_headers(const char* buf, size_t len, struct phr_header* headers, + size_t* num_headers, size_t last_len); /* should be zero-filled before start */ struct phr_chunked_decoder { - size_t bytes_left_in_chunk; /* number of bytes left in current chunk */ - char consume_trailer; /* if trailing headers should be consumed */ - char _hex_count; - char _state; + size_t bytes_left_in_chunk; /* number of bytes left in current chunk */ + char consume_trailer; /* if trailing headers should be consumed */ + char _hex_count; + char _state; }; /* the function rewrites the buffer given as (buf, bufsz) removing the chunked- @@ -75,10 +80,11 @@ struct phr_chunked_decoder { * octets left undecoded, that starts from the offset returned by `*bufsz`. * Returns -1 on error. */ -ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *bufsz); +ssize_t phr_decode_chunked(struct phr_chunked_decoder* decoder, char* buf, + size_t* bufsz); /* returns if the chunked decoder is in middle of chunked data */ -int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder); +int phr_decode_chunked_is_in_data(struct phr_chunked_decoder* decoder); #ifdef __cplusplus } diff --git a/components/spotify/cspot/bell/main/io/picohttpparser.c b/components/spotify/cspot/bell/main/io/picohttpparser.c index 5e5783ab..b4070466 100644 --- a/components/spotify/cspot/bell/main/io/picohttpparser.c +++ b/components/spotify/cspot/bell/main/io/picohttpparser.c @@ -24,9 +24,10 @@ * IN THE SOFTWARE. */ -#include -#include -#include +#include // for assert +#include // for NULL, size_t +#include // for memmove +#include // for ssize_t #ifdef __SSE4_2__ #ifdef _MSC_VER #include @@ -34,7 +35,7 @@ #include #endif #endif -#include "picohttpparser.h" +#include "picohttpparser.h" // for phr_chunked_decoder, phr_header, phr_dec... #if __GNUC__ >= 3 #define likely(x) __builtin_expect(!!(x), 1) @@ -52,612 +53,634 @@ #define IS_PRINTABLE_ASCII(c) ((unsigned char)(c)-040u < 0137u) -#define CHECK_EOF() \ - if (buf == buf_end) { \ - *ret = -2; \ - return NULL; \ - } +#define CHECK_EOF() \ + if (buf == buf_end) { \ + *ret = -2; \ + return NULL; \ + } -#define EXPECT_CHAR_NO_CHECK(ch) \ - if (*buf++ != ch) { \ - *ret = -1; \ - return NULL; \ - } +#define EXPECT_CHAR_NO_CHECK(ch) \ + if (*buf++ != ch) { \ + *ret = -1; \ + return NULL; \ + } -#define EXPECT_CHAR(ch) \ - CHECK_EOF(); \ - EXPECT_CHAR_NO_CHECK(ch); +#define EXPECT_CHAR(ch) \ + CHECK_EOF(); \ + EXPECT_CHAR_NO_CHECK(ch); -#define ADVANCE_TOKEN(tok, toklen) \ - do { \ - const char *tok_start = buf; \ - static const char ALIGNED(16) ranges2[16] = "\000\040\177\177"; \ - int found2; \ - buf = findchar_fast(buf, buf_end, ranges2, 4, &found2); \ - if (!found2) { \ - CHECK_EOF(); \ - } \ - while (1) { \ - if (*buf == ' ') { \ - break; \ - } else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \ - if ((unsigned char)*buf < '\040' || *buf == '\177') { \ - *ret = -1; \ - return NULL; \ - } \ - } \ - ++buf; \ - CHECK_EOF(); \ - } \ - tok = tok_start; \ - toklen = buf - tok_start; \ - } while (0) +#define ADVANCE_TOKEN(tok, toklen) \ + do { \ + const char* tok_start = buf; \ + static const char ALIGNED(16) ranges2[16] = "\000\040\177\177"; \ + int found2; \ + buf = findchar_fast(buf, buf_end, ranges2, 4, &found2); \ + if (!found2) { \ + CHECK_EOF(); \ + } \ + while (1) { \ + if (*buf == ' ') { \ + break; \ + } else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \ + if ((unsigned char)*buf < '\040' || *buf == '\177') { \ + *ret = -1; \ + return NULL; \ + } \ + } \ + ++buf; \ + CHECK_EOF(); \ + } \ + tok = tok_start; \ + toklen = buf - tok_start; \ + } while (0) -static const char *token_char_map = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0" - "\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1" - "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; +static const char* token_char_map = + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0" + "\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1" + "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; -static const char *findchar_fast(const char *buf, const char *buf_end, const char *ranges, size_t ranges_size, int *found) -{ - *found = 0; +static const char* findchar_fast(const char* buf, const char* buf_end, + const char* ranges, size_t ranges_size, + int* found) { + *found = 0; #if __SSE4_2__ - if (likely(buf_end - buf >= 16)) { - __m128i ranges16 = _mm_loadu_si128((const __m128i *)ranges); + if (likely(buf_end - buf >= 16)) { + __m128i ranges16 = _mm_loadu_si128((const __m128i*)ranges); - size_t left = (buf_end - buf) & ~15; - do { - __m128i b16 = _mm_loadu_si128((const __m128i *)buf); - int r = _mm_cmpestri(ranges16, ranges_size, b16, 16, _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS); - if (unlikely(r != 16)) { - buf += r; - *found = 1; - break; - } - buf += 16; - left -= 16; - } while (likely(left != 0)); - } + size_t left = (buf_end - buf) & ~15; + do { + __m128i b16 = _mm_loadu_si128((const __m128i*)buf); + int r = _mm_cmpestri( + ranges16, ranges_size, b16, 16, + _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS); + if (unlikely(r != 16)) { + buf += r; + *found = 1; + break; + } + buf += 16; + left -= 16; + } while (likely(left != 0)); + } #else - /* suppress unused parameter warning */ - (void)buf_end; - (void)ranges; - (void)ranges_size; + /* suppress unused parameter warning */ + (void)buf_end; + (void)ranges; + (void)ranges_size; #endif - return buf; + return buf; } -static const char *get_token_to_eol(const char *buf, const char *buf_end, const char **token, size_t *token_len, int *ret) -{ - const char *token_start = buf; +static const char* get_token_to_eol(const char* buf, const char* buf_end, + const char** token, size_t* token_len, + int* ret) { + const char* token_start = buf; #ifdef __SSE4_2__ - static const char ALIGNED(16) ranges1[16] = "\0\010" /* allow HT */ - "\012\037" /* allow SP and up to but not including DEL */ - "\177\177"; /* allow chars w. MSB set */ - int found; - buf = findchar_fast(buf, buf_end, ranges1, 6, &found); - if (found) - goto FOUND_CTL; + static const char ALIGNED(16) ranges1[16] = + "\0\010" /* allow HT */ + "\012\037" /* allow SP and up to but not including DEL */ + "\177\177"; /* allow chars w. MSB set */ + int found; + buf = findchar_fast(buf, buf_end, ranges1, 6, &found); + if (found) + goto FOUND_CTL; #else - /* find non-printable char within the next 8 bytes, this is the hottest code; manually inlined */ - while (likely(buf_end - buf >= 8)) { -#define DOIT() \ - do { \ - if (unlikely(!IS_PRINTABLE_ASCII(*buf))) \ - goto NonPrintable; \ - ++buf; \ - } while (0) - DOIT(); - DOIT(); - DOIT(); - DOIT(); - DOIT(); - DOIT(); - DOIT(); - DOIT(); + /* find non-printable char within the next 8 bytes, this is the hottest code; manually inlined */ + while (likely(buf_end - buf >= 8)) { +#define DOIT() \ + do { \ + if (unlikely(!IS_PRINTABLE_ASCII(*buf))) \ + goto NonPrintable; \ + ++buf; \ + } while (0) + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); #undef DOIT - continue; - NonPrintable: - if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) { - goto FOUND_CTL; - } - ++buf; + continue; + NonPrintable: + if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || + unlikely(*buf == '\177')) { + goto FOUND_CTL; } + ++buf; + } #endif - for (;; ++buf) { - CHECK_EOF(); - if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { - if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) { - goto FOUND_CTL; - } - } + for (;; ++buf) { + CHECK_EOF(); + if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { + if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || + unlikely(*buf == '\177')) { + goto FOUND_CTL; + } } + } FOUND_CTL: - if (likely(*buf == '\015')) { - ++buf; - EXPECT_CHAR('\012'); - *token_len = buf - 2 - token_start; - } else if (*buf == '\012') { - *token_len = buf - token_start; - ++buf; - } else { - *ret = -1; - return NULL; - } - *token = token_start; - - return buf; -} - -static const char *is_complete(const char *buf, const char *buf_end, size_t last_len, int *ret) -{ - int ret_cnt = 0; - buf = last_len < 3 ? buf : buf + last_len - 3; - - while (1) { - CHECK_EOF(); - if (*buf == '\015') { - ++buf; - CHECK_EOF(); - EXPECT_CHAR('\012'); - ++ret_cnt; - } else if (*buf == '\012') { - ++buf; - ++ret_cnt; - } else { - ++buf; - ret_cnt = 0; - } - if (ret_cnt == 2) { - return buf; - } - } - - *ret = -2; + if (likely(*buf == '\015')) { + ++buf; + EXPECT_CHAR('\012'); + *token_len = buf - 2 - token_start; + } else if (*buf == '\012') { + *token_len = buf - token_start; + ++buf; + } else { + *ret = -1; return NULL; + } + *token = token_start; + + return buf; } -#define PARSE_INT(valp_, mul_) \ - if (*buf < '0' || '9' < *buf) { \ - buf++; \ - *ret = -1; \ - return NULL; \ - } \ - *(valp_) = (mul_) * (*buf++ - '0'); +static const char* is_complete(const char* buf, const char* buf_end, + size_t last_len, int* ret) { + int ret_cnt = 0; + buf = last_len < 3 ? buf : buf + last_len - 3; -#define PARSE_INT_3(valp_) \ - do { \ - int res_ = 0; \ - PARSE_INT(&res_, 100) \ - *valp_ = res_; \ - PARSE_INT(&res_, 10) \ - *valp_ += res_; \ - PARSE_INT(&res_, 1) \ - *valp_ += res_; \ - } while (0) - -/* returned pointer is always within [buf, buf_end), or null */ -static const char *parse_token(const char *buf, const char *buf_end, const char **token, size_t *token_len, char next_char, - int *ret) -{ - /* We use pcmpestri to detect non-token characters. This instruction can take no more than eight character ranges (8*2*8=128 - * bits that is the size of a SSE register). Due to this restriction, characters `|` and `~` are handled in the slow loop. */ - static const char ALIGNED(16) ranges[] = "\x00 " /* control chars and up to SP */ - "\"\"" /* 0x22 */ - "()" /* 0x28,0x29 */ - ",," /* 0x2c */ - "//" /* 0x2f */ - ":@" /* 0x3a-0x40 */ - "[]" /* 0x5b-0x5d */ - "{\xff"; /* 0x7b-0xff */ - const char *buf_start = buf; - int found; - buf = findchar_fast(buf, buf_end, ranges, sizeof(ranges) - 1, &found); - if (!found) { - CHECK_EOF(); - } - while (1) { - if (*buf == next_char) { - break; - } else if (!token_char_map[(unsigned char)*buf]) { - *ret = -1; - return NULL; - } - ++buf; - CHECK_EOF(); - } - *token = buf_start; - *token_len = buf - buf_start; - return buf; -} - -/* returned pointer is always within [buf, buf_end), or null */ -static const char *parse_http_version(const char *buf, const char *buf_end, int *minor_version, int *ret) -{ - /* we want at least [HTTP/1.] to try to parse */ - if (buf_end - buf < 9) { - *ret = -2; - return NULL; - } - EXPECT_CHAR_NO_CHECK('H'); - EXPECT_CHAR_NO_CHECK('T'); - EXPECT_CHAR_NO_CHECK('T'); - EXPECT_CHAR_NO_CHECK('P'); - EXPECT_CHAR_NO_CHECK('/'); - EXPECT_CHAR_NO_CHECK('1'); - EXPECT_CHAR_NO_CHECK('.'); - PARSE_INT(minor_version, 1); - return buf; -} - -static const char *parse_headers(const char *buf, const char *buf_end, struct phr_header *headers, size_t *num_headers, - size_t max_headers, int *ret) -{ - for (;; ++*num_headers) { - CHECK_EOF(); - if (*buf == '\015') { - ++buf; - EXPECT_CHAR('\012'); - break; - } else if (*buf == '\012') { - ++buf; - break; - } - if (*num_headers == max_headers) { - *ret = -1; - return NULL; - } - if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) { - /* parsing name, but do not discard SP before colon, see - * http://www.mozilla.org/security/announce/2006/mfsa2006-33.html */ - if ((buf = parse_token(buf, buf_end, &headers[*num_headers].name, &headers[*num_headers].name_len, ':', ret)) == NULL) { - return NULL; - } - if (headers[*num_headers].name_len == 0) { - *ret = -1; - return NULL; - } - ++buf; - for (;; ++buf) { - CHECK_EOF(); - if (!(*buf == ' ' || *buf == '\t')) { - break; - } - } - } else { - headers[*num_headers].name = NULL; - headers[*num_headers].name_len = 0; - } - const char *value; - size_t value_len; - if ((buf = get_token_to_eol(buf, buf_end, &value, &value_len, ret)) == NULL) { - return NULL; - } - /* remove trailing SPs and HTABs */ - const char *value_end = value + value_len; - for (; value_end != value; --value_end) { - const char c = *(value_end - 1); - if (!(c == ' ' || c == '\t')) { - break; - } - } - headers[*num_headers].value = value; - headers[*num_headers].value_len = value_end - value; - } - return buf; -} - -static const char *parse_request(const char *buf, const char *buf_end, const char **method, size_t *method_len, const char **path, - size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, - size_t max_headers, int *ret) -{ - /* skip first empty line (some clients add CRLF after POST content) */ + while (1) { CHECK_EOF(); if (*buf == '\015') { - ++buf; - EXPECT_CHAR('\012'); + ++buf; + CHECK_EOF(); + EXPECT_CHAR('\012'); + ++ret_cnt; } else if (*buf == '\012') { - ++buf; + ++buf; + ++ret_cnt; + } else { + ++buf; + ret_cnt = 0; } + if (ret_cnt == 2) { + return buf; + } + } - /* parse request line */ - if ((buf = parse_token(buf, buf_end, method, method_len, ' ', ret)) == NULL) { - return NULL; - } - do { - ++buf; - CHECK_EOF(); - } while (*buf == ' '); - ADVANCE_TOKEN(*path, *path_len); - do { - ++buf; - CHECK_EOF(); - } while (*buf == ' '); - if (*method_len == 0 || *path_len == 0) { - *ret = -1; - return NULL; - } - if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) { - return NULL; + *ret = -2; + return NULL; +} + +#define PARSE_INT(valp_, mul_) \ + if (*buf < '0' || '9' < *buf) { \ + buf++; \ + *ret = -1; \ + return NULL; \ + } \ + *(valp_) = (mul_) * (*buf++ - '0'); + +#define PARSE_INT_3(valp_) \ + do { \ + int res_ = 0; \ + PARSE_INT(&res_, 100) \ + *valp_ = res_; \ + PARSE_INT(&res_, 10) \ + *valp_ += res_; \ + PARSE_INT(&res_, 1) \ + *valp_ += res_; \ + } while (0) + +/* returned pointer is always within [buf, buf_end), or null */ +static const char* parse_token(const char* buf, const char* buf_end, + const char** token, size_t* token_len, + char next_char, int* ret) { + /* We use pcmpestri to detect non-token characters. This instruction can take no more than eight character ranges (8*2*8=128 + * bits that is the size of a SSE register). Due to this restriction, characters `|` and `~` are handled in the slow loop. */ + static const char ALIGNED(16) ranges[] = + "\x00 " /* control chars and up to SP */ + "\"\"" /* 0x22 */ + "()" /* 0x28,0x29 */ + ",," /* 0x2c */ + "//" /* 0x2f */ + ":@" /* 0x3a-0x40 */ + "[]" /* 0x5b-0x5d */ + "{\xff"; /* 0x7b-0xff */ + const char* buf_start = buf; + int found; + buf = findchar_fast(buf, buf_end, ranges, sizeof(ranges) - 1, &found); + if (!found) { + CHECK_EOF(); + } + while (1) { + if (*buf == next_char) { + break; + } else if (!token_char_map[(unsigned char)*buf]) { + *ret = -1; + return NULL; } + ++buf; + CHECK_EOF(); + } + *token = buf_start; + *token_len = buf - buf_start; + return buf; +} + +/* returned pointer is always within [buf, buf_end), or null */ +static const char* parse_http_version(const char* buf, const char* buf_end, + int* minor_version, int* ret) { + /* we want at least [HTTP/1.] to try to parse */ + if (buf_end - buf < 9) { + *ret = -2; + return NULL; + } + EXPECT_CHAR_NO_CHECK('H'); + EXPECT_CHAR_NO_CHECK('T'); + EXPECT_CHAR_NO_CHECK('T'); + EXPECT_CHAR_NO_CHECK('P'); + EXPECT_CHAR_NO_CHECK('/'); + EXPECT_CHAR_NO_CHECK('1'); + EXPECT_CHAR_NO_CHECK('.'); + PARSE_INT(minor_version, 1); + return buf; +} + +static const char* parse_headers(const char* buf, const char* buf_end, + struct phr_header* headers, + size_t* num_headers, size_t max_headers, + int* ret) { + for (;; ++*num_headers) { + CHECK_EOF(); if (*buf == '\015') { - ++buf; - EXPECT_CHAR('\012'); + ++buf; + EXPECT_CHAR('\012'); + break; } else if (*buf == '\012') { - ++buf; - } else { + ++buf; + break; + } + if (*num_headers == max_headers) { + *ret = -1; + return NULL; + } + if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) { + /* parsing name, but do not discard SP before colon, see + * http://www.mozilla.org/security/announce/2006/mfsa2006-33.html */ + if ((buf = parse_token(buf, buf_end, &headers[*num_headers].name, + &headers[*num_headers].name_len, ':', ret)) == + NULL) { + return NULL; + } + if (headers[*num_headers].name_len == 0) { *ret = -1; return NULL; - } - - return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); -} - -int phr_parse_request(const char *buf_start, size_t len, const char **method, size_t *method_len, const char **path, - size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len) -{ - const char *buf = buf_start, *buf_end = buf_start + len; - size_t max_headers = *num_headers; - int r; - - *method = NULL; - *method_len = 0; - *path = NULL; - *path_len = 0; - *minor_version = -1; - *num_headers = 0; - - /* if last_len != 0, check if the request is complete (a fast countermeasure - againt slowloris */ - if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { - return r; - } - - if ((buf = parse_request(buf, buf_end, method, method_len, path, path_len, minor_version, headers, num_headers, max_headers, - &r)) == NULL) { - return r; - } - - return (int)(buf - buf_start); -} - -static const char *parse_response(const char *buf, const char *buf_end, int *minor_version, int *status, const char **msg, - size_t *msg_len, struct phr_header *headers, size_t *num_headers, size_t max_headers, int *ret) -{ - /* parse "HTTP/1.x" */ - if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) { - return NULL; - } - /* skip space */ - if (*buf != ' ') { - *ret = -1; - return NULL; - } - do { - ++buf; + } + ++buf; + for (;; ++buf) { CHECK_EOF(); - } while (*buf == ' '); - /* parse status code, we want at least [:digit:][:digit:][:digit:] to try to parse */ - if (buf_end - buf < 4) { - *ret = -2; - return NULL; - } - PARSE_INT_3(status); - - /* get message including preceding space */ - if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) { - return NULL; - } - if (*msg_len == 0) { - /* ok */ - } else if (**msg == ' ') { - /* Remove preceding space. Successful return from `get_token_to_eol` guarantees that we would hit something other than SP - * before running past the end of the given buffer. */ - do { - ++*msg; - --*msg_len; - } while (**msg == ' '); + if (!(*buf == ' ' || *buf == '\t')) { + break; + } + } } else { - /* garbage found after status code */ - *ret = -1; - return NULL; + headers[*num_headers].name = NULL; + headers[*num_headers].name_len = 0; } - - return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); + const char* value; + size_t value_len; + if ((buf = get_token_to_eol(buf, buf_end, &value, &value_len, ret)) == + NULL) { + return NULL; + } + /* remove trailing SPs and HTABs */ + const char* value_end = value + value_len; + for (; value_end != value; --value_end) { + const char c = *(value_end - 1); + if (!(c == ' ' || c == '\t')) { + break; + } + } + headers[*num_headers].value = value; + headers[*num_headers].value_len = value_end - value; + } + return buf; } -int phr_parse_response(const char *buf_start, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len, - struct phr_header *headers, size_t *num_headers, size_t last_len) -{ - const char *buf = buf_start, *buf_end = buf + len; - size_t max_headers = *num_headers; - int r; +static const char* parse_request(const char* buf, const char* buf_end, + const char** method, size_t* method_len, + const char** path, size_t* path_len, + int* minor_version, struct phr_header* headers, + size_t* num_headers, size_t max_headers, + int* ret) { + /* skip first empty line (some clients add CRLF after POST content) */ + CHECK_EOF(); + if (*buf == '\015') { + ++buf; + EXPECT_CHAR('\012'); + } else if (*buf == '\012') { + ++buf; + } - *minor_version = -1; - *status = 0; - *msg = NULL; - *msg_len = 0; - *num_headers = 0; + /* parse request line */ + if ((buf = parse_token(buf, buf_end, method, method_len, ' ', ret)) == NULL) { + return NULL; + } + do { + ++buf; + CHECK_EOF(); + } while (*buf == ' '); + ADVANCE_TOKEN(*path, *path_len); + do { + ++buf; + CHECK_EOF(); + } while (*buf == ' '); + if (*method_len == 0 || *path_len == 0) { + *ret = -1; + return NULL; + } + if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) { + return NULL; + } + if (*buf == '\015') { + ++buf; + EXPECT_CHAR('\012'); + } else if (*buf == '\012') { + ++buf; + } else { + *ret = -1; + return NULL; + } - /* if last_len != 0, check if the response is complete (a fast countermeasure - against slowloris */ - if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { - return r; - } - - if ((buf = parse_response(buf, buf_end, minor_version, status, msg, msg_len, headers, num_headers, max_headers, &r)) == NULL) { - return r; - } - - return (int)(buf - buf_start); + return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); } -int phr_parse_headers(const char *buf_start, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len) -{ - const char *buf = buf_start, *buf_end = buf + len; - size_t max_headers = *num_headers; - int r; +int phr_parse_request(const char* buf_start, size_t len, const char** method, + size_t* method_len, const char** path, size_t* path_len, + int* minor_version, struct phr_header* headers, + size_t* num_headers, size_t last_len) { + const char *buf = buf_start, *buf_end = buf_start + len; + size_t max_headers = *num_headers; + int r; - *num_headers = 0; + *method = NULL; + *method_len = 0; + *path = NULL; + *path_len = 0; + *minor_version = -1; + *num_headers = 0; - /* if last_len != 0, check if the response is complete (a fast countermeasure + /* if last_len != 0, check if the request is complete (a fast countermeasure + againt slowloris */ + if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { + return r; + } + + if ((buf = parse_request(buf, buf_end, method, method_len, path, path_len, + minor_version, headers, num_headers, max_headers, + &r)) == NULL) { + return r; + } + + return (int)(buf - buf_start); +} + +static const char* parse_response(const char* buf, const char* buf_end, + int* minor_version, int* status, + const char** msg, size_t* msg_len, + struct phr_header* headers, + size_t* num_headers, size_t max_headers, + int* ret) { + /* parse "HTTP/1.x" */ + if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) { + return NULL; + } + /* skip space */ + if (*buf != ' ') { + *ret = -1; + return NULL; + } + do { + ++buf; + CHECK_EOF(); + } while (*buf == ' '); + /* parse status code, we want at least [:digit:][:digit:][:digit:] to try to parse */ + if (buf_end - buf < 4) { + *ret = -2; + return NULL; + } + PARSE_INT_3(status); + + /* get message including preceding space */ + if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) { + return NULL; + } + if (*msg_len == 0) { + /* ok */ + } else if (**msg == ' ') { + /* Remove preceding space. Successful return from `get_token_to_eol` guarantees that we would hit something other than SP + * before running past the end of the given buffer. */ + do { + ++*msg; + --*msg_len; + } while (**msg == ' '); + } else { + /* garbage found after status code */ + *ret = -1; + return NULL; + } + + return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); +} + +int phr_parse_response(const char* buf_start, size_t len, int* minor_version, + int* status, const char** msg, size_t* msg_len, + struct phr_header* headers, size_t* num_headers, + size_t last_len) { + const char *buf = buf_start, *buf_end = buf + len; + size_t max_headers = *num_headers; + int r; + + *minor_version = -1; + *status = 0; + *msg = NULL; + *msg_len = 0; + *num_headers = 0; + + /* if last_len != 0, check if the response is complete (a fast countermeasure against slowloris */ - if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { - return r; - } + if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { + return r; + } - if ((buf = parse_headers(buf, buf_end, headers, num_headers, max_headers, &r)) == NULL) { - return r; - } + if ((buf = parse_response(buf, buf_end, minor_version, status, msg, msg_len, + headers, num_headers, max_headers, &r)) == NULL) { + return r; + } - return (int)(buf - buf_start); + return (int)(buf - buf_start); +} + +int phr_parse_headers(const char* buf_start, size_t len, + struct phr_header* headers, size_t* num_headers, + size_t last_len) { + const char *buf = buf_start, *buf_end = buf + len; + size_t max_headers = *num_headers; + int r; + + *num_headers = 0; + + /* if last_len != 0, check if the response is complete (a fast countermeasure + against slowloris */ + if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { + return r; + } + + if ((buf = parse_headers(buf, buf_end, headers, num_headers, max_headers, + &r)) == NULL) { + return r; + } + + return (int)(buf - buf_start); } enum { - CHUNKED_IN_CHUNK_SIZE, - CHUNKED_IN_CHUNK_EXT, - CHUNKED_IN_CHUNK_DATA, - CHUNKED_IN_CHUNK_CRLF, - CHUNKED_IN_TRAILERS_LINE_HEAD, - CHUNKED_IN_TRAILERS_LINE_MIDDLE + CHUNKED_IN_CHUNK_SIZE, + CHUNKED_IN_CHUNK_EXT, + CHUNKED_IN_CHUNK_DATA, + CHUNKED_IN_CHUNK_CRLF, + CHUNKED_IN_TRAILERS_LINE_HEAD, + CHUNKED_IN_TRAILERS_LINE_MIDDLE }; -static int decode_hex(int ch) -{ - if ('0' <= ch && ch <= '9') { - return ch - '0'; - } else if ('A' <= ch && ch <= 'F') { - return ch - 'A' + 0xa; - } else if ('a' <= ch && ch <= 'f') { - return ch - 'a' + 0xa; - } else { - return -1; - } +static int decode_hex(int ch) { + if ('0' <= ch && ch <= '9') { + return ch - '0'; + } else if ('A' <= ch && ch <= 'F') { + return ch - 'A' + 0xa; + } else if ('a' <= ch && ch <= 'f') { + return ch - 'a' + 0xa; + } else { + return -1; + } } -ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *_bufsz) -{ - size_t dst = 0, src = 0, bufsz = *_bufsz; - ssize_t ret = -2; /* incomplete */ +ssize_t phr_decode_chunked(struct phr_chunked_decoder* decoder, char* buf, + size_t* _bufsz) { + size_t dst = 0, src = 0, bufsz = *_bufsz; + ssize_t ret = -2; /* incomplete */ - while (1) { - switch (decoder->_state) { - case CHUNKED_IN_CHUNK_SIZE: - for (;; ++src) { - int v; - if (src == bufsz) - goto Exit; - if ((v = decode_hex(buf[src])) == -1) { - if (decoder->_hex_count == 0) { - ret = -1; - goto Exit; - } - break; - } - if (decoder->_hex_count == sizeof(size_t) * 2) { - ret = -1; - goto Exit; - } - decoder->bytes_left_in_chunk = decoder->bytes_left_in_chunk * 16 + v; - ++decoder->_hex_count; + while (1) { + switch (decoder->_state) { + case CHUNKED_IN_CHUNK_SIZE: + for (;; ++src) { + int v; + if (src == bufsz) + goto Exit; + if ((v = decode_hex(buf[src])) == -1) { + if (decoder->_hex_count == 0) { + ret = -1; + goto Exit; } - decoder->_hex_count = 0; - decoder->_state = CHUNKED_IN_CHUNK_EXT; - /* fallthru */ - case CHUNKED_IN_CHUNK_EXT: - /* RFC 7230 A.2 "Line folding in chunk extensions is disallowed" */ - for (;; ++src) { - if (src == bufsz) - goto Exit; - if (buf[src] == '\012') - break; - } - ++src; - if (decoder->bytes_left_in_chunk == 0) { - if (decoder->consume_trailer) { - decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD; - break; - } else { - goto Complete; - } - } - decoder->_state = CHUNKED_IN_CHUNK_DATA; - /* fallthru */ - case CHUNKED_IN_CHUNK_DATA: { - size_t avail = bufsz - src; - if (avail < decoder->bytes_left_in_chunk) { - if (dst != src) - memmove(buf + dst, buf + src, avail); - src += avail; - dst += avail; - decoder->bytes_left_in_chunk -= avail; - goto Exit; - } - if (dst != src) - memmove(buf + dst, buf + src, decoder->bytes_left_in_chunk); - src += decoder->bytes_left_in_chunk; - dst += decoder->bytes_left_in_chunk; - decoder->bytes_left_in_chunk = 0; - decoder->_state = CHUNKED_IN_CHUNK_CRLF; - } - /* fallthru */ - case CHUNKED_IN_CHUNK_CRLF: - for (;; ++src) { - if (src == bufsz) - goto Exit; - if (buf[src] != '\015') - break; - } - if (buf[src] != '\012') { - ret = -1; - goto Exit; - } - ++src; - decoder->_state = CHUNKED_IN_CHUNK_SIZE; break; - case CHUNKED_IN_TRAILERS_LINE_HEAD: - for (;; ++src) { - if (src == bufsz) - goto Exit; - if (buf[src] != '\015') - break; - } - if (buf[src++] == '\012') - goto Complete; - decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE; - /* fallthru */ - case CHUNKED_IN_TRAILERS_LINE_MIDDLE: - for (;; ++src) { - if (src == bufsz) - goto Exit; - if (buf[src] == '\012') - break; - } - ++src; + } + if (decoder->_hex_count == sizeof(size_t) * 2) { + ret = -1; + goto Exit; + } + decoder->bytes_left_in_chunk = decoder->bytes_left_in_chunk * 16 + v; + ++decoder->_hex_count; + } + decoder->_hex_count = 0; + decoder->_state = CHUNKED_IN_CHUNK_EXT; + /* fallthru */ + case CHUNKED_IN_CHUNK_EXT: + /* RFC 7230 A.2 "Line folding in chunk extensions is disallowed" */ + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] == '\012') + break; + } + ++src; + if (decoder->bytes_left_in_chunk == 0) { + if (decoder->consume_trailer) { decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD; break; - default: - assert(!"decoder is corrupt"); + } else { + goto Complete; + } } + decoder->_state = CHUNKED_IN_CHUNK_DATA; + /* fallthru */ + case CHUNKED_IN_CHUNK_DATA: { + size_t avail = bufsz - src; + if (avail < decoder->bytes_left_in_chunk) { + if (dst != src) + memmove(buf + dst, buf + src, avail); + src += avail; + dst += avail; + decoder->bytes_left_in_chunk -= avail; + goto Exit; + } + if (dst != src) + memmove(buf + dst, buf + src, decoder->bytes_left_in_chunk); + src += decoder->bytes_left_in_chunk; + dst += decoder->bytes_left_in_chunk; + decoder->bytes_left_in_chunk = 0; + decoder->_state = CHUNKED_IN_CHUNK_CRLF; + } + /* fallthru */ + case CHUNKED_IN_CHUNK_CRLF: + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] != '\015') + break; + } + if (buf[src] != '\012') { + ret = -1; + goto Exit; + } + ++src; + decoder->_state = CHUNKED_IN_CHUNK_SIZE; + break; + case CHUNKED_IN_TRAILERS_LINE_HEAD: + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] != '\015') + break; + } + if (buf[src++] == '\012') + goto Complete; + decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE; + /* fallthru */ + case CHUNKED_IN_TRAILERS_LINE_MIDDLE: + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] == '\012') + break; + } + ++src; + decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD; + break; + default: + assert(!"decoder is corrupt"); } + } Complete: - ret = bufsz - src; + ret = bufsz - src; Exit: - if (dst != src) - memmove(buf + dst, buf + src, bufsz - src); - *_bufsz = dst; - return ret; + if (dst != src) + memmove(buf + dst, buf + src, bufsz - src); + *_bufsz = dst; + return ret; } -int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder) -{ - return decoder->_state == CHUNKED_IN_CHUNK_DATA; +int phr_decode_chunked_is_in_data(struct phr_chunked_decoder* decoder) { + return decoder->_state == CHUNKED_IN_CHUNK_DATA; } #undef CHECK_EOF diff --git a/components/spotify/cspot/bell/main/platform/MDNSService.h b/components/spotify/cspot/bell/main/platform/MDNSService.h index f7389a41..ad372314 100644 --- a/components/spotify/cspot/bell/main/platform/MDNSService.h +++ b/components/spotify/cspot/bell/main/platform/MDNSService.h @@ -1,23 +1,19 @@ #pragma once -#include -#include -#include +#include // for map +#include // for unique_ptr +#include // for string namespace bell { class MDNSService { -public: - virtual ~MDNSService() { } - static std::unique_ptr registerService( - const std::string &serviceName, - const std::string &serviceType, - const std::string &serviceProto, - const std::string &serviceHost, - int servicePort, - const std::map txtData - ); - virtual void unregisterService() = 0; + public: + virtual ~MDNSService() {} + static std::unique_ptr registerService( + const std::string& serviceName, const std::string& serviceType, + const std::string& serviceProto, const std::string& serviceHost, + int servicePort, const std::map txtData); + virtual void unregisterService() = 0; }; -} // namespace bell \ No newline at end of file +} // namespace bell \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/platform/WrappedSemaphore.h b/components/spotify/cspot/bell/main/platform/WrappedSemaphore.h index be27ac70..e6a5b264 100644 --- a/components/spotify/cspot/bell/main/platform/WrappedSemaphore.h +++ b/components/spotify/cspot/bell/main/platform/WrappedSemaphore.h @@ -4,7 +4,7 @@ #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #elif __APPLE__ -#include +#include // for dispatch_semaphore_t #elif _WIN32 #include #else @@ -15,24 +15,24 @@ namespace bell { class WrappedSemaphore { - private: + private: #ifdef ESP_PLATFORM - SemaphoreHandle_t semaphoreHandle; + SemaphoreHandle_t semaphoreHandle; #elif __APPLE__ - dispatch_semaphore_t semaphoreHandle; + dispatch_semaphore_t semaphoreHandle; #elif _WIN32 - HANDLE semaphoreHandle; + HANDLE semaphoreHandle; #else - sem_t semaphoreHandle; + sem_t semaphoreHandle; #endif - public: - WrappedSemaphore(int maxVal = 200); - ~WrappedSemaphore(); + public: + WrappedSemaphore(int maxVal = 200); + ~WrappedSemaphore(); - int wait(); - int twait(long milliseconds = 10); - void give(); + int wait(); + int twait(long milliseconds = 10); + void give(); }; -} // namespace bell +} // namespace bell diff --git a/components/spotify/cspot/bell/main/platform/apple/MDNSService.cpp b/components/spotify/cspot/bell/main/platform/apple/MDNSService.cpp index 1948302f..c872f656 100644 --- a/components/spotify/cspot/bell/main/platform/apple/MDNSService.cpp +++ b/components/spotify/cspot/bell/main/platform/apple/MDNSService.cpp @@ -1,16 +1,20 @@ #include "MDNSService.h" -#include "dns_sd.h" -#include + +#include // for NULL +#include // for pair + +#include "dns_sd.h" // for DNSServiceRef, DNSServiceRefDeallocate, DNS... +#include "i386/endian.h" // for htons using namespace bell; class implMDNSService : public MDNSService { -private: - DNSServiceRef* service; + private: + DNSServiceRef* service; -public: - implMDNSService(DNSServiceRef* service) : service(service) { } - void unregisterService() { DNSServiceRefDeallocate(*service); } + public: + implMDNSService(DNSServiceRef* service) : service(service) {} + void unregisterService() { DNSServiceRefDeallocate(*service); } }; /** @@ -18,33 +22,30 @@ public: * @see https://developer.apple.com/documentation/dnssd/1804733-dnsserviceregister **/ std::unique_ptr MDNSService::registerService( - const std::string& serviceName, - const std::string& serviceType, - const std::string& serviceProto, - const std::string& serviceHost, - int servicePort, - const std::map txtData -) { - DNSServiceRef* ref = new DNSServiceRef(); - TXTRecordRef txtRecord; - TXTRecordCreate(&txtRecord, 0, NULL); - for (auto& data : txtData) { - TXTRecordSetValue(&txtRecord, data.first.c_str(), data.second.size(), data.second.c_str()); - } - DNSServiceRegister( - ref, /* sdRef */ - 0, /* flags */ - 0, /* interfaceIndex */ - serviceName.c_str(), /* name */ - (serviceType + "." + serviceProto).c_str(), /* regType (_spotify-connect._tcp) */ - NULL, /* domain */ - NULL, /* host */ - htons(servicePort), /* port */ - TXTRecordGetLength(&txtRecord), /* txtLen */ - TXTRecordGetBytesPtr(&txtRecord), /* txtRecord */ - NULL, /* callBack */ - NULL /* context */ - ); - TXTRecordDeallocate(&txtRecord); - return std::make_unique(ref); -} \ No newline at end of file + const std::string& serviceName, const std::string& serviceType, + const std::string& serviceProto, const std::string& serviceHost, + int servicePort, const std::map txtData) { + DNSServiceRef* ref = new DNSServiceRef(); + TXTRecordRef txtRecord; + TXTRecordCreate(&txtRecord, 0, NULL); + for (auto& data : txtData) { + TXTRecordSetValue(&txtRecord, data.first.c_str(), data.second.size(), + data.second.c_str()); + } + DNSServiceRegister(ref, /* sdRef */ + 0, /* flags */ + 0, /* interfaceIndex */ + serviceName.c_str(), /* name */ + (serviceType + "." + serviceProto) + .c_str(), /* regType (_spotify-connect._tcp) */ + NULL, /* domain */ + NULL, /* host */ + htons(servicePort), /* port */ + TXTRecordGetLength(&txtRecord), /* txtLen */ + TXTRecordGetBytesPtr(&txtRecord), /* txtRecord */ + NULL, /* callBack */ + NULL /* context */ + ); + TXTRecordDeallocate(&txtRecord); + return std::make_unique(ref); +} diff --git a/components/spotify/cspot/bell/main/platform/apple/WrappedSemaphore.cpp b/components/spotify/cspot/bell/main/platform/apple/WrappedSemaphore.cpp index 505f974b..4fc146ff 100644 --- a/components/spotify/cspot/bell/main/platform/apple/WrappedSemaphore.cpp +++ b/components/spotify/cspot/bell/main/platform/apple/WrappedSemaphore.cpp @@ -2,30 +2,26 @@ using namespace bell; -WrappedSemaphore::WrappedSemaphore(int count) -{ - semaphoreHandle = dispatch_semaphore_create(0); +WrappedSemaphore::WrappedSemaphore(int count) { + semaphoreHandle = dispatch_semaphore_create(0); } -WrappedSemaphore::~WrappedSemaphore() -{ - dispatch_release(semaphoreHandle); +WrappedSemaphore::~WrappedSemaphore() { + dispatch_release(semaphoreHandle); } -int WrappedSemaphore::wait() -{ +int WrappedSemaphore::wait() { - return dispatch_semaphore_wait(semaphoreHandle, DISPATCH_TIME_FOREVER); + return dispatch_semaphore_wait(semaphoreHandle, DISPATCH_TIME_FOREVER); } -int WrappedSemaphore::twait(long milliseconds) -{ - dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (NSEC_PER_SEC / 1000) * milliseconds); +int WrappedSemaphore::twait(long milliseconds) { + dispatch_time_t timeout = + dispatch_time(DISPATCH_TIME_NOW, (NSEC_PER_SEC / 1000) * milliseconds); - return dispatch_semaphore_wait(semaphoreHandle, timeout); + return dispatch_semaphore_wait(semaphoreHandle, timeout); } -void WrappedSemaphore::give() -{ - dispatch_semaphore_signal(semaphoreHandle); +void WrappedSemaphore::give() { + dispatch_semaphore_signal(semaphoreHandle); } diff --git a/components/spotify/cspot/bell/main/platform/esp/MDNSService.cpp b/components/spotify/cspot/bell/main/platform/esp/MDNSService.cpp index 81f269cf..b568cecc 100644 --- a/components/spotify/cspot/bell/main/platform/esp/MDNSService.cpp +++ b/components/spotify/cspot/bell/main/platform/esp/MDNSService.cpp @@ -6,13 +6,14 @@ using namespace bell; class implMDNSService : public MDNSService { -private: - const std::string type; - const std::string proto; - void unregisterService() { mdns_service_remove(type.c_str(), proto.c_str()); } + private: + const std::string type; + const std::string proto; + void unregisterService() { mdns_service_remove(type.c_str(), proto.c_str()); } -public: - implMDNSService(std::string type, std::string proto) : type(type), proto(proto) { }; + public: + implMDNSService(std::string type, std::string proto) + : type(type), proto(proto){}; }; /** @@ -21,30 +22,25 @@ public: **/ std::unique_ptr MDNSService::registerService( - const std::string& serviceName, - const std::string& serviceType, - const std::string& serviceProto, - const std::string& serviceHost, - int servicePort, - const std::map txtData -) { - std::vector txtItems; - txtItems.reserve(txtData.size()); - for (auto& data : txtData) { - mdns_txt_item_t item; - item.key = data.first.c_str(); - item.value = data.second.c_str(); - txtItems.push_back(item); - } + const std::string& serviceName, const std::string& serviceType, + const std::string& serviceProto, const std::string& serviceHost, + int servicePort, const std::map txtData) { + std::vector txtItems; + txtItems.reserve(txtData.size()); + for (auto& data : txtData) { + mdns_txt_item_t item; + item.key = data.first.c_str(); + item.value = data.second.c_str(); + txtItems.push_back(item); + } - mdns_service_add( - serviceName.c_str(), /* instance_name */ - serviceType.c_str(), /* service_type */ - serviceProto.c_str(), /* proto */ - servicePort, /* port */ - txtItems.data(), /* txt */ - txtItems.size() /* num_items */ - ); + mdns_service_add(serviceName.c_str(), /* instance_name */ + serviceType.c_str(), /* service_type */ + serviceProto.c_str(), /* proto */ + servicePort, /* port */ + txtItems.data(), /* txt */ + txtItems.size() /* num_items */ + ); - return std::make_unique(serviceType, serviceProto); + return std::make_unique(serviceType, serviceProto); } diff --git a/components/spotify/cspot/bell/main/platform/esp/WrappedSemaphore.cpp b/components/spotify/cspot/bell/main/platform/esp/WrappedSemaphore.cpp index 01d7e4c6..af988b8f 100644 --- a/components/spotify/cspot/bell/main/platform/esp/WrappedSemaphore.cpp +++ b/components/spotify/cspot/bell/main/platform/esp/WrappedSemaphore.cpp @@ -6,36 +6,32 @@ using namespace bell; -WrappedSemaphore::WrappedSemaphore(int count) -{ - semaphoreHandle = xSemaphoreCreateCounting(count, 0); +WrappedSemaphore::WrappedSemaphore(int count) { + semaphoreHandle = xSemaphoreCreateCounting(count, 0); } -WrappedSemaphore::~WrappedSemaphore() -{ - vSemaphoreDelete(semaphoreHandle); +WrappedSemaphore::~WrappedSemaphore() { + vSemaphoreDelete(semaphoreHandle); } -int WrappedSemaphore::wait() -{ - if (xSemaphoreTake(semaphoreHandle, portMAX_DELAY) == pdTRUE) { - return 0; - } +int WrappedSemaphore::wait() { + if (xSemaphoreTake(semaphoreHandle, portMAX_DELAY) == pdTRUE) { + return 0; + } - return 1; + return 1; } -int WrappedSemaphore::twait(long milliseconds) -{ - if (xSemaphoreTake(semaphoreHandle, milliseconds / portTICK_PERIOD_MS) == pdTRUE) { - return 0; - } +int WrappedSemaphore::twait(long milliseconds) { + if (xSemaphoreTake(semaphoreHandle, milliseconds / portTICK_PERIOD_MS) == + pdTRUE) { + return 0; + } - return 1; + return 1; } -void WrappedSemaphore::give() -{ +void WrappedSemaphore::give() { - xSemaphoreGive(semaphoreHandle); + xSemaphoreGive(semaphoreHandle); } diff --git a/components/spotify/cspot/bell/main/platform/linux/MDNSService.cpp b/components/spotify/cspot/bell/main/platform/linux/MDNSService.cpp index 0109890b..0a9f36f2 100644 --- a/components/spotify/cspot/bell/main/platform/linux/MDNSService.cpp +++ b/components/spotify/cspot/bell/main/platform/linux/MDNSService.cpp @@ -1,9 +1,10 @@ -#include -#include #include +#include #include #include -#include +#include +#include +#include #if __has_include("avahi-client/client.h") #include @@ -14,40 +15,42 @@ #define BELL_DISABLE_AVAHI #endif -#include "mdnssvc.h" #include "BellLogger.h" #include "MDNSService.h" +#include "mdnssvc.h" using namespace bell; #ifndef BELL_DISABLE_AVAHI -static void groupHandler(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata) { } +static void groupHandler(AvahiEntryGroup* g, AvahiEntryGroupState state, + AVAHI_GCC_UNUSED void* userdata) {} #endif class implMDNSService : public MDNSService { -private: + private: #ifndef BELL_DISABLE_AVAHI - AvahiEntryGroup *avahiGroup; + AvahiEntryGroup* avahiGroup; #endif - struct mdns_service* service; + struct mdns_service* service; -public: + public: #ifndef BELL_DISABLE_AVAHI - static AvahiClient *avahiClient; - static AvahiSimplePoll *avahiPoll; + static AvahiClient* avahiClient; + static AvahiSimplePoll* avahiPoll; #endif - static struct mdnsd* mdnsServer; - static in_addr_t host; + static struct mdnsd* mdnsServer; + static in_addr_t host; - implMDNSService(struct mdns_service* service) : service(service) { }; -#ifndef BELL_DISABLE_AVAHI - implMDNSService(AvahiEntryGroup *avahiGroup) : avahiGroup(avahiGroup) { }; -#endif - void unregisterService(); + implMDNSService(struct mdns_service* service) : service(service){}; +#ifndef BELL_DISABLE_AVAHI + implMDNSService(AvahiEntryGroup* avahiGroup) : avahiGroup(avahiGroup){}; +#endif + void unregisterService(); }; struct mdnsd* implMDNSService::mdnsServer = NULL; in_addr_t implMDNSService::host = INADDR_ANY; +static std::mutex registerMutex; #ifndef BELL_DISABLE_AVAHI AvahiClient* implMDNSService::avahiClient = NULL; AvahiSimplePoll* implMDNSService::avahiPoll = NULL; @@ -60,130 +63,135 @@ AvahiSimplePoll* implMDNSService::avahiPoll = NULL; void implMDNSService::unregisterService() { #ifndef BELL_DISABLE_AVAHI - if (avahiGroup) { - avahi_entry_group_free(avahiGroup); - } else + if (avahiGroup) { + avahi_entry_group_free(avahiGroup); + } else #endif - { - mdns_service_remove(implMDNSService::mdnsServer, service); - } + { + mdns_service_remove(implMDNSService::mdnsServer, service); + } } std::unique_ptr MDNSService::registerService( - const std::string& serviceName, - const std::string& serviceType, - const std::string& serviceProto, - const std::string& serviceHost, - int servicePort, - const std::map txtData -) { + const std::string& serviceName, const std::string& serviceType, + const std::string& serviceProto, const std::string& serviceHost, + int servicePort, const std::map txtData) { + std::lock_guard lock(registerMutex); #ifndef BELL_DISABLE_AVAHI - // try avahi first if available - if (!implMDNSService::avahiPoll) { - implMDNSService::avahiPoll = avahi_simple_poll_new(); + // try avahi first if available + if (!implMDNSService::avahiPoll) { + implMDNSService::avahiPoll = avahi_simple_poll_new(); + } + + if (implMDNSService::avahiPoll && !implMDNSService::avahiClient) { + implMDNSService::avahiClient = + avahi_client_new(avahi_simple_poll_get(implMDNSService::avahiPoll), + AvahiClientFlags(0), NULL, NULL, NULL); + } + AvahiEntryGroup* avahiGroup = NULL; + + if (implMDNSService::avahiClient && + (avahiGroup = avahi_entry_group_new(implMDNSService::avahiClient, + groupHandler, NULL)) == NULL) { + BELL_LOG(error, "MDNS", "cannot create service %s", serviceName.c_str()); + } + + if (avahiGroup != NULL) { + AvahiStringList* avahiTxt = NULL; + + for (auto& [key, value] : txtData) { + avahiTxt = + avahi_string_list_add_pair(avahiTxt, key.c_str(), value.c_str()); } - if (implMDNSService::avahiPoll && !implMDNSService::avahiClient) { - implMDNSService::avahiClient = avahi_client_new(avahi_simple_poll_get(implMDNSService::avahiPoll), - AvahiClientFlags(0), NULL, NULL, NULL); + std::string type(serviceType + "." + serviceProto); + int ret = avahi_entry_group_add_service_strlst( + avahiGroup, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, (AvahiPublishFlags)0, + serviceName.c_str(), type.c_str(), NULL, NULL, servicePort, avahiTxt); + avahi_string_list_free(avahiTxt); + + if (ret >= 0) { + ret = avahi_entry_group_commit(avahiGroup); } - - AvahiEntryGroup *avahiGroup; - if (implMDNSService::avahiClient && - (avahiGroup = avahi_entry_group_new(implMDNSService::avahiClient, groupHandler, NULL)) == NULL) { - BELL_LOG(error, "MDNS", "cannot create service %s", serviceName.c_str()); - } - - if (avahiGroup) { - AvahiStringList* avahiTxt = NULL; - - for (auto& [key, value] : txtData) { - avahiTxt = avahi_string_list_add_pair(avahiTxt, key.c_str(), value.c_str()); - } - - std::string type(serviceType + "." + serviceProto); - int ret = avahi_entry_group_add_service_strlst(avahiGroup, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, (AvahiPublishFlags) 0, - serviceName.c_str(), type.c_str(), NULL, NULL, servicePort, avahiTxt); - avahi_string_list_free(avahiTxt); - - if (ret >= 0) { - ret = avahi_entry_group_commit(avahiGroup); - } - - if (ret < 0) { - BELL_LOG(error, "MDNS", "cannot run service %s", serviceName.c_str()); - avahi_entry_group_free(avahiGroup); - } else { - BELL_LOG(info, "MDNS", "using avahi for %s", serviceName.c_str()); - return std::make_unique(avahiGroup); - } + if (ret < 0) { + BELL_LOG(error, "MDNS", "cannot run service %s", serviceName.c_str()); + avahi_entry_group_free(avahiGroup); + } else { + BELL_LOG(info, "MDNS", "using avahi for %s", serviceName.c_str()); + return std::make_unique(avahiGroup); } + } #endif - // avahi failed, use build-in server - struct ifaddrs* ifaddr; + // avahi failed, use build-in server + struct ifaddrs* ifaddr; - // get the host address first - if (serviceHost.size()) { - struct hostent *h = gethostbyname(serviceHost.c_str()); - if (h) { - memcpy(&implMDNSService::host, h->h_addr_list[0], 4); - } + // get the host address first + if (serviceHost.size()) { + struct hostent* h = gethostbyname(serviceHost.c_str()); + if (h) { + memcpy(&implMDNSService::host, h->h_addr_list[0], 4); } + } - // try go guess ifaddr if we have nothing as listening to INADDR_ANY usually does not work - if (implMDNSService::host == INADDR_ANY && getifaddrs(&ifaddr) != -1) { - for (struct ifaddrs* ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { - if (ifa->ifa_addr == NULL || ifa->ifa_addr->sa_family != AF_INET || - !(ifa->ifa_flags & IFF_UP) || !(ifa->ifa_flags & IFF_MULTICAST) || - (ifa->ifa_flags & IFF_LOOPBACK)) continue; + // try go guess ifaddr if we have nothing as listening to INADDR_ANY usually does not work + if (implMDNSService::host == INADDR_ANY && getifaddrs(&ifaddr) != -1) { + for (struct ifaddrs* ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL || ifa->ifa_addr->sa_family != AF_INET || + !(ifa->ifa_flags & IFF_UP) || !(ifa->ifa_flags & IFF_MULTICAST) || + (ifa->ifa_flags & IFF_LOOPBACK)) + continue; - implMDNSService::host = ((struct sockaddr_in*)ifa->ifa_addr)->sin_addr.s_addr; - break; - } - freeifaddrs(ifaddr); - } - - if (!implMDNSService::mdnsServer) { - char hostname[256]; - struct in_addr addr; - - // it's the same, but who knows.. - addr.s_addr = implMDNSService::host; - gethostname(hostname, sizeof(hostname)); - - implMDNSService::mdnsServer = mdnsd_start(addr, false); - - if (implMDNSService::mdnsServer) { - mdnsd_set_hostname(implMDNSService::mdnsServer, hostname, addr); - } + implMDNSService::host = + ((struct sockaddr_in*)ifa->ifa_addr)->sin_addr.s_addr; + break; } + freeifaddrs(ifaddr); + } + + if (!implMDNSService::mdnsServer) { + char hostname[256]; + struct in_addr addr; + + // it's the same, but who knows.. + addr.s_addr = implMDNSService::host; + gethostname(hostname, sizeof(hostname)); + + implMDNSService::mdnsServer = mdnsd_start(addr, false); if (implMDNSService::mdnsServer) { - std::vector txt; - std::vector> txtStr; + mdnsd_set_hostname(implMDNSService::mdnsServer, hostname, addr); + } + } - for (auto& [key, value] : txtData) { - auto str = make_unique(key + "=" + value); - txtStr.push_back(std::move(str)); - txt.push_back(txtStr.back()->c_str()); - } + if (implMDNSService::mdnsServer) { + std::vector txt; + std::vector> txtStr; - txt.push_back(NULL); - std::string type(serviceType + "." + serviceProto + ".local"); - - BELL_LOG(info, "MDNS", "using built-in mDNS for %s", serviceName.c_str()); - struct mdns_service* mdnsService = mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(), - type.c_str(), servicePort, NULL, txt.data()); - if (mdnsService) { - auto service = mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(), - type.c_str(), servicePort, NULL, txt.data()); - - return std::make_unique(service); - } + for (auto& [key, value] : txtData) { + auto str = make_unique(key + "=" + value); + txtStr.push_back(std::move(str)); + txt.push_back(txtStr.back()->c_str()); } - BELL_LOG(error, "MDNS", "cannot start any mDNS listener for %s", serviceName.c_str()); - return NULL; + txt.push_back(NULL); + std::string type(serviceType + "." + serviceProto + ".local"); + + BELL_LOG(info, "MDNS", "using built-in mDNS for %s", serviceName.c_str()); + struct mdns_service* mdnsService = + mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(), + type.c_str(), servicePort, NULL, txt.data()); + if (mdnsService) { + auto service = + mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(), + type.c_str(), servicePort, NULL, txt.data()); + + return std::make_unique(service); + } + } + + BELL_LOG(error, "MDNS", "cannot start any mDNS listener for %s", + serviceName.c_str()); + return NULL; } diff --git a/components/spotify/cspot/bell/main/platform/linux/WrappedSemaphore.cpp b/components/spotify/cspot/bell/main/platform/linux/WrappedSemaphore.cpp index 131d0b9e..ca44436d 100644 --- a/components/spotify/cspot/bell/main/platform/linux/WrappedSemaphore.cpp +++ b/components/spotify/cspot/bell/main/platform/linux/WrappedSemaphore.cpp @@ -1,33 +1,33 @@ #include "WrappedSemaphore.h" +#include using namespace bell; -WrappedSemaphore::WrappedSemaphore(int count) -{ - sem_init(&this->semaphoreHandle, 0, 0); // eek pointer +WrappedSemaphore::WrappedSemaphore(int count) { + sem_init(&this->semaphoreHandle, 0, 0); // eek pointer } -WrappedSemaphore::~WrappedSemaphore() -{ - sem_destroy(&this->semaphoreHandle); +WrappedSemaphore::~WrappedSemaphore() { + sem_destroy(&this->semaphoreHandle); } -int WrappedSemaphore::wait() -{ - sem_wait(&this->semaphoreHandle); - return 0; +int WrappedSemaphore::wait() { + sem_wait(&this->semaphoreHandle); + return 0; } -int WrappedSemaphore::twait(long milliseconds) -{ - // wait on semaphore with timeout - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - ts.tv_nsec += (milliseconds % 1000) * 1000000; - return sem_timedwait(&this->semaphoreHandle, &ts); +int WrappedSemaphore::twait(long milliseconds) { + // wait on semaphore with timeout + struct timespec ts; + struct timeval tv; + + gettimeofday(&tv, 0); + + ts.tv_sec = tv.tv_sec + milliseconds / 1000; + ts.tv_nsec = tv.tv_usec * 1000 + (milliseconds % 1000) * 1000000; + return sem_timedwait(&this->semaphoreHandle, &ts); } -void WrappedSemaphore::give() -{ - sem_post(&this->semaphoreHandle); +void WrappedSemaphore::give() { + sem_post(&this->semaphoreHandle); } diff --git a/components/spotify/cspot/bell/main/platform/win32/MDNSService.cpp b/components/spotify/cspot/bell/main/platform/win32/MDNSService.cpp index fb91061a..7ee64a17 100644 --- a/components/spotify/cspot/bell/main/platform/win32/MDNSService.cpp +++ b/components/spotify/cspot/bell/main/platform/win32/MDNSService.cpp @@ -1,8 +1,8 @@ -#include #include +#include -#include "MDNSService.h" #include "BellLogger.h" +#include "MDNSService.h" #ifdef _WIN32 #include @@ -12,19 +12,20 @@ #else #include #include "mdns.h" -#include #endif using namespace bell; class implMDNSService : public MDNSService { -private: - struct mdns_service* service; - void unregisterService(void) { mdns_service_remove(implMDNSService::mdnsServer, service); }; - -public: - static struct mdnsd* mdnsServer; - implMDNSService(struct mdns_service* service) : service(service) { }; + private: + struct mdns_service* service; + void unregisterService(void) { + mdns_service_remove(implMDNSService::mdnsServer, service); + }; + + public: + static struct mdnsd* mdnsServer; + implMDNSService(struct mdns_service* service) : service(service){}; }; /** @@ -32,58 +33,66 @@ public: **/ struct mdnsd* implMDNSService::mdnsServer = NULL; +static std::mutex registerMutex; std::unique_ptr MDNSService::registerService( - const std::string& serviceName, - const std::string& serviceType, - const std::string& serviceProto, - const std::string& serviceHost, - int servicePort, - const std::map txtData -) { - if (!implMDNSService::mdnsServer) { - char hostname[128]; - gethostname(hostname, sizeof(hostname)); + const std::string& serviceName, const std::string& serviceType, + const std::string& serviceProto, const std::string& serviceHost, + int servicePort, const std::map txtData) { + std::lock_guard lock(registerMutex); + if (!implMDNSService::mdnsServer) { + char hostname[128]; + gethostname(hostname, sizeof(hostname)); - struct sockaddr_in* host = NULL; - ULONG size = sizeof(IP_ADAPTER_ADDRESSES) * 64; - IP_ADAPTER_ADDRESSES* adapters = (IP_ADAPTER_ADDRESSES*)malloc(size); - int ret = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_GATEWAYS | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_ANYCAST, 0, adapters, &size); + struct sockaddr_in* host = NULL; + ULONG size = sizeof(IP_ADAPTER_ADDRESSES) * 64; + IP_ADAPTER_ADDRESSES* adapters = (IP_ADAPTER_ADDRESSES*)malloc(size); + int ret = GetAdaptersAddresses(AF_UNSPEC, + GAA_FLAG_INCLUDE_GATEWAYS | + GAA_FLAG_SKIP_MULTICAST | + GAA_FLAG_SKIP_ANYCAST, + 0, adapters, &size); - for (PIP_ADAPTER_ADDRESSES adapter = adapters; adapter && !host; adapter = adapter->Next) { - if (adapter->TunnelType == TUNNEL_TYPE_TEREDO) continue; - if (adapter->OperStatus != IfOperStatusUp) continue; + for (PIP_ADAPTER_ADDRESSES adapter = adapters; adapter && !host; + adapter = adapter->Next) { + if (adapter->TunnelType == TUNNEL_TYPE_TEREDO) + continue; + if (adapter->OperStatus != IfOperStatusUp) + continue; - for (IP_ADAPTER_UNICAST_ADDRESS* unicast = adapter->FirstUnicastAddress; unicast; - unicast = unicast->Next) { - if (adapter->FirstGatewayAddress && unicast->Address.lpSockaddr->sa_family == AF_INET) { - host = (struct sockaddr_in*)unicast->Address.lpSockaddr; - BELL_LOG(info, "mdns", "mDNS on interface %s", inet_ntoa(host->sin_addr)); - implMDNSService::mdnsServer = mdnsd_start(host->sin_addr, false); - break; - } - } + for (IP_ADAPTER_UNICAST_ADDRESS* unicast = adapter->FirstUnicastAddress; + unicast; unicast = unicast->Next) { + if (adapter->FirstGatewayAddress && + unicast->Address.lpSockaddr->sa_family == AF_INET) { + host = (struct sockaddr_in*)unicast->Address.lpSockaddr; + BELL_LOG(info, "mdns", "mDNS on interface %s", + inet_ntoa(host->sin_addr)); + implMDNSService::mdnsServer = mdnsd_start(host->sin_addr, false); + break; } - - assert(implMDNSService::mdnsServer); - mdnsd_set_hostname(implMDNSService::mdnsServer, hostname, host->sin_addr); - free(adapters); + } } - std::vector txt; - std::vector> txtStr; + assert(implMDNSService::mdnsServer); + mdnsd_set_hostname(implMDNSService::mdnsServer, hostname, host->sin_addr); + free(adapters); + } - for (auto& [key, value] : txtData) { - auto str = make_unique(key + "=" + value); - txtStr.push_back(std::move(str)); - txt.push_back(txtStr.back()->c_str()); - } - txt.push_back(NULL); + std::vector txt; + std::vector> txtStr; - std::string type(serviceType + "." + serviceProto + ".local"); + for (auto& [key, value] : txtData) { + auto str = make_unique(key + "=" + value); + txtStr.push_back(std::move(str)); + txt.push_back(txtStr.back()->c_str()); + } + txt.push_back(NULL); - auto service = mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(), - type.c_str(), servicePort, NULL, txt.data()); + std::string type(serviceType + "." + serviceProto + ".local"); - return std::make_unique(service); + auto service = + mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(), + type.c_str(), servicePort, NULL, txt.data()); + + return std::make_unique(service); } diff --git a/components/spotify/cspot/bell/main/platform/win32/WrappedSemaphore.cpp b/components/spotify/cspot/bell/main/platform/win32/WrappedSemaphore.cpp index 4ed8c491..20c33825 100644 --- a/components/spotify/cspot/bell/main/platform/win32/WrappedSemaphore.cpp +++ b/components/spotify/cspot/bell/main/platform/win32/WrappedSemaphore.cpp @@ -2,28 +2,24 @@ using namespace bell; -WrappedSemaphore::WrappedSemaphore(int count) -{ - this->semaphoreHandle = CreateSemaphore(NULL, 0, count, NULL); +WrappedSemaphore::WrappedSemaphore(int count) { + this->semaphoreHandle = CreateSemaphore(NULL, 0, count, NULL); } -WrappedSemaphore::~WrappedSemaphore() -{ - CloseHandle(this->semaphoreHandle); +WrappedSemaphore::~WrappedSemaphore() { + CloseHandle(this->semaphoreHandle); } -int WrappedSemaphore::wait() -{ - WaitForSingleObject(this->semaphoreHandle, INFINITE); - return 0; +int WrappedSemaphore::wait() { + WaitForSingleObject(this->semaphoreHandle, INFINITE); + return 0; } -int WrappedSemaphore::twait(long milliseconds) -{ - return WaitForSingleObject(this->semaphoreHandle, milliseconds) != WAIT_OBJECT_0; +int WrappedSemaphore::twait(long milliseconds) { + return WaitForSingleObject(this->semaphoreHandle, milliseconds) != + WAIT_OBJECT_0; } -void WrappedSemaphore::give() -{ - ReleaseSemaphore(this->semaphoreHandle, 1, NULL); +void WrappedSemaphore::give() { + ReleaseSemaphore(this->semaphoreHandle, 1, NULL); } diff --git a/components/spotify/cspot/bell/main/platform/win32/win32shim.h b/components/spotify/cspot/bell/main/platform/win32/win32shim.h index 57fbedfa..f06bebf3 100644 --- a/components/spotify/cspot/bell/main/platform/win32/win32shim.h +++ b/components/spotify/cspot/bell/main/platform/win32/win32shim.h @@ -7,8 +7,12 @@ #define strcasecmp stricmp #define strncasecmp _strnicmp -#define bzero(p,n) memset(p,0,n) -#define usleep(x) Sleep((x)/1000) +#define bzero(p, n) memset(p, 0, n) +#define usleep(x) Sleep((x) / 1000) -inline size_t read(int sock, char* buf, size_t n) { return recv(sock, buf, n, 0); } -inline int write(int sock, const char* buf, size_t n) { return send(sock, buf, n, 0); } +inline size_t read(int sock, char* buf, size_t n) { + return recv(sock, buf, n, 0); +} +inline int write(int sock, const char* buf, size_t n) { + return send(sock, buf, n, 0); +} diff --git a/components/spotify/cspot/bell/main/utilities/BellLogger.cpp b/components/spotify/cspot/bell/main/utilities/BellLogger.cpp index 975b2975..46ec6d92 100644 --- a/components/spotify/cspot/bell/main/utilities/BellLogger.cpp +++ b/components/spotify/cspot/bell/main/utilities/BellLogger.cpp @@ -3,9 +3,13 @@ bell::AbstractLogger* bell::bellGlobalLogger; void bell::setDefaultLogger() { - bell::bellGlobalLogger = new bell::BellLogger(); + bell::bellGlobalLogger = new bell::BellLogger(); } void bell::enableSubmoduleLogging() { - bell::bellGlobalLogger->enableSubmodule = true; -} \ No newline at end of file + bell::bellGlobalLogger->enableSubmodule = true; +} + +void bell::enableTimestampLogging() { + bell::bellGlobalLogger->enableTimestamp = true; +} diff --git a/components/spotify/cspot/bell/main/utilities/BellUtils.cpp b/components/spotify/cspot/bell/main/utilities/BellUtils.cpp index f77a1368..902252f3 100644 --- a/components/spotify/cspot/bell/main/utilities/BellUtils.cpp +++ b/components/spotify/cspot/bell/main/utilities/BellUtils.cpp @@ -1,5 +1,11 @@ #include "BellUtils.h" +#include // for free +#include // for mt19937, uniform_int_distribution, random_device +#ifdef ESP_PLATFORM +#include "esp_system.h" +#endif + std::string bell::generateRandomUUID() { static std::random_device dev; static std::mt19937 rng(dev()); diff --git a/components/spotify/cspot/bell/main/utilities/Crypto.cpp b/components/spotify/cspot/bell/main/utilities/Crypto.cpp index ab4883b0..28c2d31d 100644 --- a/components/spotify/cspot/bell/main/utilities/Crypto.cpp +++ b/components/spotify/cspot/bell/main/utilities/Crypto.cpp @@ -1,5 +1,17 @@ #include "Crypto.h" +#include // for mbedtls_base64_encode, mbedtls_base64_... +#include // for mbedtls_mpi_free, mbedtls_mpi_init +#include // for mbedtls_ctr_drbg_free, mbedtls_ctr_drb... +#include // for mbedtls_entropy_free, mbedtls_entropy_... +#include // for mbedtls_pkcs5_pbkdf2_hmac +#include // for uint8_t +#include // for runtime_error + +extern "C" { +#include "aes.h" // for AES_ECB_decrypt, AES_init_ctx, AES_ctx +} + CryptoMbedTLS::CryptoMbedTLS() {} CryptoMbedTLS::~CryptoMbedTLS() { diff --git a/components/spotify/cspot/bell/main/utilities/NanoPBHelper.cpp b/components/spotify/cspot/bell/main/utilities/NanoPBHelper.cpp index 9a4170c7..4bd626d5 100644 --- a/components/spotify/cspot/bell/main/utilities/NanoPBHelper.cpp +++ b/components/spotify/cspot/bell/main/utilities/NanoPBHelper.cpp @@ -1,78 +1,82 @@ #include "NanoPBHelper.h" -static bool vectorWrite(pb_ostream_t *stream, const pb_byte_t *buf, size_t count) -{ - size_t i; - auto *dest = reinterpret_cast *>(stream->state); +#include // for malloc +#include // for strcpy, memcpy, strlen +#include // for copy +#include // for uint8_t - dest->insert(dest->end(), buf, buf + count); +#include "pb_encode.h" // for pb_ostream_s, pb_encode, pb_get_encoded_size - return true; +static bool vectorWrite(pb_ostream_t* stream, const pb_byte_t* buf, + size_t count) { + size_t i; + auto* dest = reinterpret_cast*>(stream->state); + + dest->insert(dest->end(), buf, buf + count); + + return true; } -pb_ostream_t pb_ostream_from_vector(std::vector &vec) -{ - pb_ostream_t stream; +pb_ostream_t pb_ostream_from_vector(std::vector& vec) { + pb_ostream_t stream; - stream.callback = &vectorWrite; - stream.state = &vec; - stream.max_size = 100000; - stream.bytes_written = 0; + stream.callback = &vectorWrite; + stream.state = &vec; + stream.max_size = 100000; + stream.bytes_written = 0; - return stream; + return stream; } -std::vector pbEncode(const pb_msgdesc_t *fields, const void *src_struct) -{ - std::vector vecData(0); - pb_ostream_t stream = pb_ostream_from_vector(vecData); - pb_encode(&stream, fields, src_struct); +std::vector pbEncode(const pb_msgdesc_t* fields, + const void* src_struct) { + std::vector vecData(0); + pb_ostream_t stream = pb_ostream_from_vector(vecData); + pb_encode(&stream, fields, src_struct); - return vecData; + return vecData; } -void packString(char *&dst, std::string stringToPack) -{ - dst = (char *)malloc((strlen(stringToPack.c_str()) + 1) * sizeof(char)); - strcpy(dst, stringToPack.c_str()); +void packString(char*& dst, std::string stringToPack) { + dst = (char*)malloc((strlen(stringToPack.c_str()) + 1) * sizeof(char)); + strcpy(dst, stringToPack.c_str()); } -pb_bytes_array_t* vectorToPbArray(const std::vector& vectorToPack) -{ - auto size = static_cast(vectorToPack.size()); - auto result = static_cast( - malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(size))); - result->size = size; - memcpy(result->bytes, vectorToPack.data(), size); - return result; +pb_bytes_array_t* vectorToPbArray(const std::vector& vectorToPack) { + auto size = static_cast(vectorToPack.size()); + auto result = + static_cast(malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(size))); + result->size = size; + memcpy(result->bytes, vectorToPack.data(), size); + return result; } -void pbPutString(const std::string &stringToPack, char* dst) { - stringToPack.copy(dst, stringToPack.size()); - dst[stringToPack.size()] = '\0'; +void pbPutString(const std::string& stringToPack, char* dst) { + stringToPack.copy(dst, stringToPack.size()); + dst[stringToPack.size()] = '\0'; } -void pbPutCharArray(const char * stringToPack, char* dst) { - // copy stringToPack into dst - strcpy(dst, stringToPack); - //dst[sizeof(stringToPack)-1] = '\0'; +void pbPutCharArray(const char* stringToPack, char* dst) { + // copy stringToPack into dst + strcpy(dst, stringToPack); + //dst[sizeof(stringToPack)-1] = '\0'; } -void pbPutBytes(const std::vector &data, pb_bytes_array_t &dst) { - dst.size = data.size(); - std::copy(data.begin(), data.end(), dst.bytes); +void pbPutBytes(const std::vector& data, pb_bytes_array_t& dst) { + dst.size = data.size(); + std::copy(data.begin(), data.end(), dst.bytes); } std::vector pbArrayToVector(pb_bytes_array_t* pbArray) { - return std::vector(pbArray->bytes, pbArray->bytes + pbArray->size); + return std::vector(pbArray->bytes, pbArray->bytes + pbArray->size); } -const char *pb_encode_to_string(const pb_msgdesc_t *fields, const void *data) { - size_t len; - pb_get_encoded_size(&len, fields, data); - auto *buf = static_cast(malloc(len + 1)); - auto ostream = pb_ostream_from_buffer(buf, len); - pb_encode(&ostream, fields, data); - buf[len] = '\0'; - return reinterpret_cast(buf); +const char* pb_encode_to_string(const pb_msgdesc_t* fields, const void* data) { + size_t len; + pb_get_encoded_size(&len, fields, data); + auto* buf = static_cast(malloc(len + 1)); + auto ostream = pb_ostream_from_buffer(buf, len); + pb_encode(&ostream, fields, data); + buf[len] = '\0'; + return reinterpret_cast(buf); } \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/utilities/aes.c b/components/spotify/cspot/bell/main/utilities/aes.c index 2bd115e2..2ed66c69 100644 --- a/components/spotify/cspot/bell/main/utilities/aes.c +++ b/components/spotify/cspot/bell/main/utilities/aes.c @@ -31,12 +31,13 @@ NOTE: String length must be evenly divisible by 16byte (str_len % 16 == 0) */ - +#include // for uint8_t /*****************************************************************************/ /* Includes: */ /*****************************************************************************/ -#include // CBC mode, for memset -#include "aes.h" +#include // for memcpy, size_t + +#include "aes.h" // for AES_ctx, AES_BLOCKLEN, CBC, ECB, CTR, AES192 /*****************************************************************************/ /* Defines: */ @@ -45,80 +46,87 @@ NOTE: String length must be evenly divisible by 16byte (str_len % 16 == 0) #define Nb 4 #if defined(AES256) && (AES256 == 1) - #define Nk 8 - #define Nr 14 +#define Nk 8 +#define Nr 14 #elif defined(AES192) && (AES192 == 1) - #define Nk 6 - #define Nr 12 +#define Nk 6 +#define Nr 12 #else - #define Nk 4 // The number of 32 bit words in a key. - #define Nr 10 // The number of rounds in AES Cipher. +#define Nk 4 // The number of 32 bit words in a key. +#define Nr 10 // The number of rounds in AES Cipher. #endif -// jcallan@github points out that declaring Multiply as a function +// jcallan@github points out that declaring Multiply as a function // reduces code size considerably with the Keil ARM compiler. // See this link for more information: https://github.com/kokke/tiny-AES-C/pull/3 #ifndef MULTIPLY_AS_A_FUNCTION - #define MULTIPLY_AS_A_FUNCTION 0 +#define MULTIPLY_AS_A_FUNCTION 0 #endif - - - /*****************************************************************************/ /* Private variables: */ /*****************************************************************************/ // state - array holding the intermediate results during decryption. typedef uint8_t state_t[4][4]; - - // The lookup-tables are marked const so they can be placed in read-only storage instead of RAM -// The numbers below can be computed dynamically trading ROM for RAM - +// The numbers below can be computed dynamically trading ROM for RAM - // This can be useful in (embedded) bootloader applications, where ROM is often limited. static const uint8_t sbox[256] = { - //0 1 2 3 4 5 6 7 8 9 A B C D E F - 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, - 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, - 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, - 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, - 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, - 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, - 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, - 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, - 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, - 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, - 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, - 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, - 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, - 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, - 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, - 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 }; + //0 1 2 3 4 5 6 7 8 9 A B C D E F + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, + 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, + 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, + 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, + 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, + 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, + 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, + 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, + 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, + 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, + 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, + 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, + 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, + 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, + 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, + 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, + 0xb0, 0x54, 0xbb, 0x16}; #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) static const uint8_t rsbox[256] = { - 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, - 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, - 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, - 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, - 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, - 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, - 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, - 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, - 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, - 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, - 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, - 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, - 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, - 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, - 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, - 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d }; + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, + 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, + 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, + 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, + 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, + 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, + 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, + 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, + 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, + 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, + 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, + 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, + 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, + 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, + 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, + 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, + 0x55, 0x21, 0x0c, 0x7d}; #endif -// The round constant word array, Rcon[i], contains the values given by +// The round constant word array, Rcon[i], contains the values given by // x to the power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8) -static const uint8_t Rcon[11] = { - 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 }; +static const uint8_t Rcon[11] = {0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, + 0x20, 0x40, 0x80, 0x1b, 0x36}; /* * Jordan Goulder points out in PR #12 (https://github.com/kokke/tiny-AES-C/pull/12), @@ -130,7 +138,6 @@ static const uint8_t Rcon[11] = { * up to rcon[8] for AES-192, up to rcon[7] for AES-256. rcon[0] is not used in AES algorithm." */ - /*****************************************************************************/ /* Private functions: */ /*****************************************************************************/ @@ -142,15 +149,13 @@ static uint8_t getSBoxValue(uint8_t num) */ #define getSBoxValue(num) (sbox[(num)]) -// This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states. -static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) -{ +// This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states. +static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) { unsigned i, j, k; - uint8_t tempa[4]; // Used for the column/row operations - + uint8_t tempa[4]; // Used for the column/row operations + // The first round key is the key itself. - for (i = 0; i < Nk; ++i) - { + for (i = 0; i < Nk; ++i) { RoundKey[(i * 4) + 0] = Key[(i * 4) + 0]; RoundKey[(i * 4) + 1] = Key[(i * 4) + 1]; RoundKey[(i * 4) + 2] = Key[(i * 4) + 2]; @@ -158,19 +163,16 @@ static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) } // All other round keys are found from the previous round keys. - for (i = Nk; i < Nb * (Nr + 1); ++i) - { + for (i = Nk; i < Nb * (Nr + 1); ++i) { { k = (i - 1) * 4; - tempa[0]=RoundKey[k + 0]; - tempa[1]=RoundKey[k + 1]; - tempa[2]=RoundKey[k + 2]; - tempa[3]=RoundKey[k + 3]; - + tempa[0] = RoundKey[k + 0]; + tempa[1] = RoundKey[k + 1]; + tempa[2] = RoundKey[k + 2]; + tempa[3] = RoundKey[k + 3]; } - if (i % Nk == 0) - { + if (i % Nk == 0) { // This function shifts the 4 bytes in a word to the left once. // [a0,a1,a2,a3] becomes [a1,a2,a3,a0] @@ -183,7 +185,7 @@ static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) tempa[3] = u8tmp; } - // SubWord() is a function that takes a four-byte input word and + // SubWord() is a function that takes a four-byte input word and // applies the S-box to each of the four bytes to produce an output word. // Function Subword() @@ -194,11 +196,10 @@ static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) tempa[3] = getSBoxValue(tempa[3]); } - tempa[0] = tempa[0] ^ Rcon[i/Nk]; + tempa[0] = tempa[0] ^ Rcon[i / Nk]; } #if defined(AES256) && (AES256 == 1) - if (i % Nk == 4) - { + if (i % Nk == 4) { // Function Subword() { tempa[0] = getSBoxValue(tempa[0]); @@ -208,7 +209,8 @@ static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) } } #endif - j = i * 4; k=(i - Nk) * 4; + j = i * 4; + k = (i - Nk) * 4; RoundKey[j + 0] = RoundKey[k + 0] ^ tempa[0]; RoundKey[j + 1] = RoundKey[k + 1] ^ tempa[1]; RoundKey[j + 2] = RoundKey[k + 2] ^ tempa[2]; @@ -216,31 +218,27 @@ static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) } } -void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key) -{ +void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key) { KeyExpansion(ctx->RoundKey, key); } #if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) -void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv) -{ +void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, + const uint8_t* iv) { KeyExpansion(ctx->RoundKey, key); - memcpy (ctx->Iv, iv, AES_BLOCKLEN); + memcpy(ctx->Iv, iv, AES_BLOCKLEN); } -void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv) -{ - memcpy (ctx->Iv, iv, AES_BLOCKLEN); +void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv) { + memcpy(ctx->Iv, iv, AES_BLOCKLEN); } #endif // This function adds the round key to state. // The round key is added to the state by an XOR function. -static void AddRoundKey(uint8_t round, state_t* state, const uint8_t* RoundKey) -{ - uint8_t i,j; - for (i = 0; i < 4; ++i) - { - for (j = 0; j < 4; ++j) - { +static void AddRoundKey(uint8_t round, state_t* state, + const uint8_t* RoundKey) { + uint8_t i, j; + for (i = 0; i < 4; ++i) { + for (j = 0; j < 4; ++j) { (*state)[i][j] ^= RoundKey[(round * Nb * 4) + (i * Nb) + j]; } } @@ -248,13 +246,10 @@ static void AddRoundKey(uint8_t round, state_t* state, const uint8_t* RoundKey) // The SubBytes Function Substitutes the values in the // state matrix with values in an S-box. -static void SubBytes(state_t* state) -{ +static void SubBytes(state_t* state) { uint8_t i, j; - for (i = 0; i < 4; ++i) - { - for (j = 0; j < 4; ++j) - { + for (i = 0; i < 4; ++i) { + for (j = 0; j < 4; ++j) { (*state)[j][i] = getSBoxValue((*state)[j][i]); } } @@ -263,52 +258,56 @@ static void SubBytes(state_t* state) // The ShiftRows() function shifts the rows in the state to the left. // Each row is shifted with different offset. // Offset = Row number. So the first row is not shifted. -static void ShiftRows(state_t* state) -{ +static void ShiftRows(state_t* state) { uint8_t temp; - // Rotate first row 1 columns to left - temp = (*state)[0][1]; + // Rotate first row 1 columns to left + temp = (*state)[0][1]; (*state)[0][1] = (*state)[1][1]; (*state)[1][1] = (*state)[2][1]; (*state)[2][1] = (*state)[3][1]; (*state)[3][1] = temp; - // Rotate second row 2 columns to left - temp = (*state)[0][2]; + // Rotate second row 2 columns to left + temp = (*state)[0][2]; (*state)[0][2] = (*state)[2][2]; (*state)[2][2] = temp; - temp = (*state)[1][2]; + temp = (*state)[1][2]; (*state)[1][2] = (*state)[3][2]; (*state)[3][2] = temp; // Rotate third row 3 columns to left - temp = (*state)[0][3]; + temp = (*state)[0][3]; (*state)[0][3] = (*state)[3][3]; (*state)[3][3] = (*state)[2][3]; (*state)[2][3] = (*state)[1][3]; (*state)[1][3] = temp; } -static uint8_t xtime(uint8_t x) -{ - return ((x<<1) ^ (((x>>7) & 1) * 0x1b)); +static uint8_t xtime(uint8_t x) { + return ((x << 1) ^ (((x >> 7) & 1) * 0x1b)); } // MixColumns function mixes the columns of the state matrix -static void MixColumns(state_t* state) -{ +static void MixColumns(state_t* state) { uint8_t i; uint8_t Tmp, Tm, t; - for (i = 0; i < 4; ++i) - { - t = (*state)[i][0]; - Tmp = (*state)[i][0] ^ (*state)[i][1] ^ (*state)[i][2] ^ (*state)[i][3] ; - Tm = (*state)[i][0] ^ (*state)[i][1] ; Tm = xtime(Tm); (*state)[i][0] ^= Tm ^ Tmp ; - Tm = (*state)[i][1] ^ (*state)[i][2] ; Tm = xtime(Tm); (*state)[i][1] ^= Tm ^ Tmp ; - Tm = (*state)[i][2] ^ (*state)[i][3] ; Tm = xtime(Tm); (*state)[i][2] ^= Tm ^ Tmp ; - Tm = (*state)[i][3] ^ t ; Tm = xtime(Tm); (*state)[i][3] ^= Tm ^ Tmp ; + for (i = 0; i < 4; ++i) { + t = (*state)[i][0]; + Tmp = (*state)[i][0] ^ (*state)[i][1] ^ (*state)[i][2] ^ (*state)[i][3]; + Tm = (*state)[i][0] ^ (*state)[i][1]; + Tm = xtime(Tm); + (*state)[i][0] ^= Tm ^ Tmp; + Tm = (*state)[i][1] ^ (*state)[i][2]; + Tm = xtime(Tm); + (*state)[i][1] ^= Tm ^ Tmp; + Tm = (*state)[i][2] ^ (*state)[i][3]; + Tm = xtime(Tm); + (*state)[i][2] ^= Tm ^ Tmp; + Tm = (*state)[i][3] ^ t; + Tm = xtime(Tm); + (*state)[i][3] ^= Tm ^ Tmp; } } @@ -317,21 +316,20 @@ static void MixColumns(state_t* state) // The compiler seems to be able to vectorize the operation better this way. // See https://github.com/kokke/tiny-AES-c/pull/34 #if MULTIPLY_AS_A_FUNCTION -static uint8_t Multiply(uint8_t x, uint8_t y) -{ - return (((y & 1) * x) ^ - ((y>>1 & 1) * xtime(x)) ^ - ((y>>2 & 1) * xtime(xtime(x))) ^ - ((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ - ((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))); /* this last call to xtime() can be omitted */ - } +static uint8_t Multiply(uint8_t x, uint8_t y) { + return (((y & 1) * x) ^ ((y >> 1 & 1) * xtime(x)) ^ + ((y >> 2 & 1) * xtime(xtime(x))) ^ + ((y >> 3 & 1) * xtime(xtime(xtime(x)))) ^ + ((y >> 4 & 1) * + xtime(xtime(xtime( + xtime(x)))))); /* this last call to xtime() can be omitted */ +} #else -#define Multiply(x, y) \ - ( ((y & 1) * x) ^ \ - ((y>>1 & 1) * xtime(x)) ^ \ - ((y>>2 & 1) * xtime(xtime(x))) ^ \ - ((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ \ - ((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))) \ +#define Multiply(x, y) \ + (((y & 1) * x) ^ ((y >> 1 & 1) * xtime(x)) ^ \ + ((y >> 2 & 1) * xtime(xtime(x))) ^ \ + ((y >> 3 & 1) * xtime(xtime(xtime(x)))) ^ \ + ((y >> 4 & 1) * xtime(xtime(xtime(xtime(x)))))) #endif @@ -347,51 +345,48 @@ static uint8_t getSBoxInvert(uint8_t num) // MixColumns function mixes the columns of the state matrix. // The method used to multiply may be difficult to understand for the inexperienced. // Please use the references to gain more information. -static void InvMixColumns(state_t* state) -{ +static void InvMixColumns(state_t* state) { int i; uint8_t a, b, c, d; - for (i = 0; i < 4; ++i) - { + for (i = 0; i < 4; ++i) { a = (*state)[i][0]; b = (*state)[i][1]; c = (*state)[i][2]; d = (*state)[i][3]; - (*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09); - (*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d); - (*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b); - (*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e); + (*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ + Multiply(d, 0x09); + (*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ + Multiply(d, 0x0d); + (*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ + Multiply(d, 0x0b); + (*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ + Multiply(d, 0x0e); } } - // The SubBytes Function Substitutes the values in the // state matrix with values in an S-box. -static void InvSubBytes(state_t* state) -{ +static void InvSubBytes(state_t* state) { uint8_t i, j; - for (i = 0; i < 4; ++i) - { - for (j = 0; j < 4; ++j) - { + for (i = 0; i < 4; ++i) { + for (j = 0; j < 4; ++j) { (*state)[j][i] = getSBoxInvert((*state)[j][i]); } } } -static void InvShiftRows(state_t* state) -{ +static void InvShiftRows(state_t* state) { uint8_t temp; - // Rotate first row 1 columns to right + // Rotate first row 1 columns to right temp = (*state)[3][1]; (*state)[3][1] = (*state)[2][1]; (*state)[2][1] = (*state)[1][1]; (*state)[1][1] = (*state)[0][1]; (*state)[0][1] = temp; - // Rotate second row 2 columns to right + // Rotate second row 2 columns to right temp = (*state)[0][2]; (*state)[0][2] = (*state)[2][2]; (*state)[2][2] = temp; @@ -407,11 +402,10 @@ static void InvShiftRows(state_t* state) (*state)[2][3] = (*state)[3][3]; (*state)[3][3] = temp; } -#endif // #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) +#endif // #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) // Cipher is the main function that encrypts the PlainText. -static void Cipher(state_t* state, const uint8_t* RoundKey) -{ +static void Cipher(state_t* state, const uint8_t* RoundKey) { uint8_t round = 0; // Add the First round key to the state before starting the rounds. @@ -421,8 +415,7 @@ static void Cipher(state_t* state, const uint8_t* RoundKey) // The first Nr-1 rounds are identical. // These Nr rounds are executed in the loop below. // Last one without MixColumns() - for (round = 1; ; ++round) - { + for (round = 1;; ++round) { SubBytes(state); ShiftRows(state); if (round == Nr) { @@ -436,8 +429,7 @@ static void Cipher(state_t* state, const uint8_t* RoundKey) } #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) -static void InvCipher(state_t* state, const uint8_t* RoundKey) -{ +static void InvCipher(state_t* state, const uint8_t* RoundKey) { uint8_t round = 0; // Add the First round key to the state before starting the rounds. @@ -447,8 +439,7 @@ static void InvCipher(state_t* state, const uint8_t* RoundKey) // The first Nr-1 rounds are identical. // These Nr rounds are executed in the loop below. // Last one without InvMixColumn() - for (round = (Nr - 1); ; --round) - { + for (round = (Nr - 1);; --round) { InvShiftRows(state); InvSubBytes(state); AddRoundKey(round, state, RoundKey); @@ -457,53 +448,41 @@ static void InvCipher(state_t* state, const uint8_t* RoundKey) } InvMixColumns(state); } - } -#endif // #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) +#endif // #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) /*****************************************************************************/ /* Public functions: */ /*****************************************************************************/ #if defined(ECB) && (ECB == 1) - -void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf) -{ +void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf) { // The next function call encrypts the PlainText with the Key using AES algorithm. Cipher((state_t*)buf, ctx->RoundKey); } -void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf) -{ +void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf) { // The next function call decrypts the PlainText with the Key using AES algorithm. InvCipher((state_t*)buf, ctx->RoundKey); } - -#endif // #if defined(ECB) && (ECB == 1) - - - - +#endif // #if defined(ECB) && (ECB == 1) #if defined(CBC) && (CBC == 1) - -static void XorWithIv(uint8_t* buf, const uint8_t* Iv) -{ +static void XorWithIv(uint8_t* buf, const uint8_t* Iv) { uint8_t i; - for (i = 0; i < AES_BLOCKLEN; ++i) // The block in AES is always 128bit no matter the key size + for (i = 0; i < AES_BLOCKLEN; + ++i) // The block in AES is always 128bit no matter the key size { buf[i] ^= Iv[i]; } } -void AES_CBC_encrypt_buffer(struct AES_ctx *ctx, uint8_t* buf, size_t length) -{ +void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) { size_t i; - uint8_t *Iv = ctx->Iv; - for (i = 0; i < length; i += AES_BLOCKLEN) - { + uint8_t* Iv = ctx->Iv; + for (i = 0; i < length; i += AES_BLOCKLEN) { XorWithIv(buf, Iv); Cipher((state_t*)buf, ctx->RoundKey); Iv = buf; @@ -513,53 +492,44 @@ void AES_CBC_encrypt_buffer(struct AES_ctx *ctx, uint8_t* buf, size_t length) memcpy(ctx->Iv, Iv, AES_BLOCKLEN); } -void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) -{ +void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) { size_t i; uint8_t storeNextIv[AES_BLOCKLEN]; - for (i = 0; i < length; i += AES_BLOCKLEN) - { + for (i = 0; i < length; i += AES_BLOCKLEN) { memcpy(storeNextIv, buf, AES_BLOCKLEN); InvCipher((state_t*)buf, ctx->RoundKey); XorWithIv(buf, ctx->Iv); memcpy(ctx->Iv, storeNextIv, AES_BLOCKLEN); buf += AES_BLOCKLEN; } - } -#endif // #if defined(CBC) && (CBC == 1) - - +#endif // #if defined(CBC) && (CBC == 1) #if defined(CTR) && (CTR == 1) /* Symmetrical operation: same function for encrypting as for decrypting. Note any IV/nonce should never be reused with the same key */ -void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) -{ +void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) { uint8_t buffer[AES_BLOCKLEN]; - + size_t i; int bi; - for (i = 0, bi = AES_BLOCKLEN; i < length; ++i, ++bi) - { + for (i = 0, bi = AES_BLOCKLEN; i < length; ++i, ++bi) { if (bi == AES_BLOCKLEN) /* we need to regen xor compliment in buffer */ { - + memcpy(buffer, ctx->Iv, AES_BLOCKLEN); - Cipher((state_t*)buffer,ctx->RoundKey); + Cipher((state_t*)buffer, ctx->RoundKey); /* Increment Iv and handle overflow */ - for (bi = (AES_BLOCKLEN - 1); bi >= 0; --bi) - { - /* inc will overflow */ - if (ctx->Iv[bi] == 255) - { + for (bi = (AES_BLOCKLEN - 1); bi >= 0; --bi) { + /* inc will overflow */ + if (ctx->Iv[bi] == 255) { ctx->Iv[bi] = 0; continue; - } + } ctx->Iv[bi] += 1; - break; + break; } bi = 0; } @@ -568,4 +538,4 @@ void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) } } -#endif // #if defined(CTR) && (CTR == 1) +#endif // #if defined(CTR) && (CTR == 1) diff --git a/components/spotify/cspot/bell/main/utilities/include/BellLogger.h b/components/spotify/cspot/bell/main/utilities/include/BellLogger.h index a71784b3..ba6694c3 100644 --- a/components/spotify/cspot/bell/main/utilities/include/BellLogger.h +++ b/components/spotify/cspot/bell/main/utilities/include/BellLogger.h @@ -1,120 +1,140 @@ #ifndef BELL_LOGGER_H #define BELL_LOGGER_H -#include -#include -#include -#include -#include +#include // for va_end, va_list, va_start +#include // for printf, vprintf +#include +#include +#include +#include // for string, basic_string -namespace bell -{ +namespace bell { - class AbstractLogger - { - public: - bool enableSubmodule = false; - virtual void debug(std::string filename, int line, std::string submodule, const char *format, ...) = 0; - virtual void error(std::string filename, int line, std::string submodule, const char *format, ...) = 0; - virtual void info(std::string filename, int line, std::string submodule, const char *format, ...) = 0; - }; +class AbstractLogger { + public: + bool enableSubmodule = false; + bool enableTimestamp = false; - extern bell::AbstractLogger* bellGlobalLogger; - class BellLogger : public bell::AbstractLogger - { - public: - // static bool enableColors = true; - void debug(std::string filename, int line, std::string submodule, const char *format, ...) - { + virtual void debug(std::string filename, int line, std::string submodule, + const char* format, ...) = 0; + virtual void error(std::string filename, int line, std::string submodule, + const char* format, ...) = 0; + virtual void info(std::string filename, int line, std::string submodule, + const char* format, ...) = 0; +}; - printf(colorRed); - printf("D "); - if (enableSubmodule) { - printf(colorReset); - printf("[%s] ", submodule.c_str()); - } - printFilename(filename); - printf(":%d: ", line); - va_list args; - va_start(args, format); - vprintf(format, args); - va_end(args); - printf("\n"); - }; +extern bell::AbstractLogger* bellGlobalLogger; +class BellLogger : public bell::AbstractLogger { + public: + // static bool enableColors = true; + void debug(std::string filename, int line, std::string submodule, + const char* format, ...) { + printTimestamp(); - void error(std::string filename, int line, std::string submodule, const char *format, ...) - { + printf(colorRed); + printf("D "); + if (enableSubmodule) { + printf(colorReset); + printf("[%s] ", submodule.c_str()); + } + printFilename(filename); + printf(":%d: ", line); + va_list args; + va_start(args, format); + vprintf(format, args); + va_end(args); + printf("\n"); + }; - printf(colorRed); - printf("E "); - if (enableSubmodule) { - printf(colorReset); - printf("[%s] ", submodule.c_str()); - } - printFilename(filename); - printf(":%d: ", line); - printf(colorRed); - va_list args; - va_start(args, format); - vprintf(format, args); - va_end(args); - printf("\n"); - }; + void error(std::string filename, int line, std::string submodule, + const char* format, ...) { + printTimestamp(); - void info(std::string filename, int line, std::string submodule, const char *format, ...) - { + printf(colorRed); + printf("E "); + if (enableSubmodule) { + printf(colorReset); + printf("[%s] ", submodule.c_str()); + } + printFilename(filename); + printf(":%d: ", line); + printf(colorRed); + va_list args; + va_start(args, format); + vprintf(format, args); + va_end(args); + printf("\n"); + }; - printf(colorBlue); - printf("I "); - if (enableSubmodule) { - printf(colorReset); - printf("[%s] ", submodule.c_str()); - } - printFilename(filename); - printf(":%d: ", line); - printf(colorReset); - va_list args; - va_start(args, format); - vprintf(format, args); - va_end(args); - printf("\n"); - }; + void info(std::string filename, int line, std::string submodule, + const char* format, ...) { + printTimestamp(); - void printFilename(std::string filename) - { + printf(colorBlue); + printf("I "); + if (enableSubmodule) { + printf(colorReset); + printf("[%s] ", submodule.c_str()); + } + printFilename(filename); + printf(":%d: ", line); + printf(colorReset); + va_list args; + va_start(args, format); + vprintf(format, args); + va_end(args); + printf("\n"); + }; + + void printTimestamp() { + if (enableTimestamp) { + auto now = std::chrono::system_clock::now(); + time_t now_time = std::chrono::system_clock::to_time_t(now); + const auto nowMs = std::chrono::duration_cast( + now.time_since_epoch()) % + 1000; + + auto gmt_time = gmtime(&now_time); + printf(colorReset); + std::cout << std::put_time(gmt_time, "[%Y-%m-%d %H:%M:%S") << '.' + << std::setfill('0') << std::setw(3) << nowMs.count() << "] "; + } + } + + void printFilename(std::string filename) { #ifdef _WIN32 - std::string basenameStr(filename.substr(filename.rfind("\\") + 1)); + std::string basenameStr(filename.substr(filename.rfind("\\") + 1)); #else - std::string basenameStr(filename.substr(filename.rfind("/") + 1)); + std::string basenameStr(filename.substr(filename.rfind("/") + 1)); #endif - unsigned long hash = 5381; - for (char const &c : basenameStr) - { - hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ - } + unsigned long hash = 5381; + for (char const& c : basenameStr) { + hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ + } - printf("\033[0;%dm", allColors[hash % NColors]); + printf("\033[0;%dm", allColors[hash % NColors]); - printf("%s", basenameStr.c_str()); - printf(colorReset); - } + printf("%s", basenameStr.c_str()); + printf(colorReset); + } - private: - static constexpr const char *colorReset = "\033[0m"; - static constexpr const char *colorRed = "\033[0;31m"; - static constexpr const char *colorBlue = "\033[0;34m"; - static constexpr const int NColors = 15; - static constexpr int allColors[NColors] = {31, 32, 33, 34, 35, 36, 37, 90, 91, 92, 93, 94, 95, 96, 97}; - }; + private: + static constexpr const char* colorReset = "\033[0m"; + static constexpr const char* colorRed = "\033[0;31m"; + static constexpr const char* colorBlue = "\033[0;34m"; + static constexpr const int NColors = 15; + static constexpr int allColors[NColors] = {31, 32, 33, 34, 35, 36, 37, 90, + 91, 92, 93, 94, 95, 96, 97}; +}; - void setDefaultLogger(); - void enableSubmoduleLogging(); -} +void setDefaultLogger(); +void enableSubmoduleLogging(); +void enableTimestampLogging(); +} // namespace bell -#define BELL_LOG(type, ...) \ - do \ - { \ - bell::bellGlobalLogger->type(__FILE__, __LINE__, __VA_ARGS__); \ - } while (0) +#define BELL_LOG(type, ...) \ + do { \ + bell::bellGlobalLogger->type(__FILE__, __LINE__, __VA_ARGS__); \ + } while (0) -#endif \ No newline at end of file +#endif diff --git a/components/spotify/cspot/bell/main/utilities/include/BellTask.h b/components/spotify/cspot/bell/main/utilities/include/BellTask.h index b549fa4a..7f0fc3be 100644 --- a/components/spotify/cspot/bell/main/utilities/include/BellTask.h +++ b/components/spotify/cspot/bell/main/utilities/include/BellTask.h @@ -15,8 +15,8 @@ #include #endif -#include #include +#include namespace bell { class Task { diff --git a/components/spotify/cspot/bell/main/utilities/include/BellUtils.h b/components/spotify/cspot/bell/main/utilities/include/BellUtils.h index ac6921cd..c196368c 100644 --- a/components/spotify/cspot/bell/main/utilities/include/BellUtils.h +++ b/components/spotify/cspot/bell/main/utilities/include/BellUtils.h @@ -1,14 +1,16 @@ #ifndef EUPHONIUM_BELL_UTILS #define EUPHONIUM_BELL_UTILS -#include +#include // for int32_t, int64_t +#include // for NULL #ifdef _WIN32 #include #else -#include +#include // for timeval, gettimeofday +#include // for usleep #endif -#include -#include +#include // for floor +#include // for string #ifdef ESP_PLATFORM #include "esp_system.h" @@ -28,9 +30,9 @@ struct tv { #if _WIN32 static const uint64_t EPOCH = ((uint64_t)116444736000000000ULL); - SYSTEMTIME system_time; - FILETIME file_time; - uint64_t time; + SYSTEMTIME system_time; + FILETIME file_time; + uint64_t time; GetSystemTime(&system_time); SystemTimeToFileTime(&system_time, &file_time); @@ -50,7 +52,9 @@ struct tv { int32_t sec; int32_t usec; - int64_t ms() { return (sec * (int64_t)1000) + (usec / 1000); } + int64_t ms() { + return (sec * (int64_t)1000) + (usec / 1000); + } tv operator+(const tv& other) const { tv result(*this); @@ -95,7 +99,6 @@ struct tv { #define BELL_SLEEP_MS(ms) Sleep(ms) #define BELL_YIELD() ; #else -#include #define BELL_SLEEP_MS(ms) usleep(ms * 1000) #define BELL_YIELD() ; diff --git a/components/spotify/cspot/bell/main/utilities/include/Crypto.h b/components/spotify/cspot/bell/main/utilities/include/Crypto.h index bafb5f07..503e0cca 100644 --- a/components/spotify/cspot/bell/main/utilities/include/Crypto.h +++ b/components/spotify/cspot/bell/main/utilities/include/Crypto.h @@ -1,83 +1,76 @@ #ifndef BELL_CRYPTO_H #define BELL_CRYPTO_H -#define Crypto CryptoMbedTLS - -#include -#include -#include -#include - -extern "C" { -#include "aes.h" -} -#include -#include -#include -#include -#include -#include -#include +#include // for string +#include // for vector +#include // for mbedtls_aes_context +#include // for mbedtls_md_context_t +#include // for size_t +#include // for uint8_t #define DH_KEY_SIZE 96 const static unsigned char DHPrime[] = { /* Well-known Group 1, 768-bit prime */ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, - 0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6, - 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, - 0x08, 0x8a, 0x67, 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6, - 0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e, - 0x34, 0x04, 0xdd, 0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a, - 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, 0xf2, 0x5f, 0x14, - 0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45, - 0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, 0xf4, - 0x4c, 0x42, 0xe9, 0xa6, 0x3a, 0x36, 0x20, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff -}; + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, 0xda, 0xa2, + 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, + 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6, + 0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd, + 0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, + 0xf2, 0x5f, 0x14, 0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45, + 0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, 0xf4, 0x4c, 0x42, 0xe9, + 0xa6, 0x3a, 0x36, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; static unsigned char DHGenerator[1] = {2}; class CryptoMbedTLS { -private: - mbedtls_md_context_t sha1Context; - mbedtls_aes_context aesCtx; - bool aesCtxInitialized = false; -public: - CryptoMbedTLS(); - ~CryptoMbedTLS(); - // Base64 - std::vector base64Decode(const std::string& data); - std::string base64Encode(const std::vector& data); + private: + mbedtls_md_context_t sha1Context; + mbedtls_aes_context aesCtx; + bool aesCtxInitialized = false; - // Sha1 - void sha1Init(); - void sha1Update(const std::string& s); - void sha1Update(const std::vector& vec); - std::string sha1Final(); - std::vector sha1FinalBytes(); + public: + CryptoMbedTLS(); + ~CryptoMbedTLS(); + // Base64 + std::vector base64Decode(const std::string& data); + std::string base64Encode(const std::vector& data); - // HMAC SHA1 - std::vector sha1HMAC(const std::vector& inputKey, const std::vector& message); + // Sha1 + void sha1Init(); + void sha1Update(const std::string& s); + void sha1Update(const std::vector& vec); + std::string sha1Final(); + std::vector sha1FinalBytes(); - // AES CTR - void aesCTRXcrypt(const std::vector& key, std::vector& iv, uint8_t* data, size_t nbytes); - - // AES ECB - void aesECBdecrypt(const std::vector& key, std::vector& data); + // HMAC SHA1 + std::vector sha1HMAC(const std::vector& inputKey, + const std::vector& message); - // Diffie Hellman - std::vector publicKey; - std::vector privateKey; - void dhInit(); - std::vector dhCalculateShared(const std::vector& remoteKey); + // AES CTR + void aesCTRXcrypt(const std::vector& key, std::vector& iv, + uint8_t* data, size_t nbytes); - // PBKDF2 - std::vector pbkdf2HmacSha1(const std::vector& password, const std::vector& salt, int iterations, int digestSize); + // AES ECB + void aesECBdecrypt(const std::vector& key, + std::vector& data); - // Random stuff - std::vector generateVectorWithRandomData(size_t length); + // Diffie Hellman + std::vector publicKey; + std::vector privateKey; + void dhInit(); + std::vector dhCalculateShared(const std::vector& remoteKey); + + // PBKDF2 + std::vector pbkdf2HmacSha1(const std::vector& password, + const std::vector& salt, + int iterations, int digestSize); + + // Random stuff + std::vector generateVectorWithRandomData(size_t length); }; +#define Crypto CryptoMbedTLS + #endif \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/utilities/include/NanoPBHelper.h b/components/spotify/cspot/bell/main/utilities/include/NanoPBHelper.h index f0c68eb5..f488a7c5 100644 --- a/components/spotify/cspot/bell/main/utilities/include/NanoPBHelper.h +++ b/components/spotify/cspot/bell/main/utilities/include/NanoPBHelper.h @@ -1,47 +1,51 @@ #pragma once -#include -#include "pb_encode.h" -#include "pb_decode.h" -#include +#include // for uint8_t +#include // for printf +#include // for string +#include // for vector -std::vector pbEncode(const pb_msgdesc_t *fields, const void *src_struct); +#include "pb.h" // for pb_msgdesc_t, pb_bytes_array_t, PB_GET_ERROR +#include "pb_decode.h" // for pb_istream_from_buffer, pb_decode, pb_istream_s + +std::vector pbEncode(const pb_msgdesc_t* fields, + const void* src_struct); pb_bytes_array_t* vectorToPbArray(const std::vector& vectorToPack); -void packString(char* &dst, std::string stringToPack); +void packString(char*& dst, std::string stringToPack); std::vector pbArrayToVector(pb_bytes_array_t* pbArray); template -T pbDecode(const pb_msgdesc_t *fields, std::vector &data) -{ +T pbDecode(const pb_msgdesc_t* fields, std::vector& data) { - T result = {}; - // Create stream - pb_istream_t stream = pb_istream_from_buffer(&data[0], data.size()); - - // Decode the message - if (pb_decode(&stream, fields, &result) == false) { - printf("Decode failed: %s\n", PB_GET_ERROR(&stream)); - } - return result; + T result = {}; + // Create stream + pb_istream_t stream = pb_istream_from_buffer(&data[0], data.size()); + + // Decode the message + if (pb_decode(&stream, fields, &result) == false) { + printf("Decode failed: %s\n", PB_GET_ERROR(&stream)); + } + + return result; } template -void pbDecode(T &result, const pb_msgdesc_t *fields, std::vector &data) -{ - // Create stream - pb_istream_t stream = pb_istream_from_buffer(&data[0], data.size()); - - // Decode the message - if (pb_decode(&stream, fields, &result) == false) { - printf("Decode failed: %s\n", PB_GET_ERROR(&stream)); - } +void pbDecode(T& result, const pb_msgdesc_t* fields, + std::vector& data) { + // Create stream + pb_istream_t stream = pb_istream_from_buffer(&data[0], data.size()); + + // Decode the message + if (pb_decode(&stream, fields, &result) == false) { + printf("Decode failed: %s\n", PB_GET_ERROR(&stream)); + } } -void pbPutString(const std::string &stringToPack, char* dst); -void pbPutCharArray(const char * stringToPack, char* dst); -void pbPutBytes(const std::vector &data, pb_bytes_array_t &dst); +void pbPutString(const std::string& stringToPack, char* dst); +void pbPutCharArray(const char* stringToPack, char* dst); +void pbPutBytes(const std::vector& data, pb_bytes_array_t& dst); -const char* pb_encode_to_string(const pb_msgdesc_t *fields, const void *data); +const char* pb_encode_to_string(const pb_msgdesc_t* fields, const void* data); diff --git a/components/spotify/cspot/bell/main/utilities/include/Queue.h b/components/spotify/cspot/bell/main/utilities/include/Queue.h index ae938ea2..80b3c0b8 100644 --- a/components/spotify/cspot/bell/main/utilities/include/Queue.h +++ b/components/spotify/cspot/bell/main/utilities/include/Queue.h @@ -1,117 +1,101 @@ #ifndef BELL_QUEUE_H #define BELL_QUEUE_H -#include #include #include -#include +#include -namespace bell -{ - template - class Queue - { - private: - /// Queue - std::queue m_queue; - /// Mutex to controll multiple access - mutable std::mutex m_mutex; - /// Conditional variable used to fire event - std::condition_variable m_cv; - /// Atomic variable used to terminate immediately wpop and wtpop functions - std::atomic m_forceExit = false; +namespace bell { +template +class Queue { + private: + /// Queue + std::queue m_queue; + /// Mutex to controll multiple access + mutable std::mutex m_mutex; + /// Conditional variable used to fire event + std::condition_variable m_cv; + /// Atomic variable used to terminate immediately wpop and wtpop functions + std::atomic m_forceExit = false; - public: - /// Add a new element in the queue. - /// New element. - void push(dataType const &data) - { - m_forceExit.store(false); - std::unique_lock lk(m_mutex); - m_queue.push(data); - lk.unlock(); - m_cv.notify_one(); - } - /// Check queue empty. - /// True if the queue is empty. - bool isEmpty() const - { - std::unique_lock lk(m_mutex); - return m_queue.empty(); - } - /// Pop element from queue. - /// [in,out] Element. - /// false if the queue is empty. - bool pop(dataType &popped_value) - { - std::unique_lock lk(m_mutex); - if (m_queue.empty()) - { - return false; - } - else - { - popped_value = m_queue.front(); - m_queue.pop(); - return true; - } - } - /// Wait and pop an element in the queue. - /// [in,out] Element. - /// False for forced exit. - bool wpop(dataType &popped_value) - { - std::unique_lock lk(m_mutex); - m_cv.wait(lk, [&]() -> bool - { return !m_queue.empty() || m_forceExit.load(); }); - if (m_forceExit.load()) - return false; - popped_value = m_queue.front(); - m_queue.pop(); - return true; - } - /// Timed wait and pop an element in the queue. - /// [in,out] Element. - /// [in] Wait time. - /// False for timeout or forced exit. - bool wtpop(dataType &popped_value, long milliseconds = 1000) - { - std::unique_lock lk(m_mutex); - m_cv.wait_for(lk, std::chrono::milliseconds(milliseconds), [&]() -> bool - { return !m_queue.empty() || m_forceExit.load(); }); - if (m_forceExit.load()) - return false; - if (m_queue.empty()) - return false; - popped_value = m_queue.front(); - m_queue.pop(); - return true; - } - /// Queue size. - int size() - { - std::unique_lock lk(m_mutex); - return static_cast(m_queue.size()); - } - /// Free the queue and force stop. - void clear() - { - m_forceExit.store(true); - std::unique_lock lk(m_mutex); - while (!m_queue.empty()) - { - //delete m_queue.front(); - m_queue.pop(); - } - lk.unlock(); - m_cv.notify_one(); - } - /// Check queue in forced exit state. - bool isExit() const - { - return m_forceExit.load(); - } - }; -} + public: + /// Add a new element in the queue. + /// New element. + void push(dataType const& data) { + m_forceExit.store(false); + std::unique_lock lk(m_mutex); + m_queue.push(data); + lk.unlock(); + m_cv.notify_one(); + } + /// Check queue empty. + /// True if the queue is empty. + bool isEmpty() const { + std::unique_lock lk(m_mutex); + return m_queue.empty(); + } + /// Pop element from queue. + /// [in,out] Element. + /// false if the queue is empty. + bool pop(dataType& popped_value) { + std::unique_lock lk(m_mutex); + if (m_queue.empty()) { + return false; + } else { + popped_value = m_queue.front(); + m_queue.pop(); + return true; + } + } + /// Wait and pop an element in the queue. + /// [in,out] Element. + /// False for forced exit. + bool wpop(dataType& popped_value) { + std::unique_lock lk(m_mutex); + m_cv.wait(lk, + [&]() -> bool { return !m_queue.empty() || m_forceExit.load(); }); + if (m_forceExit.load()) + return false; + popped_value = m_queue.front(); + m_queue.pop(); + return true; + } + /// Timed wait and pop an element in the queue. + /// [in,out] Element. + /// [in] Wait time. + /// False for timeout or forced exit. + bool wtpop(dataType& popped_value, long milliseconds = 1000) { + std::unique_lock lk(m_mutex); + m_cv.wait_for(lk, std::chrono::milliseconds(milliseconds), [&]() -> bool { + return !m_queue.empty() || m_forceExit.load(); + }); + if (m_forceExit.load()) + return false; + if (m_queue.empty()) + return false; + popped_value = m_queue.front(); + m_queue.pop(); + return true; + } + /// Queue size. + int size() { + std::unique_lock lk(m_mutex); + return static_cast(m_queue.size()); + } + /// Free the queue and force stop. + void clear() { + m_forceExit.store(true); + std::unique_lock lk(m_mutex); + while (!m_queue.empty()) { + //delete m_queue.front(); + m_queue.pop(); + } + lk.unlock(); + m_cv.notify_one(); + } + /// Check queue in forced exit state. + bool isExit() const { return m_forceExit.load(); } +}; +} // namespace bell #endif \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/utilities/include/TimeDefs.h b/components/spotify/cspot/bell/main/utilities/include/TimeDefs.h index 21d602ab..8299b679 100644 --- a/components/spotify/cspot/bell/main/utilities/include/TimeDefs.h +++ b/components/spotify/cspot/bell/main/utilities/include/TimeDefs.h @@ -5,4 +5,4 @@ #ifndef EUPHONIUMCLI_TIMEDEFS_H #define EUPHONIUMCLI_TIMEDEFS_H -#endif // EUPHONIUMCLI_TIMEDEFS_H +#endif // EUPHONIUMCLI_TIMEDEFS_H diff --git a/components/spotify/cspot/bell/main/utilities/include/aes.h b/components/spotify/cspot/bell/main/utilities/include/aes.h index 3a24bd28..3eba21de 100644 --- a/components/spotify/cspot/bell/main/utilities/include/aes.h +++ b/components/spotify/cspot/bell/main/utilities/include/aes.h @@ -1,8 +1,8 @@ #ifndef _AES_H_ #define _AES_H_ -#include #include +#include // #define the macros below to 1/0 to enable/disable the mode of operation. // @@ -12,37 +12,35 @@ // The #ifndef-guard allows it to be configured before #include'ing or at compile time. #ifndef CBC - #define CBC 1 +#define CBC 1 #endif #ifndef ECB - #define ECB 1 +#define ECB 1 #endif #ifndef CTR - #define CTR 1 +#define CTR 1 #endif - // #define AES128 1 #define AES192 1 //#define AES256 1 -#define AES_BLOCKLEN 16 // Block length in bytes - AES is 128b block only +#define AES_BLOCKLEN 16 // Block length in bytes - AES is 128b block only #if defined(AES256) && (AES256 == 1) - #define AES_KEYLEN 32 - #define AES_keyExpSize 240 +#define AES_KEYLEN 32 +#define AES_keyExpSize 240 #elif defined(AES192) && (AES192 == 1) - #define AES_KEYLEN 24 - #define AES_keyExpSize 208 +#define AES_KEYLEN 24 +#define AES_keyExpSize 208 #else - #define AES_KEYLEN 16 // Key length in bytes - #define AES_keyExpSize 176 +#define AES_KEYLEN 16 // Key length in bytes +#define AES_keyExpSize 176 #endif -struct AES_ctx -{ +struct AES_ctx { uint8_t RoundKey[AES_keyExpSize]; #if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) uint8_t Iv[AES_BLOCKLEN]; @@ -51,41 +49,39 @@ struct AES_ctx void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key); #if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) -void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv); +void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, + const uint8_t* iv); void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv); #endif #if defined(ECB) && (ECB == 1) -// buffer size is exactly AES_BLOCKLEN bytes; -// you need only AES_init_ctx as IV is not used in ECB +// buffer size is exactly AES_BLOCKLEN bytes; +// you need only AES_init_ctx as IV is not used in ECB // NB: ECB is considered insecure for most uses void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf); void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf); -#endif // #if defined(ECB) && (ECB == !) - +#endif // #if defined(ECB) && (ECB == !) #if defined(CBC) && (CBC == 1) // buffer size MUST be mutile of AES_BLOCKLEN; // Suggest https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme // NOTES: you need to set IV in ctx via AES_init_ctx_iv() or AES_ctx_set_iv() -// no IV should ever be reused with the same key +// no IV should ever be reused with the same key void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); -#endif // #if defined(CBC) && (CBC == 1) - +#endif // #if defined(CBC) && (CBC == 1) #if defined(CTR) && (CTR == 1) -// Same function for encrypting as for decrypting. +// Same function for encrypting as for decrypting. // IV is incremented for every block, and used after encryption as XOR-compliment for output // Suggesting https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme // NOTES: you need to set IV in ctx with AES_init_ctx_iv() or AES_ctx_set_iv() -// no IV should ever be reused with the same key +// no IV should ever be reused with the same key void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); -#endif // #if defined(CTR) && (CTR == 1) +#endif // #if defined(CTR) && (CTR == 1) - -#endif // _AES_H_ \ No newline at end of file +#endif // _AES_H_ \ No newline at end of file diff --git a/components/spotify/cspot/include/AccessKeyFetcher.h b/components/spotify/cspot/include/AccessKeyFetcher.h index 11e5a73c..949c1461 100644 --- a/components/spotify/cspot/include/AccessKeyFetcher.h +++ b/components/spotify/cspot/include/AccessKeyFetcher.h @@ -3,29 +3,41 @@ #include // for function #include // for shared_ptr #include // for string +#include +namespace bell { +class WrappedSemaphore; +}; namespace cspot { struct Context; class AccessKeyFetcher { public: AccessKeyFetcher(std::shared_ptr ctx); - ~AccessKeyFetcher(); - typedef std::function Callback; + /** + * @brief Checks if key is expired + * @returns true when currently held access key is not valid + */ + bool isExpired(); - void getAccessKey(Callback callback); + /** + * @brief Fetches a new access key + * @remark In case the key is expired, this function blocks until a refresh is done. + * @returns access key + */ + std::string getAccessKey(); + + /** + * @brief Forces a refresh of the access key + */ + void updateAccessKey(); private: - const std::string CLIENT_ID = - "65b708073fc0480ea92a077233ca87bd"; // Spotify web client's client id - const std::string SCOPES = - "streaming,user-library-read,user-library-modify,user-top-read,user-read-" - "recently-played"; // Required access scopes - std::shared_ptr ctx; + std::shared_ptr updateSemaphore; - bool isExpired(); + std::atomic keyPending = false; std::string accessKey; long long int expiresAt; }; diff --git a/components/spotify/cspot/include/ApResolve.h b/components/spotify/cspot/include/ApResolve.h index 13b282a9..fafb4485 100644 --- a/components/spotify/cspot/include/ApResolve.h +++ b/components/spotify/cspot/include/ApResolve.h @@ -8,17 +8,16 @@ namespace cspot { class ApResolve { - private: - std::string apOverride; - public: ApResolve(std::string apOverride); /** - * @brief Connects to spotify's servers and returns first valid ap address - * - * @return std::string Address in form of url:port - */ + * @brief Connects to spotify's servers and returns first valid ap address + * @returns std::string Address in form of url:port + */ std::string fetchFirstApAddress(); + + private: + std::string apOverride; }; } // namespace cspot diff --git a/components/spotify/cspot/include/AuthChallenges.h b/components/spotify/cspot/include/AuthChallenges.h index 38d5a161..379f2e7c 100644 --- a/components/spotify/cspot/include/AuthChallenges.h +++ b/components/spotify/cspot/include/AuthChallenges.h @@ -1,10 +1,9 @@ #pragma once - -#include // for uint8_t -#include // for unique_ptr -#include // for string -#include // for vector +#include // for uint8_t +#include // for unique_ptr +#include // for string +#include // for vector #include "Crypto.h" // for Crypto #include "protobuf/authentication.pb.h" // for ClientResponseEncrypted @@ -16,20 +15,45 @@ class AuthChallenges { AuthChallenges(); ~AuthChallenges(); - std::vector shanSendKey = {}; - std::vector shanRecvKey = {}; - + /** + * @brief Prepares a spotify authentication packet + * @param authBlob authentication blob bytes + * @param authType value representing spotify's authentication type + * @param deviceId device id to use during auth. + * @param username spotify's username + * + * @returns vector containing bytes of the authentication packet + */ std::vector prepareAuthPacket(std::vector& authBlob, int authType, const std::string& deviceId, const std::string& username); + + /** + * @brief Solves the ApHello packet, and returns a packet with response + * + * @param helloPacket hello packet bytes received from the server + * @param data authentication data received from the server + * + * @returns vector containing response packet + */ std::vector solveApHello(std::vector& helloPacket, std::vector& data); + /** + * @brief Prepares an client hello packet, used for initial auth with spotify + * + * @returns vector containing the packet's data + */ std::vector prepareClientHello(); + std::vector shanSendKey = {}; + std::vector shanRecvKey = {}; + private: const long long SPOTIFY_VERSION = 0x10800000000; + + // Protobuf structures ClientResponseEncrypted authRequest; ClientResponsePlaintext clientResPlaintext; ClientHello clientHello; @@ -37,4 +61,4 @@ class AuthChallenges { std::unique_ptr crypto; }; -} // namespace cspot \ No newline at end of file +} // namespace cspot diff --git a/components/spotify/cspot/include/CSpotContext.h b/components/spotify/cspot/include/CSpotContext.h index 0237da85..ae1c77d2 100644 --- a/components/spotify/cspot/include/CSpotContext.h +++ b/components/spotify/cspot/include/CSpotContext.h @@ -2,10 +2,10 @@ #include +#include "LoginBlob.h" #include "MercurySession.h" #include "TimeProvider.h" #include "protobuf/metadata.pb.h" -#include "LoginBlob.h" namespace cspot { struct Context { @@ -25,7 +25,8 @@ struct Context { std::shared_ptr timeProvider; std::shared_ptr session; - static std::shared_ptr createFromBlob(std::shared_ptr blob) { + static std::shared_ptr createFromBlob( + std::shared_ptr blob) { auto ctx = std::make_shared(); ctx->timeProvider = std::make_shared(); @@ -37,6 +38,5 @@ struct Context { return ctx; } - }; -} // namespace cspot \ No newline at end of file +} // namespace cspot diff --git a/components/spotify/cspot/include/ConstantParameters.h b/components/spotify/cspot/include/ConstantParameters.h index 7cdd5495..12ebac30 100644 --- a/components/spotify/cspot/include/ConstantParameters.h +++ b/components/spotify/cspot/include/ConstantParameters.h @@ -7,11 +7,11 @@ extern char deviceId[]; namespace cspot { // Hardcoded information sent to spotify servers -const char * const informationString = "cspot-player"; -const char * const brandName = "cspot"; -const char * const versionString = "cspot-1.1"; -const char * const protocolVersion = "2.7.1"; -const char * const defaultDeviceName = "CSpot"; -const char * const swVersion = "1.0.0"; +const char* const informationString = "cspot-player"; +const char* const brandName = "cspot"; +const char* const versionString = "cspot-1.1"; +const char* const protocolVersion = "2.7.1"; +const char* const defaultDeviceName = "CSpot"; +const char* const swVersion = "1.0.0"; -} \ No newline at end of file +} // namespace cspot \ No newline at end of file diff --git a/components/spotify/cspot/include/CspotAssert.h b/components/spotify/cspot/include/CspotAssert.h index de535984..ca0dd8fd 100644 --- a/components/spotify/cspot/include/CspotAssert.h +++ b/components/spotify/cspot/include/CspotAssert.h @@ -3,14 +3,13 @@ #include #include -#define CSPOT_ASSERT(CONDITION, MESSAGE) \ - do \ - { \ - if (!(CONDITION)) \ - { \ - printf("At %s in %s:%d\n Assertion %s failed: %s", __func__, __FILE__, __LINE__, #CONDITION, MESSAGE); \ - abort(); \ - } \ - } while (0) +#define CSPOT_ASSERT(CONDITION, MESSAGE) \ + do { \ + if (!(CONDITION)) { \ + printf("At %s in %s:%d\n Assertion %s failed: %s", __func__, __FILE__, \ + __LINE__, #CONDITION, MESSAGE); \ + abort(); \ + } \ + } while (0) #endif diff --git a/components/spotify/cspot/include/Logger.h b/components/spotify/cspot/include/Logger.h index 40c3c333..60864ff8 100644 --- a/components/spotify/cspot/include/Logger.h +++ b/components/spotify/cspot/include/Logger.h @@ -2,8 +2,7 @@ #include -#define CSPOT_LOG(type, ...) \ - do \ - { \ - bell::bellGlobalLogger->type(__FILE__, __LINE__, "cspot", __VA_ARGS__); \ - } while (0) +#define CSPOT_LOG(type, ...) \ + do { \ + bell::bellGlobalLogger->type(__FILE__, __LINE__, "cspot", __VA_ARGS__); \ + } while (0) diff --git a/components/spotify/cspot/include/LoginBlob.h b/components/spotify/cspot/include/LoginBlob.h index 2a1f3611..0a8bb00b 100644 --- a/components/spotify/cspot/include/LoginBlob.h +++ b/components/spotify/cspot/include/LoginBlob.h @@ -1,10 +1,10 @@ #pragma once -#include // for uint8_t, uint32_t -#include // for map -#include // for unique_ptr -#include // for string -#include // for vector +#include // for uint8_t, uint32_t +#include // for map +#include // for unique_ptr +#include // for string +#include // for vector #include "Crypto.h" // for CryptoMbedTLS, Crypto diff --git a/components/spotify/cspot/include/MercurySession.h b/components/spotify/cspot/include/MercurySession.h index 4f4126e7..07496cc2 100644 --- a/components/spotify/cspot/include/MercurySession.h +++ b/components/spotify/cspot/include/MercurySession.h @@ -1,13 +1,13 @@ #pragma once -#include // for atomic -#include // for uint8_t, uint64_t, uint32_t -#include // for function -#include // for shared_ptr -#include // for mutex -#include // for string -#include // for unordered_map -#include // for vector +#include // for atomic +#include // for uint8_t, uint64_t, uint32_t +#include // for function +#include // for shared_ptr +#include // for mutex +#include // for string +#include // for unordered_map +#include // for vector #include "BellTask.h" // for Task #include "Packet.h" // for Packet @@ -15,7 +15,7 @@ #include "Session.h" // for Session #include "protobuf/mercury.pb.h" // for Header -namespace cspot { +namespace cspot { class TimeProvider; class MercurySession : public bell::Task, public cspot::Session { @@ -33,7 +33,8 @@ class MercurySession : public bell::Task, public cspot::Session { }; typedef std::function ResponseCallback; - typedef std::function&)> AudioKeyCallback; + typedef std::function&)> + AudioKeyCallback; typedef std::function ConnectionEstabilishedCallback; enum class RequestType : uint8_t { @@ -82,7 +83,11 @@ class MercurySession : public bell::Task, public cspot::Session { return this->executeSubscription(type, uri, callback, nullptr, parts); } - void requestAudioKey(const std::vector& trackId, + void unregister(uint64_t sequenceId); + + void unregisterAudioKey(uint32_t sequenceId); + + uint32_t requestAudioKey(const std::vector& trackId, const std::vector& fileId, AudioKeyCallback audioCallback); @@ -108,7 +113,7 @@ class MercurySession : public bell::Task, public cspot::Session { std::unordered_map callbacks; std::unordered_map subscriptions; - AudioKeyCallback audioKeyCallback; + std::unordered_map audioKeyCallbacks; uint64_t sequenceId = 1; uint32_t audioKeySequence = 1; diff --git a/components/spotify/cspot/include/PlainConnection.h b/components/spotify/cspot/include/PlainConnection.h index 6bb37b47..0bd735cc 100644 --- a/components/spotify/cspot/include/PlainConnection.h +++ b/components/spotify/cspot/include/PlainConnection.h @@ -6,7 +6,7 @@ #include "win32shim.h" #else -#include // for size_t +#include // for size_t #endif #include // for uint8_t #include // for function @@ -37,8 +37,8 @@ class PlainConnection { void readBlock(const uint8_t* dst, size_t size); size_t writeBlock(const std::vector& data); - private: - int apSock; + private: + int apSock; }; } // namespace cspot diff --git a/components/spotify/cspot/include/PlaybackState.h b/components/spotify/cspot/include/PlaybackState.h index b13fd71e..a79eab01 100644 --- a/components/spotify/cspot/include/PlaybackState.h +++ b/components/spotify/cspot/include/PlaybackState.h @@ -1,10 +1,11 @@ #pragma once -#include // for uint8_t, uint32_t -#include // for shared_ptr -#include // for string -#include // for vector +#include // for uint8_t, uint32_t +#include // for shared_ptr +#include // for string +#include // for vector +#include "TrackReference.h" #include "protobuf/spirc.pb.h" // for Frame, TrackRef, CapabilityType, Mess... namespace cspot { @@ -13,8 +14,10 @@ struct Context; class PlaybackState { private: std::shared_ptr ctx; + uint32_t seqNum = 0; uint8_t capabilityIndex = 0; + std::vector frameData; void addCapability( @@ -24,6 +27,9 @@ class PlaybackState { public: Frame innerFrame; Frame remoteFrame; + + std::vector remoteTracks; + enum class State { Playing, Stopped, Loading, Paused }; /** @@ -74,56 +80,9 @@ class PlaybackState { void setVolume(uint32_t volume); /** - * @brief Enables queue shuffling. - * - * Sets shuffle parameter on local frame, and in case shuffling is enabled, - * it will randomize the entire local queue. - * - * @param shuffle whenever should shuffle + * @brief Updates local track queue from remote data. */ - void setShuffle(bool shuffle); - - /** - * @brief Enables repeat - * - * @param repeat should repeat param - */ - void setRepeat(bool repeat); - - /** - * @brief Updates local track queue from remote data. - */ - void updateTracks(); - - /** - * @brief Changes playback to next queued track. - * - * Will go back to first track if current track is last track in queue. - * In that case, it will pause if repeat is disabled. - */ - bool nextTrack(); - - /** - * @brief Changes playback to previous queued track. - * - * Will stop if current track is the first track in queue and repeat is disabled. - * If repeat is enabled, it will loop back to the last track in queue. - */ - void prevTrack(); - - /** - * @brief Gets the current track reference. - * - * @return std::shared_ptr pointer to track reference - */ - TrackRef* getCurrentTrackRef(); - - /** - * @brief Gets reference to next track in queue, or nullptr if there is no next track. - * - * @return std::shared_ptr pointer to track reference - */ - TrackRef* getNextTrackRef(); + void syncWithRemote(); /** * @brief Encodes current frame into binary data via protobuf. @@ -132,5 +91,7 @@ class PlaybackState { * @return std::vector binary frame data */ std::vector encodeCurrentFrame(MessageType typ); + + bool decodeRemoteFrame(std::vector& data); }; -} // namespace cspot \ No newline at end of file +} // namespace cspot diff --git a/components/spotify/cspot/include/Shannon.h b/components/spotify/cspot/include/Shannon.h index aa19bdb8..23e9f172 100644 --- a/components/spotify/cspot/include/Shannon.h +++ b/components/spotify/cspot/include/Shannon.h @@ -4,41 +4,40 @@ #include // for uint32_t, uint8_t #include // for vector -class Shannon -{ -public: - static constexpr unsigned int N = 16; +class Shannon { + public: + static constexpr unsigned int N = 16; - void key(const std::vector &key); /* set key */ - void nonce(const std::vector &nonce); /* set Init Vector */ - void stream(std::vector &buf); /* stream cipher */ - void maconly(std::vector &buf); /* accumulate MAC */ - void encrypt(std::vector &buf); /* encrypt + MAC */ - void decrypt(std::vector &buf); /* finalize + MAC */ - void finish(std::vector &buf); /* finalise MAC */ + void key(const std::vector& key); /* set key */ + void nonce(const std::vector& nonce); /* set Init Vector */ + void stream(std::vector& buf); /* stream cipher */ + void maconly(std::vector& buf); /* accumulate MAC */ + void encrypt(std::vector& buf); /* encrypt + MAC */ + void decrypt(std::vector& buf); /* finalize + MAC */ + void finish(std::vector& buf); /* finalise MAC */ -private: - static constexpr unsigned int FOLD = Shannon::N; - static constexpr unsigned int INITKONST = 0x6996c53a; - static constexpr unsigned int KEYP = 13; - uint32_t R[Shannon::N]; - uint32_t CRC[Shannon::N]; - uint32_t initR[Shannon::N]; - uint32_t konst; - uint32_t sbuf; - uint32_t mbuf; - int nbuf; - static uint32_t sbox1(uint32_t w); - static uint32_t sbox2(uint32_t w); - void cycle(); - void crcfunc(uint32_t i); - void macfunc(uint32_t i); - void initState(); - void saveState(); - void reloadState(); - void genkonst(); - void diffuse(); - void loadKey(const std::vector &key); + private: + static constexpr unsigned int FOLD = Shannon::N; + static constexpr unsigned int INITKONST = 0x6996c53a; + static constexpr unsigned int KEYP = 13; + uint32_t R[Shannon::N]; + uint32_t CRC[Shannon::N]; + uint32_t initR[Shannon::N]; + uint32_t konst; + uint32_t sbuf; + uint32_t mbuf; + int nbuf; + static uint32_t sbox1(uint32_t w); + static uint32_t sbox2(uint32_t w); + void cycle(); + void crcfunc(uint32_t i); + void macfunc(uint32_t i); + void initState(); + void saveState(); + void reloadState(); + void genkonst(); + void diffuse(); + void loadKey(const std::vector& key); }; #endif \ No newline at end of file diff --git a/components/spotify/cspot/include/ShannonConnection.h b/components/spotify/cspot/include/ShannonConnection.h index b1a1fd01..30a8cbac 100644 --- a/components/spotify/cspot/include/ShannonConnection.h +++ b/components/spotify/cspot/include/ShannonConnection.h @@ -1,10 +1,10 @@ #ifndef SHANNONCONNECTION_H #define SHANNONCONNECTION_H -#include // for uint8_t, uint32_t -#include // for shared_ptr, unique_ptr -#include // for mutex -#include // for vector +#include // for uint8_t, uint32_t +#include // for shared_ptr, unique_ptr +#include // for mutex +#include // for vector #include "Packet.h" // for Packet diff --git a/components/spotify/cspot/include/SpircHandler.h b/components/spotify/cspot/include/SpircHandler.h index 5401088b..ecb21082 100644 --- a/components/spotify/cspot/include/SpircHandler.h +++ b/components/spotify/cspot/include/SpircHandler.h @@ -1,14 +1,14 @@ #pragma once -#include // for uint32_t, uint8_t -#include // for function -#include // for shared_ptr, unique_ptr -#include // for string -#include // for variant -#include // for vector +#include // for uint32_t, uint8_t +#include // for function +#include // for shared_ptr, unique_ptr +#include // for string +#include // for variant +#include // for vector -#include "CDNTrackStream.h" // for CDNTrackStream, CDNTrackStream::Track... -#include "PlaybackState.h" // for PlaybackState +#include "CDNAudioFile.h" // for CDNTrackStream, CDNTrackStream::Track... +#include "TrackQueue.h" #include "protobuf/spirc.pb.h" // for MessageType namespace cspot { @@ -31,7 +31,8 @@ class SpircHandler { FLUSH, PLAYBACK_START }; - typedef std::variant EventData; + + typedef std::variant EventData; struct Event { EventType eventType; @@ -47,36 +48,34 @@ class SpircHandler { void setPause(bool pause); - void nextSong(); void previousSong(); + void nextSong(); + void notifyAudioReachedPlayback(); void updatePositionMs(uint32_t position); void setRemoteVolume(int volume); void loadTrackFromURI(const std::string& uri); + std::shared_ptr getTrackQueue() { return trackQueue; } void disconnect(); private: std::shared_ptr ctx; std::shared_ptr trackPlayer; + std::shared_ptr trackQueue; EventHandler eventHandler = nullptr; - cspot::PlaybackState playbackState; - CDNTrackStream::TrackInfo currentTrackInfo; - - bool isTrackFresh = true; - bool isRequestedFromLoad = false; - bool isNextTrackPreloaded = false; - uint32_t nextTrackPosition = 0; + std::shared_ptr playbackState; void sendCmd(MessageType typ); void sendEvent(EventType type); void sendEvent(EventType type, EventData data); + void skipSong(TrackQueue::SkipDirection dir); void handleFrame(std::vector& data); void notify(); }; -} // namespace cspot \ No newline at end of file +} // namespace cspot diff --git a/components/spotify/cspot/include/TrackPlayer.h b/components/spotify/cspot/include/TrackPlayer.h index 5eaf9f50..74735ab4 100644 --- a/components/spotify/cspot/include/TrackPlayer.h +++ b/components/spotify/cspot/include/TrackPlayer.h @@ -1,47 +1,54 @@ #pragma once -#include // for atomic -#include // for uint8_t, int64_t -#include // for size_t, time -#include // for function -#include // for shared_ptr, unique_ptr -#include // for mutex -#include // for string_view -#include // for vector +#include // for atomic +#include // for uint8_t, int64_t +#include // for size_t, time +#include // for function +#include // for shared_ptr, unique_ptr +#include // for mutex +#include // for string_view +#include // for vector -#include "BellTask.h" // for Task -#include "CDNTrackStream.h" // for CDNTrackStream, CDNTrackStream::TrackInfo +#include "BellTask.h" // for Task +#include "CDNAudioFile.h" +#include "TrackQueue.h" namespace bell { class WrappedSemaphore; } // namespace bell + #ifdef BELL_VORBIS_FLOAT #include "vorbis/vorbisfile.h" #else -#include "ivorbisfile.h" // for OggVorbis_File, ov_callbacks +#include "ivorbisfile.h" // for OggVorbis_File, ov_callbacks #endif namespace cspot { class TrackProvider; +class TrackQueue; struct Context; struct TrackReference; class TrackPlayer : bell::Task { public: - typedef std::function TrackLoadedCallback; - typedef std::function DataCallback; + // Callback types + typedef std::function)> TrackLoadedCallback; + typedef std::function DataCallback; typedef std::function EOFCallback; typedef std::function isAiringCallback; - TrackPlayer(std::shared_ptr ctx, isAiringCallback, EOFCallback, TrackLoadedCallback); + TrackPlayer(std::shared_ptr ctx, + std::shared_ptr trackQueue, + EOFCallback eofCallback, TrackLoadedCallback loadedCallback); ~TrackPlayer(); - - void loadTrackFromRef(TrackReference& ref, size_t playbackMs, bool startAutomatically); + + void loadTrackFromRef(TrackReference& ref, size_t playbackMs, + bool startAutomatically); void setDataCallback(DataCallback callback); - - CDNTrackStream::TrackInfo getCurrentTrackInfo(); + + // CDNTrackStream::TrackInfo getCurrentTrackInfo(); void seekMs(size_t ms); - void stopTrack(); + void resetState(); // Vorbis codec callbacks size_t _vorbisRead(void* ptr, size_t size, size_t nmemb); @@ -49,35 +56,39 @@ class TrackPlayer : bell::Task { int _vorbisSeek(int64_t offset, int whence); long _vorbisTell(); - void destroy(); + void stop(); + void start(); private: std::shared_ptr ctx; - std::shared_ptr trackProvider; - std::shared_ptr currentTrackStream; - size_t sequence = std::time(nullptr); + std::shared_ptr trackQueue; + std::shared_ptr currentTrackStream; std::unique_ptr playbackSemaphore; TrackLoadedCallback trackLoaded; DataCallback dataCallback = nullptr; EOFCallback eofCallback; - isAiringCallback isAiring; // Playback control std::atomic currentSongPlaying; std::mutex playbackMutex; - std::mutex seekMutex; - + std::mutex dataOutMutex; + // Vorbis related OggVorbis_File vorbisFile; ov_callbacks vorbisCallbacks; int currentSection; + std::vector pcmBuffer = std::vector(1024); - size_t playbackPosition = 0; bool autoStart = false; - std::atomic isRunning = true; + + std::atomic isRunning = false; + std::atomic pendingReset = false; + std::atomic inFuture = false; + std::atomic pendingSeekPositionMs = 0; + std::mutex runningMutex; void runTask() override; diff --git a/components/spotify/cspot/include/TrackReference.h b/components/spotify/cspot/include/TrackReference.h index ec87e16c..a1ede7be 100644 --- a/components/spotify/cspot/include/TrackReference.h +++ b/components/spotify/cspot/include/TrackReference.h @@ -1,51 +1,36 @@ #pragma once +#include #include #include +#include #include "NanoPBHelper.h" -#include "Utils.h" +#include "pb_decode.h" #include "protobuf/spirc.pb.h" -namespace cspot { +namespace cspot { struct TrackReference { - static constexpr auto base62Alphabet = - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + TrackReference(); // Resolved track GID std::vector gid; + std::string uri, context; + std::optional queued; // Type identifier enum class Type { TRACK, EPISODE }; + Type type; - static TrackReference fromTrackRef(TrackRef* ref) { - TrackReference trackRef; - if (ref->gid != nullptr) { - // For tracks, the GID is already in the protobuf - trackRef.gid = pbArrayToVector(ref->gid); - trackRef.type = Type::TRACK; - } else { - // Episode GID is being fetched via base62 encoded URI - auto uri = std::string(ref->uri); - auto idString = uri.substr(uri.find_last_of(":") + 1, uri.size()); - trackRef.gid = {0}; + void decodeURI(); - std::string_view alphabet(base62Alphabet); - for (int x = 0; x < idString.size(); x++) { - size_t d = alphabet.find(idString[x]); - trackRef.gid = bigNumMultiply(trackRef.gid, 62); - trackRef.gid = bigNumAdd(trackRef.gid, d); - } - } + bool operator==(const TrackReference& other) const; - return trackRef; - } + // Encodes list of track references into a pb structure, used by nanopb + static bool pbEncodeTrackList(pb_ostream_t* stream, const pb_field_t* field, + void* const* arg); - static TrackReference fromGID(std::vector gid, bool isEpisode) { - TrackReference trackRef; - trackRef.gid = gid; - trackRef.type = isEpisode ? Type::EPISODE : Type::TRACK; - return trackRef; - } + static bool pbDecodeTrackList(pb_istream_t* stream, const pb_field_t* field, + void** arg); }; -} // namespace cspot \ No newline at end of file +} // namespace cspot diff --git a/components/spotify/cspot/include/Utils.h b/components/spotify/cspot/include/Utils.h index 95dcce11..a52be4ea 100644 --- a/components/spotify/cspot/include/Utils.h +++ b/components/spotify/cspot/include/Utils.h @@ -1,7 +1,7 @@ #ifndef UTILS_H #define UTILS_H -#include // for snprintf, size_t -#include // for vector +#include // for snprintf, size_t +#include // for vector #ifdef _WIN32 #include #include @@ -55,7 +55,6 @@ std::vector bigNumAdd(std::vector num, int n); unsigned char h2int(char c); - std::string urlDecode(std::string str); /** @@ -64,7 +63,7 @@ std::string urlDecode(std::string str); * @param s string containing hex data * @return std::vector vector containing binary data */ -std::vector stringHexToBytes(const std::string &s); +std::vector stringHexToBytes(const std::string& s); /** * @brief Converts provided bytes into a human readable hex string @@ -72,7 +71,7 @@ std::vector stringHexToBytes(const std::string &s); * @param bytes vector containing binary data * @return std::string string containing hex representation of inputted data */ -std::string bytesToHexString(const std::vector &bytes); +std::string bytesToHexString(const std::vector& bytes); /** * @brief Extracts given type from binary data @@ -83,8 +82,7 @@ std::string bytesToHexString(const std::vector &bytes); * @return T extracted type */ template -T extract(const std::vector &v, int pos) -{ +T extract(const std::vector& v, int pos) { T value; memcpy(&value, &v[pos], sizeof(T)); return value; @@ -98,22 +96,25 @@ T extract(const std::vector &v, int pos) * @return std::vector resulting vector containing binary data */ template -std::vector pack(T data) -{ - std::vector rawData( (std::uint8_t*)&data, (std::uint8_t*)&(data) + sizeof(T)); +std::vector pack(T data) { + std::vector rawData((std::uint8_t*)&data, + (std::uint8_t*)&(data) + sizeof(T)); - return rawData; + return rawData; } -template -std::string string_format( const std::string& format, Args ... args ) -{ - int size_s = std::snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0' - if( size_s <= 0 ){ throw std::runtime_error( "Error during formatting." ); } - auto size = static_cast( size_s ); - std::unique_ptr buf( new char[ size ] ); - std::snprintf( buf.get(), size, format.c_str(), args ... ); - return std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside +template +std::string string_format(const std::string& format, Args... args) { + int size_s = std::snprintf(nullptr, 0, format.c_str(), args...) + + 1; // Extra space for '\0' + if (size_s <= 0) { + throw std::runtime_error("Error during formatting."); + } + auto size = static_cast(size_s); + std::unique_ptr buf(new char[size]); + std::snprintf(buf.get(), size, format.c_str(), args...); + return std::string(buf.get(), + buf.get() + size - 1); // We don't want the '\0' inside } #endif \ No newline at end of file diff --git a/components/spotify/cspot/protobuf/spirc.options b/components/spotify/cspot/protobuf/spirc.options index 3ed13c47..b7331f53 100644 --- a/components/spotify/cspot/protobuf/spirc.options +++ b/components/spotify/cspot/protobuf/spirc.options @@ -7,7 +7,4 @@ DeviceState.sw_version type:FT_POINTER DeviceState.name type:FT_POINTER DeviceState.capabilities max_count:17, fixed_count:false State.context_uri type:FT_POINTER -State.track type:FT_POINTER -TrackRef.gid type:FT_POINTER -TrackRef.uri type:FT_POINTER -TrackRef.context type:FT_POINTER \ No newline at end of file +TrackRef.queued type:FT_CALLBACK \ No newline at end of file diff --git a/components/spotify/cspot/protobuf/spirc.proto b/components/spotify/cspot/protobuf/spirc.proto index 4339949c..5d54bd26 100644 --- a/components/spotify/cspot/protobuf/spirc.proto +++ b/components/spotify/cspot/protobuf/spirc.proto @@ -58,6 +58,10 @@ enum CapabilityType { kVolumeSteps = 0x8; kSupportedTypes = 0x9; kCommandAcks = 0xa; + kSupportsRename = 0xb; + kHidden = 0xc; + kSupportsPlaylistV2 = 0xd; + kSupportsExternalEpisodes = 0xe; } message Capability { diff --git a/components/spotify/cspot/src/AccessKeyFetcher.cpp b/components/spotify/cspot/src/AccessKeyFetcher.cpp index d3d32237..968522c5 100644 --- a/components/spotify/cspot/src/AccessKeyFetcher.cpp +++ b/components/spotify/cspot/src/AccessKeyFetcher.cpp @@ -6,13 +6,14 @@ #include // for remove_extent_t #include // for vector -#include "BellLogger.h" // for AbstractLogger -#include "CSpotContext.h" // for Context -#include "Logger.h" // for CSPOT_LOG -#include "MercurySession.h" // for MercurySession, MercurySession::Res... -#include "Packet.h" // for cspot -#include "TimeProvider.h" // for TimeProvider -#include "Utils.h" // for string_format +#include "BellLogger.h" // for AbstractLogger +#include "CSpotContext.h" // for Context +#include "Logger.h" // for CSPOT_LOG +#include "MercurySession.h" // for MercurySession, MercurySession::Res... +#include "Packet.h" // for cspot +#include "TimeProvider.h" // for TimeProvider +#include "Utils.h" // for string_format +#include "WrappedSemaphore.h" #ifdef BELL_ONLY_CJSON #include "cJSON.h" #else @@ -22,11 +23,17 @@ using namespace cspot; -AccessKeyFetcher::AccessKeyFetcher(std::shared_ptr ctx) { - this->ctx = ctx; -} +static std::string CLIENT_ID = + "65b708073fc0480ea92a077233ca87bd"; // Spotify web client's client id -AccessKeyFetcher::~AccessKeyFetcher() {} +static std::string SCOPES = + "streaming,user-library-read,user-library-modify,user-top-read,user-read-" + "recently-played"; // Required access scopes + +AccessKeyFetcher::AccessKeyFetcher(std::shared_ptr ctx) + : ctx(ctx) { + this->updateSemaphore = std::make_shared(); +} bool AccessKeyFetcher::isExpired() { if (accessKey.empty()) { @@ -40,11 +47,24 @@ bool AccessKeyFetcher::isExpired() { return false; } -void AccessKeyFetcher::getAccessKey(AccessKeyFetcher::Callback callback) { +std::string AccessKeyFetcher::getAccessKey() { if (!isExpired()) { - return callback(accessKey); + return accessKey; } + updateAccessKey(); + + return accessKey; +} + +void AccessKeyFetcher::updateAccessKey() { + if (keyPending) { + // Already pending refresh request + return; + } + + keyPending = true; + CSPOT_LOG(info, "Access token expired, fetching new one..."); std::string url = @@ -54,17 +74,17 @@ void AccessKeyFetcher::getAccessKey(AccessKeyFetcher::Callback callback) { ctx->session->execute( MercurySession::RequestType::GET, url, - [this, timeProvider, callback](MercurySession::Response& res) { + [this, timeProvider](MercurySession::Response& res) { if (res.fail) return; - char* accessKeyJson = (char*)res.parts[0].data(); - auto accessJSON = std::string( - accessKeyJson, strrchr(accessKeyJson, '}') - accessKeyJson + 1); + auto accessJSON = + std::string((char*)res.parts[0].data(), res.parts[0].size()); #ifdef BELL_ONLY_CJSON cJSON* jsonBody = cJSON_Parse(accessJSON.c_str()); this->accessKey = cJSON_GetObjectItem(jsonBody, "accessToken")->valuestring; int expiresIn = cJSON_GetObjectItem(jsonBody, "expiresIn")->valueint; + cJSON_Delete(jsonBody); #else auto jsonBody = nlohmann::json::parse(accessJSON); this->accessKey = jsonBody["accessToken"]; @@ -74,11 +94,11 @@ void AccessKeyFetcher::getAccessKey(AccessKeyFetcher::Callback callback) { this->expiresAt = timeProvider->getSyncedTimestamp() + (expiresIn * 1000); -#ifdef BELL_ONLY_CJSON - callback(cJSON_GetObjectItem(jsonBody, "accessToken")->valuestring); - cJSON_Delete(jsonBody); -#else - callback(jsonBody["accessToken"]); -#endif + updateSemaphore->give(); }); + + updateSemaphore->twait(5000); + + // Mark as not pending for refresh + keyPending = false; } diff --git a/components/spotify/cspot/src/ApResolve.cpp b/components/spotify/cspot/src/ApResolve.cpp index 011f1c23..ba455e9d 100644 --- a/components/spotify/cspot/src/ApResolve.cpp +++ b/components/spotify/cspot/src/ApResolve.cpp @@ -1,10 +1,10 @@ #include "ApResolve.h" -#include // for initializer_list -#include // for operator!=, operator== -#include // for allocator, unique_ptr -#include // for string_view -#include // for vector +#include // for initializer_list +#include // for operator!=, operator== +#include // for allocator, unique_ptr +#include // for string_view +#include // for vector #include "HTTPClient.h" // for HTTPClient, HTTPClient::Response #ifdef BELL_ONLY_CJSON @@ -16,29 +16,27 @@ using namespace cspot; -ApResolve::ApResolve(std::string apOverride) -{ - this->apOverride = apOverride; +ApResolve::ApResolve(std::string apOverride) { + this->apOverride = apOverride; } -std::string ApResolve::fetchFirstApAddress() -{ - if (apOverride != "") - { - return apOverride; - } +std::string ApResolve::fetchFirstApAddress() { + if (apOverride != "") { + return apOverride; + } - auto request = bell::HTTPClient::get("https://apresolve.spotify.com/"); - std::string_view responseStr = request->body(); + auto request = bell::HTTPClient::get("https://apresolve.spotify.com/"); + std::string_view responseStr = request->body(); - // parse json with nlohmann + // parse json with nlohmann #ifdef BELL_ONLY_CJSON - cJSON* json = cJSON_Parse(responseStr.data()); - auto ap_string = std::string(cJSON_GetArrayItem(cJSON_GetObjectItem(json, "ap_list"), 0)->valuestring); - cJSON_Delete(json); - return ap_string; -#else - auto json = nlohmann::json::parse(responseStr); - return json["ap_list"][0]; -#endif + cJSON* json = cJSON_Parse(responseStr.data()); + auto ap_string = std::string( + cJSON_GetArrayItem(cJSON_GetObjectItem(json, "ap_list"), 0)->valuestring); + cJSON_Delete(json); + return ap_string; +#else + auto json = nlohmann::json::parse(responseStr); + return json["ap_list"][0]; +#endif } diff --git a/components/spotify/cspot/src/AuthChallenges.cpp b/components/spotify/cspot/src/AuthChallenges.cpp index b1b48509..633d9fa4 100644 --- a/components/spotify/cspot/src/AuthChallenges.cpp +++ b/components/spotify/cspot/src/AuthChallenges.cpp @@ -1,8 +1,8 @@ #include "AuthChallenges.h" -#include // for copy -#include // for CHAR_BIT -#include // for default_random_engine, independent_bits_en... +#include // for copy +#include // for CHAR_BIT +#include // for default_random_engine, independent_bits_en... #include "NanoPBHelper.h" // for pbPutString, pbEncode, pbDecode #include "pb.h" // for pb_byte_t @@ -94,9 +94,9 @@ std::vector AuthChallenges::solveApHello( // Get send and receive keys this->shanSendKey = std::vector(resultData.begin() + 0x14, - resultData.begin() + 0x34); + resultData.begin() + 0x34); this->shanRecvKey = std::vector(resultData.begin() + 0x34, - resultData.begin() + 0x54); + resultData.begin() + 0x54); return pbEncode(ClientResponsePlaintext_fields, &clientResPlaintext); } @@ -125,5 +125,6 @@ std::vector AuthChallenges::prepareClientHello() { // Generate the random nonce auto nonce = crypto->generateVectorWithRandomData(16); std::copy(nonce.begin(), nonce.end(), clientHello.client_nonce); + return pbEncode(ClientHello_fields, &clientHello); -} \ No newline at end of file +} diff --git a/components/spotify/cspot/src/LoginBlob.cpp b/components/spotify/cspot/src/LoginBlob.cpp index 4a1d8ca7..fb2cf919 100644 --- a/components/spotify/cspot/src/LoginBlob.cpp +++ b/components/spotify/cspot/src/LoginBlob.cpp @@ -1,18 +1,18 @@ #include "LoginBlob.h" -#include // for sprintf -#include // for initializer_list +#include // for sprintf +#include // for initializer_list #include "BellLogger.h" // for AbstractLogger #include "ConstantParameters.h" // for brandName, cspot, protoc... #include "Logger.h" // for CSPOT_LOG #include "protobuf/authentication.pb.h" // for AuthenticationType_AUTHE... #ifdef BELL_ONLY_CJSON -#include "cJSON.h" +#include "cJSON.h " #else #include "nlohmann/detail/json_pointer.hpp" // for json_pointer<>::string_t -#include "nlohmann/json.hpp" // for basic_json<>::object_t -#include "nlohmann/json_fwd.hpp" // for json +#include "nlohmann/json.hpp" // for basic_json<>::object_t, basic_json +#include "nlohmann/json_fwd.hpp" // for json #endif using namespace cspot; @@ -24,7 +24,7 @@ LoginBlob::LoginBlob(std::string name) { this->deviceId = std::string("142137fd329622137a149016") + std::string(hash); this->crypto = std::make_unique(); this->name = name; - + this->crypto->dhInit(); } @@ -142,7 +142,8 @@ void LoginBlob::loadJson(const std::string& json) { cJSON* root = cJSON_Parse(json.c_str()); this->authType = cJSON_GetObjectItem(root, "authType")->valueint; this->username = cJSON_GetObjectItem(root, "username")->valuestring; - std::string authDataObject = cJSON_GetObjectItem(root, "authData")->valuestring; + std::string authDataObject = + cJSON_GetObjectItem(root, "authData")->valuestring; cJSON_Delete(root); #else auto root = nlohmann::json::parse(json); @@ -151,23 +152,24 @@ void LoginBlob::loadJson(const std::string& json) { std::string authDataObject = root["authData"]; this->authData = crypto->base64Decode(authDataObject); -#endif +#endif } std::string LoginBlob::toJson() { #ifdef BELL_ONLY_CJSON - cJSON* json_obj = cJSON_CreateObject(); - cJSON_AddStringToObject(json_obj, "authData", crypto->base64Encode(authData).c_str()); + cJSON* json_obj = cJSON_CreateObject(); + cJSON_AddStringToObject(json_obj, "authData", + crypto->base64Encode(authData).c_str()); cJSON_AddNumberToObject(json_obj, "authType", this->authType); cJSON_AddStringToObject(json_obj, "username", this->username.c_str()); - - char *str = cJSON_PrintUnformatted(json_obj); - cJSON_Delete(json_obj); + + char* str = cJSON_PrintUnformatted(json_obj); + cJSON_Delete(json_obj); std::string json_objStr(str); free(str); - + return json_objStr; -#else +#else nlohmann::json obj; obj["authData"] = crypto->base64Encode(authData); obj["authType"] = this->authType; @@ -200,7 +202,7 @@ std::string LoginBlob::buildZeroconfInfo() { auto encodedKey = crypto->base64Encode(crypto->publicKey); #ifdef BELL_ONLY_CJSON - cJSON* json_obj = cJSON_CreateObject(); + cJSON* json_obj = cJSON_CreateObject(); cJSON_AddNumberToObject(json_obj, "status", 101); cJSON_AddStringToObject(json_obj, "statusString", "OK"); cJSON_AddStringToObject(json_obj, "version", cspot::protocolVersion); @@ -214,20 +216,21 @@ std::string LoginBlob::buildZeroconfInfo() { cJSON_AddStringToObject(json_obj, "tokenType", "default"); cJSON_AddStringToObject(json_obj, "groupStatus", "NONE"); cJSON_AddStringToObject(json_obj, "resolverVersion", "0"); - cJSON_AddStringToObject(json_obj, "scope", "streaming,client-authorization-universal"); - cJSON_AddStringToObject(json_obj, "activeUser", ""); - cJSON_AddStringToObject(json_obj, "deviceID", deviceId.c_str()); - cJSON_AddStringToObject(json_obj, "remoteName", name.c_str()); - cJSON_AddStringToObject(json_obj, "publicKey", encodedKey.c_str()); - cJSON_AddStringToObject(json_obj, "deviceType", "deviceType"); - - char *str = cJSON_PrintUnformatted(json_obj); - cJSON_Delete(json_obj); + cJSON_AddStringToObject(json_obj, "scope", + "streaming,client-authorization-universal"); + cJSON_AddStringToObject(json_obj, "activeUser", ""); + cJSON_AddStringToObject(json_obj, "deviceID", deviceId.c_str()); + cJSON_AddStringToObject(json_obj, "remoteName", name.c_str()); + cJSON_AddStringToObject(json_obj, "publicKey", encodedKey.c_str()); + cJSON_AddStringToObject(json_obj, "deviceType", "deviceType"); + + char* str = cJSON_PrintUnformatted(json_obj); + cJSON_Delete(json_obj); std::string json_objStr(str); free(str); - + return json_objStr; -#else +#else nlohmann::json obj; obj["status"] = 101; obj["statusString"] = "OK"; diff --git a/components/spotify/cspot/src/MercurySession.cpp b/components/spotify/cspot/src/MercurySession.cpp index 6778cb76..605ac924 100644 --- a/components/spotify/cspot/src/MercurySession.cpp +++ b/components/spotify/cspot/src/MercurySession.cpp @@ -6,11 +6,9 @@ #include // for runtime_error #include // for remove_extent_t, __underlying_type_impl<>:... #include // for pair - #ifndef _WIN32 -#include +#include // for htons, ntohs, htonl, ntohl #endif - #include "BellLogger.h" // for AbstractLogger #include "BellTask.h" // for Task #include "BellUtils.h" // for BELL_SLEEP_MS @@ -110,6 +108,22 @@ bool MercurySession::triggerTimeout() { return false; } +void MercurySession::unregister(uint64_t sequenceId) { + auto callback = this->callbacks.find(sequenceId); + + if (callback != this->callbacks.end()) { + this->callbacks.erase(callback); + } +} + +void MercurySession::unregisterAudioKey(uint32_t sequenceId) { + auto callback = this->audioKeyCallbacks.find(sequenceId); + + if (callback != this->audioKeyCallbacks.end()) { + this->audioKeyCallbacks.erase(callback); + } +} + void MercurySession::disconnect() { CSPOT_LOG(info, "Disconnecting mercury session"); this->isRunning = false; @@ -145,12 +159,13 @@ void MercurySession::handlePacket() { // First four bytes mark the sequence id auto seqId = ntohl(extract(packet.data, 0)); - if (seqId == (this->audioKeySequence - 1) && - audioKeyCallback != nullptr) { + + if (this->audioKeyCallbacks.count(seqId) > 0) { auto success = static_cast(packet.command) == RequestType::AUDIO_KEY_SUCCESS_RESPONSE; - audioKeyCallback(success, packet.data); + this->audioKeyCallbacks[seqId](success, packet.data); } + break; } case RequestType::SEND: @@ -290,23 +305,30 @@ uint64_t MercurySession::executeSubscription(RequestType method, // Bump sequence id this->sequenceId += 1; - this->shanConn->sendPacket( - static_cast::type>(method), - sequenceIdBytes); + try { + this->shanConn->sendPacket( + static_cast::type>(method), + sequenceIdBytes); + } catch (...) { + // @TODO: handle disconnect + } return this->sequenceId - 1; } -void MercurySession::requestAudioKey(const std::vector& trackId, - const std::vector& fileId, - AudioKeyCallback audioCallback) { +uint32_t MercurySession::requestAudioKey(const std::vector& trackId, + const std::vector& fileId, + AudioKeyCallback audioCallback) { auto buffer = fileId; - this->audioKeyCallback = audioCallback; + + // Store callback + this->audioKeyCallbacks.insert({this->audioKeySequence, audioCallback}); // Structure: [FILEID] [TRACKID] [4 BYTES SEQUENCE ID] [0x00, 0x00] buffer.insert(buffer.end(), trackId.begin(), trackId.end()); - auto audioKeySequence = pack(htonl(this->audioKeySequence)); - buffer.insert(buffer.end(), audioKeySequence.begin(), audioKeySequence.end()); + auto audioKeySequenceBuffer = pack(htonl(this->audioKeySequence)); + buffer.insert(buffer.end(), audioKeySequenceBuffer.begin(), + audioKeySequenceBuffer.end()); auto suffix = std::vector({0x00, 0x00}); buffer.insert(buffer.end(), suffix.begin(), suffix.end()); @@ -315,6 +337,11 @@ void MercurySession::requestAudioKey(const std::vector& trackId, // Used for broken connection detection // this->lastRequestTimestamp = timeProvider->getSyncedTimestamp(); - this->shanConn->sendPacket( - static_cast(RequestType::AUDIO_KEY_REQUEST_COMMAND), buffer); + try { + this->shanConn->sendPacket( + static_cast(RequestType::AUDIO_KEY_REQUEST_COMMAND), buffer); + } catch (...) { + // @TODO: Handle disconnect + } + return audioKeySequence - 1; } diff --git a/components/spotify/cspot/src/PlainConnection.cpp b/components/spotify/cspot/src/PlainConnection.cpp index 4ccb4eff..7337f691 100644 --- a/components/spotify/cspot/src/PlainConnection.cpp +++ b/components/spotify/cspot/src/PlainConnection.cpp @@ -1,19 +1,17 @@ #include "PlainConnection.h" #ifndef _WIN32 -#include // for addrinfo, freeaddrinfo, getaddrinfo -#include // for IPPROTO_IP, IPPROTO_TCP -#include // for EAGAIN, EINTR, ETIMEDOUT, errno -#include // for setsockopt, connect, recv, send, shutdown -#include // for timeval -#endif -#include // for memset -#include // for runtime_error -#ifdef _WIN32 -#include -#else +#include // for addrinfo, freeaddrinfo, getaddrinfo +#include // for IPPROTO_IP, IPPROTO_TCP +#include // for EAGAIN, EINTR, ETIMEDOUT, errno +#include // for setsockopt, connect, recv, send, shutdown +#include // for timeval +#include // for memset +#include // for runtime_error #include // for TCP_NODELAY -#include +#include +#else +#include #endif #include "BellLogger.h" // for AbstractLogger #include "Logger.h" // for CSPOT_LOG @@ -67,8 +65,8 @@ void PlainConnection::connect(const std::string& apAddress) { if (this->apSock < 0) continue; - if (::connect(this->apSock, (struct sockaddr*)ai->ai_addr, ai->ai_addrlen) != - -1) { + if (::connect(this->apSock, (struct sockaddr*)ai->ai_addr, + ai->ai_addrlen) != -1) { #ifdef _WIN32 uint32_t tv = 3000; #else @@ -144,10 +142,10 @@ void PlainConnection::readBlock(const uint8_t* dst, size_t size) { switch (getErrno()) { case EAGAIN: case ETIMEDOUT: - // if (timeoutHandler()) { - // CSPOT_LOG(error, "Connection lost, will need to reconnect..."); - // throw std::runtime_error("Reconnection required"); - // } + if (timeoutHandler()) { + CSPOT_LOG(error, "Connection lost, will need to reconnect..."); + throw std::runtime_error("Reconnection required"); + } goto READ; case EINTR: break; @@ -174,9 +172,9 @@ size_t PlainConnection::writeBlock(const std::vector& data) { switch (getErrno()) { case EAGAIN: case ETIMEDOUT: - // if (timeoutHandler()) { - // throw std::runtime_error("Reconnection required"); - // } + if (timeoutHandler()) { + throw std::runtime_error("Reconnection required"); + } goto WRITE; case EINTR: break; diff --git a/components/spotify/cspot/src/PlaybackState.cpp b/components/spotify/cspot/src/PlaybackState.cpp index fd8d7a41..72697c2f 100644 --- a/components/spotify/cspot/src/PlaybackState.cpp +++ b/components/spotify/cspot/src/PlaybackState.cpp @@ -1,11 +1,12 @@ #include "PlaybackState.h" -#include // for strdup, memcpy, strcpy, strlen -#include // for uint8_t -#include // for free, NULL, realloc, rand -#include // for shared_ptr -#include // for remove_extent_t -#include // for swap +#include // for strdup, memcpy, strcpy, strlen +#include // for uint8_t +#include // for free, NULL, realloc, rand +#include +#include // for shared_ptr +#include // for remove_extent_t +#include // for swap #include "BellLogger.h" // for AbstractLogger #include "CSpotContext.h" // for Context::ConfigState, Context (ptr o... @@ -15,6 +16,7 @@ #include "Packet.h" // for cspot #include "pb.h" // for pb_bytes_array_t, PB_BYTES_ARRAY_T_A... #include "pb_decode.h" // for pb_release +#include "protobuf/spirc.pb.h" using namespace cspot; @@ -23,6 +25,13 @@ PlaybackState::PlaybackState(std::shared_ptr ctx) { innerFrame = {}; remoteFrame = {}; + // Prepare callbacks for decoding of remote frame track data + remoteFrame.state.track.funcs.decode = &TrackReference::pbDecodeTrackList; + remoteFrame.state.track.arg = &remoteTracks; + + innerFrame.ident = strdup(ctx->config.deviceId.c_str()); + innerFrame.protocol_version = strdup(protocolVersion); + // Prepare default state innerFrame.state.has_position_ms = true; innerFrame.state.position_ms = 0; @@ -52,13 +61,12 @@ PlaybackState::PlaybackState(std::shared_ptr ctx) { innerFrame.device_state.name = strdup(ctx->config.deviceName.c_str()); - innerFrame.state.track_count = 0; - // Prepare player's capabilities addCapability(CapabilityType_kCanBePlayer, 1); addCapability(CapabilityType_kDeviceType, 4); addCapability(CapabilityType_kGaiaEqConnectId, 1); addCapability(CapabilityType_kSupportsLogout, 0); + addCapability(CapabilityType_kSupportsPlaylistV2, 1); addCapability(CapabilityType_kIsObservable, 1); addCapability(CapabilityType_kVolumeSteps, 64); addCapability(CapabilityType_kSupportedContexts, -1, @@ -66,8 +74,8 @@ PlaybackState::PlaybackState(std::shared_ptr ctx) { "inbox", "toplist", "starred", "publishedstarred", "track"})); addCapability(CapabilityType_kSupportedTypes, -1, - std::vector({"audio/local", "audio/track", - "audio/episode", "local", "track"})); + std::vector( + {"audio/track", "audio/episode", "audio/episode+track"})); innerFrame.device_state.capabilities_count = 8; } @@ -102,34 +110,20 @@ void PlaybackState::setPlaybackState(const PlaybackState::State state) { } } +void PlaybackState::syncWithRemote() { + innerFrame.state.context_uri = (char*)realloc( + innerFrame.state.context_uri, strlen(remoteFrame.state.context_uri) + 1); + + strcpy(innerFrame.state.context_uri, remoteFrame.state.context_uri); + + innerFrame.state.has_playing_track_index = true; + innerFrame.state.playing_track_index = remoteFrame.state.playing_track_index; +} + bool PlaybackState::isActive() { return innerFrame.device_state.is_active; } -bool PlaybackState::nextTrack() { - innerFrame.state.playing_track_index++; - - if (innerFrame.state.playing_track_index >= innerFrame.state.track_count) { - - innerFrame.state.playing_track_index = 0; - - if (!innerFrame.state.repeat) { - setPlaybackState(State::Paused); - return false; - } - } - - return true; -} - -void PlaybackState::prevTrack() { - if (innerFrame.state.playing_track_index > 0) { - innerFrame.state.playing_track_index--; - } else if (innerFrame.state.repeat) { - innerFrame.state.playing_track_index = innerFrame.state.track_count - 1; - } -} - void PlaybackState::setActive(bool isActive) { innerFrame.device_state.is_active = isActive; if (isActive) { @@ -145,131 +139,25 @@ void PlaybackState::updatePositionMs(uint32_t position) { ctx->timeProvider->getSyncedTimestamp(); } -#define FREE(ptr) \ - { \ - free(ptr); \ - ptr = NULL; \ - } -#define STRDUP(dst, src) \ - if (src != NULL) { \ - dst = strdup(src); \ - } else { \ - FREE(dst); \ - } // strdup null pointer safe - -void PlaybackState::updateTracks() { - CSPOT_LOG(info, "---- Track count %d", remoteFrame.state.track_count); - CSPOT_LOG(info, "---- Inner track count %d", innerFrame.state.track_count); - CSPOT_LOG(info, "--- Context URI %s", remoteFrame.state.context_uri); - - // free unused tracks - if (innerFrame.state.track_count > remoteFrame.state.track_count) { - for (uint16_t i = remoteFrame.state.track_count; - i < innerFrame.state.track_count; ++i) { - FREE(innerFrame.state.track[i].gid); - FREE(innerFrame.state.track[i].uri); - FREE(innerFrame.state.track[i].context); - } - } - - // reallocate memory for new tracks - innerFrame.state.track = (TrackRef*)realloc( - innerFrame.state.track, sizeof(TrackRef) * remoteFrame.state.track_count); - - for (uint16_t i = 0; i < remoteFrame.state.track_count; ++i) { - if (i >= innerFrame.state.track_count) { - innerFrame.state.track[i].gid = NULL; - innerFrame.state.track[i].uri = NULL; - innerFrame.state.track[i].context = NULL; - } - - if (remoteFrame.state.track[i].gid != NULL) { - uint16_t gid_size = remoteFrame.state.track[i].gid->size; - innerFrame.state.track[i].gid = (pb_bytes_array_t*)realloc( - innerFrame.state.track[i].gid, PB_BYTES_ARRAY_T_ALLOCSIZE(gid_size)); - - memcpy(innerFrame.state.track[i].gid->bytes, - remoteFrame.state.track[i].gid->bytes, gid_size); - innerFrame.state.track[i].gid->size = gid_size; - } - innerFrame.state.track[i].has_queued = - remoteFrame.state.track[i].has_queued; - innerFrame.state.track[i].queued = remoteFrame.state.track[i].queued; - - STRDUP(innerFrame.state.track[i].uri, remoteFrame.state.track[i].uri); - STRDUP(innerFrame.state.track[i].context, - remoteFrame.state.track[i].context); - } - - innerFrame.state.context_uri = (char*)realloc( - innerFrame.state.context_uri, strlen(remoteFrame.state.context_uri) + 1); - strcpy(innerFrame.state.context_uri, remoteFrame.state.context_uri); - - innerFrame.state.track_count = remoteFrame.state.track_count; - innerFrame.state.has_playing_track_index = true; - innerFrame.state.playing_track_index = remoteFrame.state.playing_track_index; - - if (remoteFrame.state.repeat) { - setRepeat(true); - } - - if (remoteFrame.state.shuffle) { - setShuffle(true); - } -} - void PlaybackState::setVolume(uint32_t volume) { innerFrame.device_state.volume = volume; ctx->config.volume = volume; } -void PlaybackState::setShuffle(bool shuffle) { - innerFrame.state.shuffle = shuffle; - if (shuffle) { - // Put current song at the begining - std::swap(innerFrame.state.track[0], - innerFrame.state.track[innerFrame.state.playing_track_index]); +bool PlaybackState::decodeRemoteFrame(std::vector& data) { + pb_release(Frame_fields, &remoteFrame); - // Shuffle current tracks - for (int x = 1; x < innerFrame.state.track_count - 1; x++) { - auto j = x + (std::rand() % (innerFrame.state.track_count - x)); - std::swap(innerFrame.state.track[j], innerFrame.state.track[x]); - } - innerFrame.state.playing_track_index = 0; - } -} + remoteTracks.clear(); -void PlaybackState::setRepeat(bool repeat) { - innerFrame.state.repeat = repeat; -} + pbDecode(remoteFrame, Frame_fields, data); -TrackRef* PlaybackState::getCurrentTrackRef() { - if (innerFrame.state.playing_track_index >= innerFrame.state.track_count) { - return nullptr; - } - return &innerFrame.state.track[innerFrame.state.playing_track_index]; -} - -TrackRef* PlaybackState::getNextTrackRef() { - if ((innerFrame.state.playing_track_index + 1) >= innerFrame.state.track_count) { - if (innerFrame.state.repeat) { - return &innerFrame.state.track[0]; - } - return nullptr; - } - - return &innerFrame.state.track[innerFrame.state.playing_track_index + 1]; + return true; } std::vector PlaybackState::encodeCurrentFrame(MessageType typ) { - free(innerFrame.ident); - free(innerFrame.protocol_version); - // Prepare current frame info innerFrame.version = 1; - innerFrame.ident = strdup(ctx->config.deviceId.c_str()); innerFrame.seq_nr = this->seqNum; - innerFrame.protocol_version = strdup(protocolVersion); innerFrame.typ = typ; innerFrame.state_update_id = ctx->timeProvider->getSyncedTimestamp(); innerFrame.has_version = true; @@ -281,6 +169,7 @@ std::vector PlaybackState::encodeCurrentFrame(MessageType typ) { innerFrame.has_state_update_id = true; this->seqNum += 1; + return pbEncode(Frame_fields, &innerFrame); } @@ -308,5 +197,6 @@ void PlaybackState::addCapability(CapabilityType typ, int intValue, this->innerFrame.device_state.capabilities[capabilityIndex] .stringValue_count = stringValue.size(); + this->capabilityIndex += 1; } diff --git a/components/spotify/cspot/src/Shannon.cpp b/components/spotify/cspot/src/Shannon.cpp index 37615eee..f6419611 100644 --- a/components/spotify/cspot/src/Shannon.cpp +++ b/components/spotify/cspot/src/Shannon.cpp @@ -5,437 +5,389 @@ using std::size_t; -static inline uint32_t rotl(uint32_t n, unsigned int c) -{ - const unsigned int mask = (CHAR_BIT * sizeof(n) - 1); // assumes width is a power of 2. - // assert ( (c<=mask) &&"rotate by type width or more"); - c &= mask; - return (n << c) | (n >> ((-c) & mask)); +static inline uint32_t rotl(uint32_t n, unsigned int c) { + const unsigned int mask = + (CHAR_BIT * sizeof(n) - 1); // assumes width is a power of 2. + // assert ( (c<=mask) &&"rotate by type width or more"); + c &= mask; + return (n << c) | (n >> ((-c) & mask)); } -static inline uint32_t rotr(uint32_t n, unsigned int c) -{ - const unsigned int mask = (CHAR_BIT * sizeof(n) - 1); // assumes width is a power of 2. - // assert ( (c<=mask) &&"rotate by type width or more"); - c &= mask; - return (n >> c) | (n << ((-c) & mask)); +static inline uint32_t rotr(uint32_t n, unsigned int c) { + const unsigned int mask = + (CHAR_BIT * sizeof(n) - 1); // assumes width is a power of 2. + // assert ( (c<=mask) &&"rotate by type width or more"); + c &= mask; + return (n >> c) | (n << ((-c) & mask)); } -uint32_t Shannon::sbox1(uint32_t w) -{ - w ^= rotl(w, 5) | rotl(w, 7); - w ^= rotl(w, 19) | rotl(w, 22); - return w; +uint32_t Shannon::sbox1(uint32_t w) { + w ^= rotl(w, 5) | rotl(w, 7); + w ^= rotl(w, 19) | rotl(w, 22); + return w; } -uint32_t Shannon::sbox2(uint32_t w) -{ - w ^= rotl(w, 7) | rotl(w, 22); - w ^= rotl(w, 5) | rotl(w, 19); - return w; +uint32_t Shannon::sbox2(uint32_t w) { + w ^= rotl(w, 7) | rotl(w, 22); + w ^= rotl(w, 5) | rotl(w, 19); + return w; } -void Shannon::cycle() -{ - uint32_t t; - int i; +void Shannon::cycle() { + uint32_t t; + int i; - /* nonlinear feedback function */ - t = this->R[12] ^ this->R[13] ^ this->konst; - t = Shannon::sbox1(t) ^ rotl(this->R[0], 1); - /* shift register */ - for (i = 1; i < N; ++i) - this->R[i - 1] = this->R[i]; - this->R[N - 1] = t; - t = Shannon::sbox2(this->R[2] ^ this->R[15]); - this->R[0] ^= t; - this->sbuf = t ^ this->R[8] ^ this->R[12]; + /* nonlinear feedback function */ + t = this->R[12] ^ this->R[13] ^ this->konst; + t = Shannon::sbox1(t) ^ rotl(this->R[0], 1); + /* shift register */ + for (i = 1; i < N; ++i) + this->R[i - 1] = this->R[i]; + this->R[N - 1] = t; + t = Shannon::sbox2(this->R[2] ^ this->R[15]); + this->R[0] ^= t; + this->sbuf = t ^ this->R[8] ^ this->R[12]; } -void Shannon::crcfunc(uint32_t i) -{ - uint32_t t; - int j; +void Shannon::crcfunc(uint32_t i) { + uint32_t t; + int j; - /* Accumulate CRC of input */ - t = this->CRC[0] ^ this->CRC[2] ^ this->CRC[15] ^ i; - for (j = 1; j < N; ++j) - this->CRC[j - 1] = this->CRC[j]; - this->CRC[N - 1] = t; + /* Accumulate CRC of input */ + t = this->CRC[0] ^ this->CRC[2] ^ this->CRC[15] ^ i; + for (j = 1; j < N; ++j) + this->CRC[j - 1] = this->CRC[j]; + this->CRC[N - 1] = t; } -void Shannon::macfunc(uint32_t i) -{ - this->crcfunc(i); - this->R[KEYP] ^= i; +void Shannon::macfunc(uint32_t i) { + this->crcfunc(i); + this->R[KEYP] ^= i; } -void Shannon::initState() -{ - int i; +void Shannon::initState() { + int i; - /* Register initialised to Fibonacci numbers; Counter zeroed. */ - this->R[0] = 1; - this->R[1] = 1; - for (i = 2; i < N; ++i) - this->R[i] = this->R[i - 1] + this->R[i - 2]; - this->konst = Shannon::INITKONST; + /* Register initialised to Fibonacci numbers; Counter zeroed. */ + this->R[0] = 1; + this->R[1] = 1; + for (i = 2; i < N; ++i) + this->R[i] = this->R[i - 1] + this->R[i - 2]; + this->konst = Shannon::INITKONST; } -void Shannon::saveState() -{ - int i; - for (i = 0; i < Shannon::N; ++i) - this->initR[i] = this->R[i]; +void Shannon::saveState() { + int i; + for (i = 0; i < Shannon::N; ++i) + this->initR[i] = this->R[i]; } -void Shannon::reloadState() -{ - int i; +void Shannon::reloadState() { + int i; - for (i = 0; i < Shannon::N; ++i) - this->R[i] = this->initR[i]; + for (i = 0; i < Shannon::N; ++i) + this->R[i] = this->initR[i]; } -void Shannon::genkonst() -{ - this->konst = this->R[0]; +void Shannon::genkonst() { + this->konst = this->R[0]; } -void Shannon::diffuse() -{ - int i; +void Shannon::diffuse() { + int i; - for (i = 0; i < Shannon::FOLD; ++i) - this->cycle(); + for (i = 0; i < Shannon::FOLD; ++i) + this->cycle(); } #define Byte(x, i) ((uint32_t)(((x) >> (8 * (i))) & 0xFF)) -#define BYTE2WORD(b) ( \ - (((uint32_t)(b)[3] & 0xFF) << 24) | \ - (((uint32_t)(b)[2] & 0xFF) << 16) | \ - (((uint32_t)(b)[1] & 0xFF) << 8) | \ - (((uint32_t)(b)[0] & 0xFF))) -#define WORD2BYTE(w, b) \ - { \ - (b)[3] = Byte(w, 3); \ - (b)[2] = Byte(w, 2); \ - (b)[1] = Byte(w, 1); \ - (b)[0] = Byte(w, 0); \ - } -#define XORWORD(w, b) \ - { \ - (b)[3] ^= Byte(w, 3); \ - (b)[2] ^= Byte(w, 2); \ - (b)[1] ^= Byte(w, 1); \ - (b)[0] ^= Byte(w, 0); \ - } +#define BYTE2WORD(b) \ + ((((uint32_t)(b)[3] & 0xFF) << 24) | (((uint32_t)(b)[2] & 0xFF) << 16) | \ + (((uint32_t)(b)[1] & 0xFF) << 8) | (((uint32_t)(b)[0] & 0xFF))) +#define WORD2BYTE(w, b) \ + { \ + (b)[3] = Byte(w, 3); \ + (b)[2] = Byte(w, 2); \ + (b)[1] = Byte(w, 1); \ + (b)[0] = Byte(w, 0); \ + } +#define XORWORD(w, b) \ + { \ + (b)[3] ^= Byte(w, 3); \ + (b)[2] ^= Byte(w, 2); \ + (b)[1] ^= Byte(w, 1); \ + (b)[0] ^= Byte(w, 0); \ + } -#define XORWORD(w, b) \ - { \ - (b)[3] ^= Byte(w, 3); \ - (b)[2] ^= Byte(w, 2); \ - (b)[1] ^= Byte(w, 1); \ - (b)[0] ^= Byte(w, 0); \ - } +#define XORWORD(w, b) \ + { \ + (b)[3] ^= Byte(w, 3); \ + (b)[2] ^= Byte(w, 2); \ + (b)[1] ^= Byte(w, 1); \ + (b)[0] ^= Byte(w, 0); \ + } /* Load key material into the register */ -#define ADDKEY(k) \ - this->R[KEYP] ^= (k); +#define ADDKEY(k) this->R[KEYP] ^= (k); -void Shannon::loadKey(const std::vector& key) -{ - int i, j; - uint32_t k; - uint8_t xtra[4]; - size_t keylen = key.size(); - /* start folding in key */ - for (i = 0; i < (keylen & ~0x3); i += 4) - { - k = BYTE2WORD(&key[i]); - ADDKEY(k); - this->cycle(); - } - - /* if there were any extra key bytes, zero pad to a word */ - if (i < keylen) - { - for (j = 0 /* i unchanged */; i < keylen; ++i) - xtra[j++] = key[i]; - for (/* j unchanged */; j < 4; ++j) - xtra[j] = 0; - k = BYTE2WORD(xtra); - ADDKEY(k); - this->cycle(); - } - - /* also fold in the length of the key */ - ADDKEY(keylen); +void Shannon::loadKey(const std::vector& key) { + int i, j; + uint32_t k; + uint8_t xtra[4]; + size_t keylen = key.size(); + /* start folding in key */ + for (i = 0; i < (keylen & ~0x3); i += 4) { + k = BYTE2WORD(&key[i]); + ADDKEY(k); this->cycle(); + } - /* save a copy of the register */ - for (i = 0; i < N; ++i) - this->CRC[i] = this->R[i]; + /* if there were any extra key bytes, zero pad to a word */ + if (i < keylen) { + for (j = 0 /* i unchanged */; i < keylen; ++i) + xtra[j++] = key[i]; + for (/* j unchanged */; j < 4; ++j) + xtra[j] = 0; + k = BYTE2WORD(xtra); + ADDKEY(k); + this->cycle(); + } - /* now diffuse */ - this->diffuse(); + /* also fold in the length of the key */ + ADDKEY(keylen); + this->cycle(); - /* now xor the copy back -- makes key loading irreversible */ - for (i = 0; i < N; ++i) - this->R[i] ^= this->CRC[i]; + /* save a copy of the register */ + for (i = 0; i < N; ++i) + this->CRC[i] = this->R[i]; + + /* now diffuse */ + this->diffuse(); + + /* now xor the copy back -- makes key loading irreversible */ + for (i = 0; i < N; ++i) + this->R[i] ^= this->CRC[i]; } -void Shannon::key(const std::vector& key) -{ - this->initState(); - this->loadKey(key); - this->genkonst(); /* in case we proceed to stream generation */ - this->saveState(); - this->nbuf = 0; +void Shannon::key(const std::vector& key) { + this->initState(); + this->loadKey(key); + this->genkonst(); /* in case we proceed to stream generation */ + this->saveState(); + this->nbuf = 0; } -void Shannon::nonce(const std::vector& nonce) -{ - this->reloadState(); - this->konst = Shannon::INITKONST; - this->loadKey(nonce); - this->genkonst(); - this->nbuf = 0; +void Shannon::nonce(const std::vector& nonce) { + this->reloadState(); + this->konst = Shannon::INITKONST; + this->loadKey(nonce); + this->genkonst(); + this->nbuf = 0; } -void Shannon::stream(std::vector& bufVec) -{ - uint8_t* endbuf; - size_t nbytes = bufVec.size(); - uint8_t* buf = bufVec.data(); - /* handle any previously buffered bytes */ - while (this->nbuf != 0 && nbytes != 0) - { - *buf++ ^= this->sbuf & 0xFF; - this->sbuf >>= 8; - this->nbuf -= 8; - --nbytes; - } +void Shannon::stream(std::vector& bufVec) { + uint8_t* endbuf; + size_t nbytes = bufVec.size(); + uint8_t* buf = bufVec.data(); + /* handle any previously buffered bytes */ + while (this->nbuf != 0 && nbytes != 0) { + *buf++ ^= this->sbuf & 0xFF; + this->sbuf >>= 8; + this->nbuf -= 8; + --nbytes; + } - /* handle whole words */ - endbuf = &buf[nbytes & ~((uint32_t)0x03)]; - while (buf < endbuf) - { - this->cycle(); - XORWORD(this->sbuf, buf); - buf += 4; - } + /* handle whole words */ + endbuf = &buf[nbytes & ~((uint32_t)0x03)]; + while (buf < endbuf) { + this->cycle(); + XORWORD(this->sbuf, buf); + buf += 4; + } - /* handle any trailing bytes */ - nbytes &= 0x03; - if (nbytes != 0) - { - this->cycle(); - this->nbuf = 32; - while (this->nbuf != 0 && nbytes != 0) - { - *buf++ ^= this->sbuf & 0xFF; - this->sbuf >>= 8; - this->nbuf -= 8; - --nbytes; - } + /* handle any trailing bytes */ + nbytes &= 0x03; + if (nbytes != 0) { + this->cycle(); + this->nbuf = 32; + while (this->nbuf != 0 && nbytes != 0) { + *buf++ ^= this->sbuf & 0xFF; + this->sbuf >>= 8; + this->nbuf -= 8; + --nbytes; } + } } -void Shannon::maconly(std::vector& bufVec) -{ - size_t nbytes = bufVec.size(); - uint8_t* buf = bufVec.data(); +void Shannon::maconly(std::vector& bufVec) { + size_t nbytes = bufVec.size(); + uint8_t* buf = bufVec.data(); - uint8_t* endbuf; + uint8_t* endbuf; - /* handle any previously buffered bytes */ - if (this->nbuf != 0) - { - while (this->nbuf != 0 && nbytes != 0) - { - this->mbuf ^= (*buf++) << (32 - this->nbuf); - this->nbuf -= 8; - --nbytes; - } - if (this->nbuf != 0) /* not a whole word yet */ - return; - /* LFSR already cycled */ - this->macfunc(this->mbuf); + /* handle any previously buffered bytes */ + if (this->nbuf != 0) { + while (this->nbuf != 0 && nbytes != 0) { + this->mbuf ^= (*buf++) << (32 - this->nbuf); + this->nbuf -= 8; + --nbytes; } + if (this->nbuf != 0) /* not a whole word yet */ + return; + /* LFSR already cycled */ + this->macfunc(this->mbuf); + } - /* handle whole words */ - endbuf = &buf[nbytes & ~((uint32_t)0x03)]; - while (buf < endbuf) - { - this->cycle(); - this->macfunc(BYTE2WORD(buf)); - buf += 4; - } + /* handle whole words */ + endbuf = &buf[nbytes & ~((uint32_t)0x03)]; + while (buf < endbuf) { + this->cycle(); + this->macfunc(BYTE2WORD(buf)); + buf += 4; + } - /* handle any trailing bytes */ - nbytes &= 0x03; - if (nbytes != 0) - { - this->cycle(); - this->mbuf = 0; - this->nbuf = 32; - while (this->nbuf != 0 && nbytes != 0) - { - this->mbuf ^= (*buf++) << (32 - this->nbuf); - this->nbuf -= 8; - --nbytes; - } + /* handle any trailing bytes */ + nbytes &= 0x03; + if (nbytes != 0) { + this->cycle(); + this->mbuf = 0; + this->nbuf = 32; + while (this->nbuf != 0 && nbytes != 0) { + this->mbuf ^= (*buf++) << (32 - this->nbuf); + this->nbuf -= 8; + --nbytes; } + } } -void Shannon::encrypt(std::vector& bufVec) -{ - size_t nbytes = bufVec.size(); - uint8_t* buf = bufVec.data(); - uint8_t* endbuf; - uint32_t t = 0; +void Shannon::encrypt(std::vector& bufVec) { + size_t nbytes = bufVec.size(); + uint8_t* buf = bufVec.data(); + uint8_t* endbuf; + uint32_t t = 0; - /* handle any previously buffered bytes */ - if (this->nbuf != 0) - { - while (this->nbuf != 0 && nbytes != 0) - { - this->mbuf ^= *buf << (32 - this->nbuf); - *buf ^= (this->sbuf >> (32 - this->nbuf)) & 0xFF; - ++buf; - this->nbuf -= 8; - --nbytes; - } - if (this->nbuf != 0) /* not a whole word yet */ - return; - /* LFSR already cycled */ - this->macfunc(this->mbuf); + /* handle any previously buffered bytes */ + if (this->nbuf != 0) { + while (this->nbuf != 0 && nbytes != 0) { + this->mbuf ^= *buf << (32 - this->nbuf); + *buf ^= (this->sbuf >> (32 - this->nbuf)) & 0xFF; + ++buf; + this->nbuf -= 8; + --nbytes; } + if (this->nbuf != 0) /* not a whole word yet */ + return; + /* LFSR already cycled */ + this->macfunc(this->mbuf); + } - /* handle whole words */ - endbuf = &buf[nbytes & ~((uint32_t)0x03)]; - while (buf < endbuf) - { - this->cycle(); - t = BYTE2WORD(buf); - this->macfunc(t); - t ^= this->sbuf; - WORD2BYTE(t, buf); - buf += 4; - } + /* handle whole words */ + endbuf = &buf[nbytes & ~((uint32_t)0x03)]; + while (buf < endbuf) { + this->cycle(); + t = BYTE2WORD(buf); + this->macfunc(t); + t ^= this->sbuf; + WORD2BYTE(t, buf); + buf += 4; + } - /* handle any trailing bytes */ - nbytes &= 0x03; - if (nbytes != 0) - { - this->cycle(); - this->mbuf = 0; - this->nbuf = 32; - while (this->nbuf != 0 && nbytes != 0) - { - this->mbuf ^= *buf << (32 - this->nbuf); - *buf ^= (this->sbuf >> (32 - this->nbuf)) & 0xFF; - ++buf; - this->nbuf -= 8; - --nbytes; - } + /* handle any trailing bytes */ + nbytes &= 0x03; + if (nbytes != 0) { + this->cycle(); + this->mbuf = 0; + this->nbuf = 32; + while (this->nbuf != 0 && nbytes != 0) { + this->mbuf ^= *buf << (32 - this->nbuf); + *buf ^= (this->sbuf >> (32 - this->nbuf)) & 0xFF; + ++buf; + this->nbuf -= 8; + --nbytes; } + } } +void Shannon::decrypt(std::vector& bufVec) { + size_t nbytes = bufVec.size(); + uint8_t* buf = bufVec.data(); + uint8_t* endbuf; + uint32_t t = 0; -void Shannon::decrypt(std::vector& bufVec) -{ - size_t nbytes = bufVec.size(); - uint8_t* buf = bufVec.data(); - uint8_t* endbuf; - uint32_t t = 0; - - /* handle any previously buffered bytes */ - if (this->nbuf != 0) - { - while (this->nbuf != 0 && nbytes != 0) - { - *buf ^= (this->sbuf >> (32 - this->nbuf)) & 0xFF; - this->mbuf ^= *buf << (32 - this->nbuf); - ++buf; - this->nbuf -= 8; - --nbytes; - } - if (this->nbuf != 0) /* not a whole word yet */ - return; - /* LFSR already cycled */ - this->macfunc(this->mbuf); + /* handle any previously buffered bytes */ + if (this->nbuf != 0) { + while (this->nbuf != 0 && nbytes != 0) { + *buf ^= (this->sbuf >> (32 - this->nbuf)) & 0xFF; + this->mbuf ^= *buf << (32 - this->nbuf); + ++buf; + this->nbuf -= 8; + --nbytes; } + if (this->nbuf != 0) /* not a whole word yet */ + return; + /* LFSR already cycled */ + this->macfunc(this->mbuf); + } - /* handle whole words */ - endbuf = &buf[nbytes & ~((uint32_t)0x03)]; - while (buf < endbuf) - { - this->cycle(); - t = BYTE2WORD(buf) ^ this->sbuf; - this->macfunc(t); - WORD2BYTE(t, buf); - buf += 4; - } + /* handle whole words */ + endbuf = &buf[nbytes & ~((uint32_t)0x03)]; + while (buf < endbuf) { + this->cycle(); + t = BYTE2WORD(buf) ^ this->sbuf; + this->macfunc(t); + WORD2BYTE(t, buf); + buf += 4; + } - /* handle any trailing bytes */ - nbytes &= 0x03; - if (nbytes != 0) - { - this->cycle(); - this->mbuf = 0; - this->nbuf = 32; - while (this->nbuf != 0 && nbytes != 0) - { - *buf ^= (this->sbuf >> (32 - this->nbuf)) & 0xFF; - this->mbuf ^= *buf << (32 - this->nbuf); - ++buf; - this->nbuf -= 8; - --nbytes; - } + /* handle any trailing bytes */ + nbytes &= 0x03; + if (nbytes != 0) { + this->cycle(); + this->mbuf = 0; + this->nbuf = 32; + while (this->nbuf != 0 && nbytes != 0) { + *buf ^= (this->sbuf >> (32 - this->nbuf)) & 0xFF; + this->mbuf ^= *buf << (32 - this->nbuf); + ++buf; + this->nbuf -= 8; + --nbytes; } + } } -void Shannon::finish(std::vector& bufVec) -{ - size_t nbytes = bufVec.size(); - uint8_t* buf = bufVec.data(); - int i; +void Shannon::finish(std::vector& bufVec) { + size_t nbytes = bufVec.size(); + uint8_t* buf = bufVec.data(); + int i; - /* handle any previously buffered bytes */ - if (this->nbuf != 0) - { - /* LFSR already cycled */ - this->macfunc(this->mbuf); - } + /* handle any previously buffered bytes */ + if (this->nbuf != 0) { + /* LFSR already cycled */ + this->macfunc(this->mbuf); + } - /* perturb the MAC to mark end of input. + /* perturb the MAC to mark end of input. * Note that only the stream register is updated, not the CRC. This is an * action that can't be duplicated by passing in plaintext, hence * defeating any kind of extension attack. */ + this->cycle(); + ADDKEY(INITKONST ^ (this->nbuf << 3)); + this->nbuf = 0; + + /* now add the CRC to the stream register and diffuse it */ + for (i = 0; i < N; ++i) + this->R[i] ^= this->CRC[i]; + this->diffuse(); + + /* produce output from the stream buffer */ + while (nbytes > 0) { this->cycle(); - ADDKEY(INITKONST ^ (this->nbuf << 3)); - this->nbuf = 0; - - /* now add the CRC to the stream register and diffuse it */ - for (i = 0; i < N; ++i) - this->R[i] ^= this->CRC[i]; - this->diffuse(); - - /* produce output from the stream buffer */ - while (nbytes > 0) - { - this->cycle(); - if (nbytes >= 4) - { - WORD2BYTE(this->sbuf, buf); - nbytes -= 4; - buf += 4; - } - else - { - for (i = 0; i < nbytes; ++i) - buf[i] = Byte(this->sbuf, i); - break; - } + if (nbytes >= 4) { + WORD2BYTE(this->sbuf, buf); + nbytes -= 4; + buf += 4; + } else { + for (i = 0; i < nbytes; ++i) + buf[i] = Byte(this->sbuf, i); + break; } + } } diff --git a/components/spotify/cspot/src/ShannonConnection.cpp b/components/spotify/cspot/src/ShannonConnection.cpp index 173ebfbb..ebfe0917 100644 --- a/components/spotify/cspot/src/ShannonConnection.cpp +++ b/components/spotify/cspot/src/ShannonConnection.cpp @@ -2,16 +2,15 @@ #include // for remove_extent_t -#ifndef _WIN32 -#include -#endif - #include "BellLogger.h" // for AbstractLogger #include "Logger.h" // for CSPOT_LOG #include "Packet.h" // for Packet, cspot #include "PlainConnection.h" // for PlainConnection #include "Shannon.h" // for Shannon #include "Utils.h" // for pack, extract +#ifndef _WIN32 +#include +#endif using namespace cspot; diff --git a/components/spotify/cspot/src/SpircHandler.cpp b/components/spotify/cspot/src/SpircHandler.cpp index 814a990b..0346b25f 100644 --- a/components/spotify/cspot/src/SpircHandler.cpp +++ b/components/spotify/cspot/src/SpircHandler.cpp @@ -1,18 +1,19 @@ #include "SpircHandler.h" -#include // for uint8_t -#include // for shared_ptr, make_unique, unique_ptr -#include // for remove_extent_t -#include // for move +#include // for uint8_t +#include // for shared_ptr, make_unique, unique_ptr +#include // for remove_extent_t +#include // for move -#include "BellLogger.h" // for AbstractLogger -#include "CSpotContext.h" // for Context::ConfigState, Context (ptr only) -#include "Logger.h" // for CSPOT_LOG -#include "MercurySession.h" // for MercurySession, MercurySession::Response -#include "NanoPBHelper.h" // for pbDecode -#include "Packet.h" // for cspot -#include "PlaybackState.h" // for PlaybackState, PlaybackState::State -#include "TrackPlayer.h" // for TrackPlayer +#include "BellLogger.h" // for AbstractLogger +#include "CSpotContext.h" // for Context::ConfigState, Context (ptr only) +#include "Logger.h" // for CSPOT_LOG +#include "MercurySession.h" // for MercurySession, MercurySession::Response +#include "NanoPBHelper.h" // for pbDecode +#include "Packet.h" // for cspot +#include "PlaybackState.h" // for PlaybackState, PlaybackState::State +#include "TrackPlayer.h" // for TrackPlayer +#include "TrackQueue.h" #include "TrackReference.h" // for TrackReference #include "Utils.h" // for stringHexToBytes #include "pb_decode.h" // for pb_release @@ -20,38 +21,30 @@ using namespace cspot; -SpircHandler::SpircHandler(std::shared_ptr ctx) - : playbackState(ctx) { - - auto isAiringCallback = [this]() { - return !(isNextTrackPreloaded || isRequestedFromLoad); - }; +SpircHandler::SpircHandler(std::shared_ptr ctx) { + this->playbackState = std::make_shared(ctx); + this->trackQueue = std::make_shared(ctx, playbackState); auto EOFCallback = [this]() { - auto ref = this->playbackState.getNextTrackRef(); - - if (!isNextTrackPreloaded && !isRequestedFromLoad && ref != nullptr) { - isNextTrackPreloaded = true; - auto trackRef = TrackReference::fromTrackRef(ref); - this->trackPlayer->loadTrackFromRef(trackRef, 0, true); - } - - if (ref == nullptr) { - sendEvent(EventType::DEPLETED); - } - }; - - auto trackLoadedCallback = [this]() { - this->currentTrackInfo = this->trackPlayer->getCurrentTrackInfo(); - - if (isRequestedFromLoad) { - sendEvent(EventType::PLAYBACK_START, (int)nextTrackPosition); - setPause(false); + if (trackQueue->isFinished()) { + sendEvent(EventType::DEPLETED); } }; + auto trackLoadedCallback = [this](std::shared_ptr track) { + playbackState->setPlaybackState(PlaybackState::State::Playing); + playbackState->updatePositionMs(track->requestedPosition); + + this->notify(); + + // Send playback start event, unpause + sendEvent(EventType::PLAYBACK_START, (int) track->requestedPosition); + sendEvent(EventType::PLAY_PAUSE, false); + }; + this->ctx = ctx; - this->trackPlayer = std::make_shared(ctx, isAiringCallback, EOFCallback, trackLoadedCallback); + this->trackPlayer = std::make_shared( + ctx, trackQueue, EOFCallback, trackLoadedCallback); // Subscribe to mercury on session ready ctx->session->setConnectedHandler([this]() { this->subscribeToMercury(); }); @@ -82,105 +75,80 @@ void SpircHandler::subscribeToMercury() { subscriptionLambda); } -void SpircHandler::loadTrackFromURI(const std::string& uri) { - // {track/episode}:{gid} - bool isEpisode = uri.find("episode:") != std::string::npos; - auto gid = stringHexToBytes(uri.substr(uri.find(":") + 1)); - auto trackRef = TrackReference::fromGID(gid, isEpisode); +void SpircHandler::loadTrackFromURI(const std::string& uri) {} - isRequestedFromLoad = true; - isNextTrackPreloaded = false; +void SpircHandler::notifyAudioReachedPlayback() { + int offset = 0; - playbackState.setActive(true); + // get HEAD track + auto currentTrack = trackQueue->consumeTrack(nullptr, offset); - auto playbackRef = playbackState.getCurrentTrackRef(); + // Do not execute when meta is already updated + if (trackQueue->notifyPending) { + trackQueue->notifyPending = false; - if (playbackRef != nullptr) { - playbackState.updatePositionMs(playbackState.remoteFrame.state.position_ms); + playbackState->updatePositionMs(currentTrack->requestedPosition); - auto ref = TrackReference::fromTrackRef(playbackRef); - this->trackPlayer->loadTrackFromRef( - ref, playbackState.remoteFrame.state.position_ms, true); - playbackState.setPlaybackState(PlaybackState::State::Loading); - this->nextTrackPosition = playbackState.remoteFrame.state.position_ms; - } - - this->notify(); -} - -void SpircHandler::notifyAudioReachedPlayback() { - if (isRequestedFromLoad || isNextTrackPreloaded) { - playbackState.updatePositionMs(nextTrackPosition); - playbackState.setPlaybackState(PlaybackState::State::Playing); + // Reset position in queued track + currentTrack->requestedPosition = 0; } else { - setPause(true); - } + trackQueue->skipTrack(TrackQueue::SkipDirection::NEXT, false); + playbackState->updatePositionMs(0); - isRequestedFromLoad = false; - - if (isNextTrackPreloaded) { - isNextTrackPreloaded = false; - - playbackState.nextTrack(); - nextTrackPosition = 0; + // we moved to next track, re-acquire currentTrack again + currentTrack = trackQueue->consumeTrack(nullptr, offset); } this->notify(); - sendEvent(EventType::TRACK_INFO, this->trackPlayer->getCurrentTrackInfo()); + sendEvent(EventType::TRACK_INFO, currentTrack->trackInfo); } void SpircHandler::updatePositionMs(uint32_t position) { - playbackState.updatePositionMs(position); - notify(); + playbackState->updatePositionMs(position); + notify(); } void SpircHandler::disconnect() { - this->trackPlayer->stopTrack(); + this->trackQueue->stopTask(); + this->trackPlayer->resetState(); this->ctx->session->disconnect(); } void SpircHandler::handleFrame(std::vector& data) { - pb_release(Frame_fields, &playbackState.remoteFrame); - pbDecode(playbackState.remoteFrame, Frame_fields, data); + // Decode received spirc frame + playbackState->decodeRemoteFrame(data); - switch (playbackState.remoteFrame.typ) { + switch (playbackState->remoteFrame.typ) { case MessageType_kMessageTypeNotify: { CSPOT_LOG(debug, "Notify frame"); // Pause the playback if another player took control - if (playbackState.isActive() && - playbackState.remoteFrame.device_state.is_active) { + if (playbackState->isActive() && + playbackState->remoteFrame.device_state.is_active) { CSPOT_LOG(debug, "Another player took control, pausing playback"); - playbackState.setActive(false); - this->trackPlayer->stopTrack(); + playbackState->setActive(false); + + this->trackPlayer->stop(); sendEvent(EventType::DISC); } break; } case MessageType_kMessageTypeSeek: { - /* If next track is already downloading, we can't seek in the current one anymore. Also, - * when last track has been reached, we has to restart as we can't tell the difference */ - if ((!isNextTrackPreloaded && this->playbackState.getNextTrackRef()) || isRequestedFromLoad) { - CSPOT_LOG(debug, "Seek command while streaming current"); - playbackState.updatePositionMs(playbackState.remoteFrame.position); - trackPlayer->seekMs(playbackState.remoteFrame.position); - sendEvent(EventType::SEEK, (int)playbackState.remoteFrame.position); - } else { - CSPOT_LOG(debug, "Seek command while streaming next or before started"); - isRequestedFromLoad = true; - isNextTrackPreloaded = false; - auto ref = TrackReference::fromTrackRef(playbackState.getCurrentTrackRef()); - this->trackPlayer->loadTrackFromRef(ref, playbackState.remoteFrame.position, true); - this->nextTrackPosition = playbackState.remoteFrame.position; - } + this->trackPlayer->seekMs(playbackState->remoteFrame.position); + + playbackState->updatePositionMs(playbackState->remoteFrame.position); + notify(); + + sendEvent(EventType::SEEK, (int)playbackState->remoteFrame.position); + //sendEvent(EventType::FLUSH); break; } case MessageType_kMessageTypeVolume: - playbackState.setVolume(playbackState.remoteFrame.volume); + playbackState->setVolume(playbackState->remoteFrame.volume); this->notify(); - sendEvent(EventType::VOLUME, (int)playbackState.remoteFrame.volume); + sendEvent(EventType::VOLUME, (int)playbackState->remoteFrame.volume); break; case MessageType_kMessageTypePause: setPause(true); @@ -197,44 +165,51 @@ void SpircHandler::handleFrame(std::vector& data) { sendEvent(EventType::PREV); break; case MessageType_kMessageTypeLoad: { - CSPOT_LOG(debug, "Load frame!"); - isRequestedFromLoad = true; - isNextTrackPreloaded = false; + this->trackPlayer->start(); - playbackState.setActive(true); - playbackState.updateTracks(); + CSPOT_LOG(debug, "Load frame %d!", playbackState->remoteTracks.size()); - auto playbackRef = playbackState.getCurrentTrackRef(); - - if (playbackRef != nullptr) { - playbackState.updatePositionMs( - playbackState.remoteFrame.state.position_ms); - - auto ref = TrackReference::fromTrackRef(playbackRef); - this->trackPlayer->loadTrackFromRef( - ref, playbackState.remoteFrame.state.position_ms, true); - playbackState.setPlaybackState(PlaybackState::State::Loading); - this->nextTrackPosition = playbackState.remoteFrame.state.position_ms; + if (playbackState->remoteTracks.size() == 0) { + CSPOT_LOG(info, "No tracks in frame, stopping playback"); + break; } + playbackState->setActive(true); + + playbackState->updatePositionMs(playbackState->remoteFrame.position); + playbackState->setPlaybackState(PlaybackState::State::Playing); + + playbackState->syncWithRemote(); + + // Update track list in case we have a new one + trackQueue->updateTracks(playbackState->remoteFrame.state.position_ms, + true); + this->notify(); + + // Stop the current track, if any + trackPlayer->resetState(); break; } case MessageType_kMessageTypeReplace: { CSPOT_LOG(debug, "Got replace frame"); - playbackState.updateTracks(); + playbackState->syncWithRemote(); + + trackQueue->updateTracks(playbackState->remoteFrame.state.position_ms, + false); this->notify(); + + trackPlayer->resetState(); + sendEvent(EventType::FLUSH); break; } case MessageType_kMessageTypeShuffle: { CSPOT_LOG(debug, "Got shuffle frame"); - playbackState.setShuffle(playbackState.remoteFrame.state.shuffle); this->notify(); break; } case MessageType_kMessageTypeRepeat: { CSPOT_LOG(debug, "Got repeat frame"); - playbackState.setRepeat(playbackState.remoteFrame.state.repeat); this->notify(); break; } @@ -244,7 +219,7 @@ void SpircHandler::handleFrame(std::vector& data) { } void SpircHandler::setRemoteVolume(int volume) { - playbackState.setVolume(volume); + playbackState->setVolume(volume); notify(); } @@ -252,32 +227,34 @@ void SpircHandler::notify() { this->sendCmd(MessageType_kMessageTypeNotify); } -void SpircHandler::nextSong() { - if (playbackState.nextTrack()) { - isRequestedFromLoad = true; - isNextTrackPreloaded = false; - auto ref = TrackReference::fromTrackRef(playbackState.getCurrentTrackRef()); - this->trackPlayer->loadTrackFromRef(ref, 0, true); +void SpircHandler::skipSong(TrackQueue::SkipDirection dir) { + if (trackQueue->skipTrack(dir)) { + playbackState->setPlaybackState(PlaybackState::State::Playing); + notify(); + + // Reset track state + trackPlayer->resetState(); + + sendEvent(EventType::PLAY_PAUSE, false); } else { - sendEvent(EventType::FLUSH); - playbackState.updatePositionMs(0); - trackPlayer->stopTrack(); + playbackState->setPlaybackState(PlaybackState::State::Paused); + playbackState->updatePositionMs(0); + notify(); + + sendEvent(EventType::PLAY_PAUSE, true); } - this->nextTrackPosition = 0; + notify(); + + sendEvent(EventType::FLUSH); +} + +void SpircHandler::nextSong() { + skipSong(TrackQueue::SkipDirection::NEXT); } void SpircHandler::previousSong() { - playbackState.prevTrack(); - isRequestedFromLoad = true; - isNextTrackPreloaded = false; - - sendEvent(EventType::PREV); - auto ref = TrackReference::fromTrackRef(playbackState.getCurrentTrackRef()); - this->trackPlayer->loadTrackFromRef(ref, 0, true); - this->nextTrackPosition = 0; - - notify(); + skipSong(TrackQueue::SkipDirection::PREV); } std::shared_ptr SpircHandler::getTrackPlayer() { @@ -286,7 +263,7 @@ std::shared_ptr SpircHandler::getTrackPlayer() { void SpircHandler::sendCmd(MessageType typ) { // Serialize current player state - auto encodedFrame = playbackState.encodeCurrentFrame(typ); + auto encodedFrame = playbackState->encodeCurrentFrame(typ); auto responseLambda = [=](MercurySession::Response& res) { }; @@ -302,11 +279,11 @@ void SpircHandler::setEventHandler(EventHandler handler) { void SpircHandler::setPause(bool isPaused) { if (isPaused) { CSPOT_LOG(debug, "External pause command"); - playbackState.setPlaybackState(PlaybackState::State::Paused); + playbackState->setPlaybackState(PlaybackState::State::Paused); } else { CSPOT_LOG(debug, "External play command"); - playbackState.setPlaybackState(PlaybackState::State::Playing); + playbackState->setPlaybackState(PlaybackState::State::Playing); } notify(); sendEvent(EventType::PLAY_PAUSE, isPaused); diff --git a/components/spotify/cspot/src/TimeProvider.cpp b/components/spotify/cspot/src/TimeProvider.cpp index 5e952449..8617ef13 100644 --- a/components/spotify/cspot/src/TimeProvider.cpp +++ b/components/spotify/cspot/src/TimeProvider.cpp @@ -1,25 +1,24 @@ #include "TimeProvider.h" -#ifndef _WIN32 -#include -#endif - #include "BellLogger.h" // for AbstractLogger #include "Logger.h" // for CSPOT_LOG #include "Utils.h" // for extract, getCurrentTimestamp +#ifndef _WIN32 +#include +#endif using namespace cspot; -TimeProvider::TimeProvider() { -} +TimeProvider::TimeProvider() {} void TimeProvider::syncWithPingPacket(const std::vector& pongPacket) { - CSPOT_LOG(debug, "Time synced with spotify servers"); - // Spotify's timestamp is in seconds since unix time - convert to millis. - uint64_t remoteTimestamp = ((uint64_t) ntohl(extract(pongPacket, 0))) * 1000; - this->timestampDiff = remoteTimestamp - getCurrentTimestamp(); + CSPOT_LOG(debug, "Time synced with spotify servers"); + // Spotify's timestamp is in seconds since unix time - convert to millis. + uint64_t remoteTimestamp = + ((uint64_t)ntohl(extract(pongPacket, 0))) * 1000; + this->timestampDiff = remoteTimestamp - getCurrentTimestamp(); } unsigned long long TimeProvider::getSyncedTimestamp() { - return getCurrentTimestamp() + this->timestampDiff; + return getCurrentTimestamp() + this->timestampDiff; } \ No newline at end of file diff --git a/components/spotify/cspot/src/TrackPlayer.cpp b/components/spotify/cspot/src/TrackPlayer.cpp index 99de08d7..03155831 100644 --- a/components/spotify/cspot/src/TrackPlayer.cpp +++ b/components/spotify/cspot/src/TrackPlayer.cpp @@ -1,18 +1,26 @@ #include "TrackPlayer.h" -#include // for mutex, scoped_lock -#include // for string -#include // for remove_extent_t -#include // for vector, vector<>::value_type +#include // for mutex, scoped_lock +#include // for string +#include // for remove_extent_t +#include // for vector, vector<>::value_type #include "BellLogger.h" // for AbstractLogger #include "BellUtils.h" // for BELL_SLEEP_MS -#include "CDNTrackStream.h" // for CDNTrackStream, CDNTrackStream::TrackInfo #include "Logger.h" // for CSPOT_LOG #include "Packet.h" // for cspot -#include "TrackProvider.h" // for TrackProvider +#include "TrackQueue.h" // for CDNTrackStream, CDNTrackStream::TrackInfo #include "WrappedSemaphore.h" // for WrappedSemaphore +#ifdef BELL_VORBIS_FLOAT +#define VORBIS_SEEK(file, position) (ov_time_seek(file, (double)position / 1000)) +#define VORBIS_READ(file, buffer, bufferSize, section) (ov_read(file, buffer, bufferSize, 0, 2, 1, section)) +#else +#define VORBIS_SEEK(file, position) (ov_time_seek(file, position)) +#define VORBIS_READ(file, buffer, bufferSize, section) \ + (ov_read(file, buffer, bufferSize, section)) +#endif + namespace cspot { struct Context; struct TrackReference; @@ -38,13 +46,14 @@ static long vorbisTellCb(TrackPlayer* self) { return self->_vorbisTell(); } -TrackPlayer::TrackPlayer(std::shared_ptr ctx, isAiringCallback isAiring, EOFCallback eof, TrackLoadedCallback trackLoaded) - : bell::Task("cspot_player", 48 * 1024, 5, 1) { +TrackPlayer::TrackPlayer(std::shared_ptr ctx, + std::shared_ptr trackQueue, + EOFCallback eof, TrackLoadedCallback trackLoaded) + : bell::Task("cspot_player", 32 * 1024, 5, 1) { this->ctx = ctx; - this->isAiring = isAiring; this->eofCallback = eof; this->trackLoaded = trackLoaded; - this->trackProvider = std::make_shared(ctx); + this->trackQueue = trackQueue; this->playbackSemaphore = std::make_unique(5); // Initialize vorbis callbacks @@ -55,9 +64,6 @@ TrackPlayer::TrackPlayer(std::shared_ptr ctx, isAiringCallback i (decltype(ov_callbacks::close_func))&vorbisCloseCb, (decltype(ov_callbacks::tell_func))&vorbisTellCb, }; - isRunning = true; - - startTask(); } TrackPlayer::~TrackPlayer() { @@ -65,123 +71,192 @@ TrackPlayer::~TrackPlayer() { std::scoped_lock lock(runningMutex); } -void TrackPlayer::loadTrackFromRef(TrackReference& ref, size_t positionMs, - bool startAutomatically) { - this->playbackPosition = positionMs; - this->autoStart = startAutomatically; - - auto nextTrack = trackProvider->loadFromTrackRef(ref); - - stopTrack(); - this->sequence++; - this->currentTrackStream = nextTrack; - this->playbackSemaphore->give(); +void TrackPlayer::start() { + if (!isRunning) { + isRunning = true; + startTask(); + } } -void TrackPlayer::stopTrack() { +void TrackPlayer::stop() { + isRunning = false; + resetState(); + std::scoped_lock lock(runningMutex); +} + +void TrackPlayer::resetState() { + // Mark for reset + this->pendingReset = true; this->currentSongPlaying = false; - std::scoped_lock lock(playbackMutex); + + std::scoped_lock lock(dataOutMutex); + + CSPOT_LOG(info, "Resetting state"); } void TrackPlayer::seekMs(size_t ms) { - std::scoped_lock lock(seekMutex); -#ifdef BELL_VORBIS_FLOAT - ov_time_seek(&vorbisFile, (double)ms / 1000); -#else - ov_time_seek(&vorbisFile, ms); -#endif + if (inFuture) { + // We're in the middle of the next track, so we need to reset the player in order to seek + resetState(); + } + + CSPOT_LOG(info, "Seeking..."); + this->pendingSeekPositionMs = ms; } void TrackPlayer::runTask() { std::scoped_lock lock(runningMutex); + std::shared_ptr track, newTrack = nullptr; + + int trackOffset = 0; + bool eof = false; + bool endOfQueueReached = false; + while (isRunning) { - this->playbackSemaphore->twait(100); - - if (this->currentTrackStream == nullptr) { + // Ensure we even have any tracks to play + if (!this->trackQueue->hasTracks() || + (endOfQueueReached && trackQueue->isFinished())) { + this->trackQueue->playableSemaphore->twait(300); continue; } - CSPOT_LOG(info, "Player received a track, waiting for it to be ready..."); - - // when track changed many times and very quickly, we are stuck on never-given semaphore - while (this->currentTrackStream->trackReady->twait(250)); - CSPOT_LOG(info, "Got track"); + // Last track was interrupted, reset to default + if (pendingReset) { + track = nullptr; + pendingReset = false; + inFuture = false; + } - if (this->currentTrackStream->status == CDNTrackStream::Status::FAILED) { - CSPOT_LOG(error, "Track failed to load, skipping it"); - this->currentTrackStream = nullptr; - this->eofCallback(); + endOfQueueReached = false; + + // Wait 800ms. If next reset is requested in meantime, restart the queue. + // Gets rid of excess actions during rapid queueing + BELL_SLEEP_MS(50); + + if (pendingReset) { continue; } - this->currentSongPlaying = true; + newTrack = trackQueue->consumeTrack(track, trackOffset); - this->trackLoaded(); + if (newTrack == nullptr) { + if (trackOffset == -1) { + // Reset required + track = nullptr; + } - this->playbackMutex.lock(); - - int32_t r = ov_open_callbacks(this, &vorbisFile, NULL, 0, vorbisCallbacks); - - if (playbackPosition > 0) { -#ifdef BELL_VORBIS_FLOAT - ov_time_seek(&vorbisFile, (double)playbackPosition / 1000); -#else - ov_time_seek(&vorbisFile, playbackPosition); -#endif + BELL_SLEEP_MS(100); + continue; } - bool eof = false; + track = newTrack; - while (!eof && currentSongPlaying) { - seekMutex.lock(); -#ifdef BELL_VORBIS_FLOAT - long ret = ov_read(&vorbisFile, (char*)&pcmBuffer[0], pcmBuffer.size(), - 0, 2, 1, ¤tSection); -#else - long ret = ov_read(&vorbisFile, (char*)&pcmBuffer[0], pcmBuffer.size(), - ¤tSection); -#endif - seekMutex.unlock(); - if (ret == 0) { - CSPOT_LOG(info, "EOF"); - // and done :) - eof = true; - } else if (ret < 0) { - CSPOT_LOG(error, "An error has occured in the stream %d", ret); - currentSongPlaying = false; - } else { + inFuture = trackOffset > 0; - if (this->dataCallback != nullptr) { - auto toWrite = ret; + if (track->state != QueuedTrack::State::READY) { + track->loadedSemaphore->twait(5000); - while (!eof && currentSongPlaying && toWrite > 0) { - auto written = - dataCallback(pcmBuffer.data() + (ret - toWrite), toWrite, - this->currentTrackStream->trackInfo.trackId, this->sequence); - if (written == 0) { - BELL_SLEEP_MS(50); + if (track->state != QueuedTrack::State::READY) { + CSPOT_LOG(error, "Track failed to load, skipping it"); + this->eofCallback(); + continue; + } + } + + CSPOT_LOG(info, "Got track ID=%s", track->identifier.c_str()); + + currentSongPlaying = true; + + { + std::scoped_lock lock(playbackMutex); + + currentTrackStream = track->getAudioFile(); + + // Open the stream + currentTrackStream->openStream(); + + if (pendingReset || !currentSongPlaying) { + continue; + } + + if (trackOffset == 0 && pendingSeekPositionMs == 0) { + this->trackLoaded(track); + } + + int32_t r = + ov_open_callbacks(this, &vorbisFile, NULL, 0, vorbisCallbacks); + + if (pendingSeekPositionMs > 0) { + track->requestedPosition = pendingSeekPositionMs; + } + + if (track->requestedPosition > 0) { + VORBIS_SEEK(&vorbisFile, track->requestedPosition); + } + + eof = false; + + CSPOT_LOG(info, "Playing"); + + while (!eof && currentSongPlaying) { + // Execute seek if needed + if (pendingSeekPositionMs > 0) { + uint32_t seekPosition = pendingSeekPositionMs; + + // Reset the pending seek position + pendingSeekPositionMs = 0; + + // Seek to the new position + VORBIS_SEEK(&vorbisFile, seekPosition); + } + + long ret = VORBIS_READ(&vorbisFile, (char*)&pcmBuffer[0], + pcmBuffer.size(), ¤tSection); + + if (ret == 0) { + CSPOT_LOG(info, "EOF"); + // and done :) + eof = true; + } else if (ret < 0) { + CSPOT_LOG(error, "An error has occured in the stream %d", ret); + currentSongPlaying = false; + } else { + if (this->dataCallback != nullptr) { + auto toWrite = ret; + + while (!eof && currentSongPlaying && !pendingReset && toWrite > 0) { + int written = 0; + { + std::scoped_lock dataOutLock(dataOutMutex); + // If reset happened during playback, return + if (!currentSongPlaying || pendingReset) + break; + + written = + dataCallback(pcmBuffer.data() + (ret - toWrite), toWrite, track->identifier); + } + if (written == 0) { + BELL_SLEEP_MS(50); + } + toWrite -= written; } - toWrite -= written; } } } - } - ov_clear(&vorbisFile); + ov_clear(&vorbisFile); - // With very large buffers, track N+1 can be downloaded while N has not aired yet and - // if we continue, the currentTrackStream will be emptied, causing a crash in - // notifyAudioReachedPlayback when it will look for trackInfo. A busy loop is never - // ideal, but this low impact, infrequent and more simple than yet another semaphore - while (currentSongPlaying && !isAiring()) { - BELL_SLEEP_MS(100); - } + CSPOT_LOG(info, "Playing done"); - // always move back to LOADING (ensure proper seeking after last track has been loaded) - this->currentTrackStream.reset(); - this->playbackMutex.unlock(); + // always move back to LOADING (ensure proper seeking after last track has been loaded) + currentTrackStream = nullptr; + } if (eof) { + if (trackQueue->isFinished()) { + endOfQueueReached = true; + } + this->eofCallback(); } } @@ -226,10 +301,6 @@ long TrackPlayer::_vorbisTell() { return this->currentTrackStream->getPosition(); } -CDNTrackStream::TrackInfo TrackPlayer::getCurrentTrackInfo() { - return this->currentTrackStream->trackInfo; -} - void TrackPlayer::setDataCallback(DataCallback callback) { this->dataCallback = callback; } diff --git a/components/spotify/cspot/src/Utils.cpp b/components/spotify/cspot/src/Utils.cpp index 6983e3ae..329f358f 100644 --- a/components/spotify/cspot/src/Utils.cpp +++ b/components/spotify/cspot/src/Utils.cpp @@ -8,7 +8,7 @@ #include // for enable_if<>::type #include #ifndef _WIN32 -#include +#include #endif unsigned long long getCurrentTimestamp() { From 806cb054baeb97b493908ec63a417fd8d86d43d8 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Sun, 7 May 2023 00:12:02 +0200 Subject: [PATCH 06/29] add missing files, removing un-nedded ones --- .../{CDNTrackStream.h => CDNAudioFile.h} | 65 +- .../spotify/cspot/include/TrackProvider.h | 40 -- components/spotify/cspot/include/TrackQueue.h | 134 ++++ .../{CDNTrackStream.cpp => CDNAudioFile.cpp} | 79 +-- .../spotify/cspot/src/TrackProvider.cpp | 245 ------- components/spotify/cspot/src/TrackQueue.cpp | 603 ++++++++++++++++++ .../spotify/cspot/src/TrackReference.cpp | 156 +++++ 7 files changed, 937 insertions(+), 385 deletions(-) rename components/spotify/cspot/include/{CDNTrackStream.h => CDNAudioFile.h} (58%) delete mode 100644 components/spotify/cspot/include/TrackProvider.h create mode 100644 components/spotify/cspot/include/TrackQueue.h rename components/spotify/cspot/src/{CDNTrackStream.cpp => CDNAudioFile.cpp} (68%) delete mode 100644 components/spotify/cspot/src/TrackProvider.cpp create mode 100644 components/spotify/cspot/src/TrackQueue.cpp create mode 100644 components/spotify/cspot/src/TrackReference.cpp diff --git a/components/spotify/cspot/include/CDNTrackStream.h b/components/spotify/cspot/include/CDNAudioFile.h similarity index 58% rename from components/spotify/cspot/include/CDNTrackStream.h rename to components/spotify/cspot/include/CDNAudioFile.h index be7c2ca6..90cf8286 100644 --- a/components/spotify/cspot/include/CDNTrackStream.h +++ b/components/spotify/cspot/include/CDNAudioFile.h @@ -1,10 +1,10 @@ #pragma once -#include // for size_t -#include // for uint8_t -#include // for shared_ptr, unique_ptr -#include // for string -#include // for vector +#include // for size_t +#include // for uint8_t +#include // for shared_ptr, unique_ptr +#include // for string +#include // for vector #include "Crypto.h" // for Crypto #include "HTTPClient.h" // for HTTPClient @@ -16,46 +16,45 @@ class WrappedSemaphore; namespace cspot { class AccessKeyFetcher; -class CDNTrackStream { +class CDNAudioFile { public: - CDNTrackStream(std::shared_ptr); - ~CDNTrackStream(); - - enum class Status { INITIALIZING, HAS_DATA, HAS_URL, FAILED }; - - struct TrackInfo { - std::string trackId; - std::string name; - std::string album; - std::string artist; - std::string imageUrl; - int duration; - }; - - TrackInfo trackInfo; - - Status status; - std::unique_ptr trackReady; - - void fetchFile(const std::vector& trackId, - const std::vector& audioKey); - - void fail(); + CDNAudioFile(const std::string& cdnUrl, const std::vector& audioKey); + /** + * @brief Opens connection to the provided cdn url, and fetches track metadata. + */ void openStream(); + /** + * @brief Read and decrypt part of the cdn stream + * + * @param dst buffer where to read received data to + * @param amount of bytes to read + * + * @returns amount of bytes read + */ size_t readBytes(uint8_t* dst, size_t bytes); + /** + * @brief Returns current position in CDN stream + */ size_t getPosition(); + /** + * @brief returns total size of the audio file in bytes + */ size_t getSize(); + /** + * @brief Seeks the track to provided position + * @param position position where to seek the track + */ void seek(size_t position); private: const int OPUS_HEADER_SIZE = 8 * 1024; - const int OPUS_FOOTER_PREFFERED = 1024 * 12; // 12K should be safe + const int OPUS_FOOTER_PREFFERED = 1024 * 12; // 12K should be safe const int SEEK_MARGIN_SIZE = 1024 * 4; const int HTTP_BUFFER_SIZE = 1024 * 14; @@ -74,12 +73,9 @@ class CDNTrackStream { 0x3f, 0x63, 0x0d, 0x93}; std::unique_ptr crypto; - std::shared_ptr accessKeyFetcher; - std::unique_ptr httpConnection; - bool isConnected = false; - size_t position = 0; // Spotify header size + size_t position = 0; size_t totalFileSize = 0; size_t lastRequestPosition = 0; size_t lastRequestCapacity = 0; @@ -87,7 +83,6 @@ class CDNTrackStream { bool enableRequestMargin = false; std::string cdnUrl; - std::vector trackId; std::vector audioKey; void decrypt(uint8_t* dst, size_t nbytes, size_t pos); diff --git a/components/spotify/cspot/include/TrackProvider.h b/components/spotify/cspot/include/TrackProvider.h deleted file mode 100644 index de2900fe..00000000 --- a/components/spotify/cspot/include/TrackProvider.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include // for uint8_t -#include // for shared_ptr, unique_ptr, weak_ptr -#include // for vector - -#include "MercurySession.h" // for MercurySession -#include "TrackReference.h" // for TrackReference -#include "protobuf/metadata.pb.h" // for Episode, Restriction, Track - -namespace cspot { -class AccessKeyFetcher; -class CDNTrackStream; -struct Context; - -class TrackProvider { - public: - TrackProvider(std::shared_ptr ctx); - ~TrackProvider(); - - std::shared_ptr loadFromTrackRef(TrackReference& trackRef); - - private: - std::shared_ptr accessKeyFetcher; - std::shared_ptr ctx; - std::unique_ptr cdnStream; - - Track trackInfo; - Episode episodeInfo; - std::weak_ptr currentTrackReference; - TrackReference trackIdInfo; - - void queryMetadata(); - void onMetadataResponse(MercurySession::Response& res); - bool doRestrictionsApply(Restriction* restrictions, int count); - void fetchFile(const std::vector& fileId, - const std::vector& trackId); - bool canPlayTrack(int index); -}; -} // namespace cspot \ No newline at end of file diff --git a/components/spotify/cspot/include/TrackQueue.h b/components/spotify/cspot/include/TrackQueue.h new file mode 100644 index 00000000..7ff097e3 --- /dev/null +++ b/components/spotify/cspot/include/TrackQueue.h @@ -0,0 +1,134 @@ +#pragma once + +#include // for size_t +#include +#include +#include +#include + +#include "BellTask.h" +#include "PlaybackState.h" +#include "TrackReference.h" + +#include "protobuf/metadata.pb.h" // for Track, _Track, AudioFile, Episode + +namespace bell { +class WrappedSemaphore; +}; + +namespace cspot { +struct Context; +class AccessKeyFetcher; +class CDNAudioFile; + +// Used in got track info event +struct TrackInfo { + std::string name, album, artist, imageUrl, trackId; + uint32_t duration; + + void loadPbTrack(Track* pbTrack, const std::vector& gid); + void loadPbEpisode(Episode* pbEpisode, const std::vector& gid); +}; + +class QueuedTrack { + public: + QueuedTrack(TrackReference& ref, std::shared_ptr ctx, + uint32_t requestedPosition = 0); + ~QueuedTrack(); + + enum class State { + QUEUED, + PENDING_META, + KEY_REQUIRED, + PENDING_KEY, + CDN_REQUIRED, + READY, + FAILED + }; + + std::shared_ptr loadedSemaphore; + + State state = State::QUEUED; // Current state of the track + TrackReference ref; // Holds GID, URI and Context + TrackInfo trackInfo; // Full track information fetched from spotify, name etc + + uint32_t requestedPosition; + std::string identifier; + + // Will return nullptr if the track is not ready + std::shared_ptr getAudioFile(); + + // --- Steps --- + void stepLoadMetadata( + Track* pbTrack, Episode* pbEpisode, std::mutex& trackListMutex, + std::shared_ptr updateSemaphore); + + void stepParseMetadata(Track* pbTrack, Episode* pbEpisode); + + void stepLoadAudioFile( + std::mutex& trackListMutex, + std::shared_ptr updateSemaphore); + + void stepLoadCDNUrl(const std::string& accessKey); + + void expire(); + + private: + std::shared_ptr ctx; + + uint64_t pendingMercuryRequest = 0; + uint32_t pendingAudioKeyRequest = 0; + + std::vector trackId, fileId, audioKey; + std::string cdnUrl; +}; + +class TrackQueue : public bell::Task { + public: + TrackQueue(std::shared_ptr ctx, + std::shared_ptr playbackState); + ~TrackQueue(); + + enum class SkipDirection { NEXT, PREV }; + + std::shared_ptr playableSemaphore; + std::atomic notifyPending = false; + + + void runTask() override; + void stopTask(); + + bool hasTracks(); + bool isFinished(); + bool skipTrack(SkipDirection dir, bool expectNotify = true); + void updateTracks(uint32_t requestedPosition = 0, bool initial = false); + TrackInfo getTrackInfo(std::string_view identifier); + std::shared_ptr consumeTrack( + std::shared_ptr prevSong, int& offset); + + private: + static const int MAX_TRACKS_PRELOAD = 3; + + std::shared_ptr accessKeyFetcher; + std::shared_ptr playbackState; + std::shared_ptr ctx; + std::shared_ptr processSemaphore; + + std::deque> preloadedTracks; + std::vector currentTracks; + std::mutex tracksMutex, runningMutex; + + // PB data + Track pbTrack; + Episode pbEpisode; + + std::string accessKey; + + int16_t currentTracksIndex = -1; + + bool isRunning = false; + + void processTrack(std::shared_ptr track); + bool queueNextTrack(int offset = 0, uint32_t positionMs = 0); +}; +} // namespace cspot diff --git a/components/spotify/cspot/src/CDNTrackStream.cpp b/components/spotify/cspot/src/CDNAudioFile.cpp similarity index 68% rename from components/spotify/cspot/src/CDNTrackStream.cpp rename to components/spotify/cspot/src/CDNAudioFile.cpp index e38c6f13..f45c3237 100644 --- a/components/spotify/cspot/src/CDNTrackStream.cpp +++ b/components/spotify/cspot/src/CDNAudioFile.cpp @@ -1,4 +1,4 @@ -#include "CDNTrackStream.h" +#include "CDNAudioFile.h" #include // for memcpy #include // for __base @@ -7,15 +7,16 @@ #include // for string_view #include // for remove_extent_t -#include "AccessKeyFetcher.h" // for AccessKeyFetcher -#include "BellLogger.h" // for AbstractLogger +#include "AccessKeyFetcher.h" // for AccessKeyFetcher +#include "BellLogger.h" // for AbstractLogger +#include "Crypto.h" #include "Logger.h" // for CSPOT_LOG #include "Packet.h" // for cspot #include "SocketStream.h" // for SocketStream #include "Utils.h" // for bigNumAdd, bytesToHexString, string... #include "WrappedSemaphore.h" // for WrappedSemaphore #ifdef BELL_ONLY_CJSON -#include "cJSON.h" +#include "cJSON.h " #else #include "nlohmann/json.hpp" // for basic_json<>::object_t, basic_json #include "nlohmann/json_fwd.hpp" // for json @@ -23,73 +24,22 @@ using namespace cspot; -CDNTrackStream::CDNTrackStream( - std::shared_ptr accessKeyFetcher) { - this->accessKeyFetcher = accessKeyFetcher; - this->status = Status::INITIALIZING; - this->trackReady = std::make_unique(5); +CDNAudioFile::CDNAudioFile(const std::string& cdnUrl, + const std::vector& audioKey) + : cdnUrl(cdnUrl), audioKey(audioKey) { this->crypto = std::make_unique(); } -CDNTrackStream::~CDNTrackStream() {} - -void CDNTrackStream::fail() { - this->status = Status::FAILED; - this->trackReady->give(); -} - -void CDNTrackStream::fetchFile(const std::vector& trackId, - const std::vector& audioKey) { - this->status = Status::HAS_DATA; - this->trackId = trackId; - this->audioKey = std::vector(audioKey.begin() + 4, audioKey.end()); - - accessKeyFetcher->getAccessKey([this, trackId, audioKey](std::string key) { - CSPOT_LOG(info, "Received access key, fetching CDN URL..."); - - std::string requestUrl = string_format( - "https://api.spotify.com/v1/storage-resolve/files/audio/interactive/" - "%s?alt=json&product=9", - bytesToHexString(trackId).c_str()); - - auto req = bell::HTTPClient::get( - requestUrl, - {bell::HTTPClient::ValueHeader({"Authorization", "Bearer " + key})}); - - std::string_view result = req->body(); - -#ifdef BELL_ONLY_CJSON - cJSON* jsonResult = cJSON_Parse(result.data()); - std::string cdnUrl = - cJSON_GetArrayItem(cJSON_GetObjectItem(jsonResult, "cdnurl"), 0) - ->valuestring; - cJSON_Delete(jsonResult); -#else - auto jsonResult = nlohmann::json::parse(result); - std::string cdnUrl = jsonResult["cdnurl"][0]; -#endif - if (this->status != Status::FAILED) { - - this->cdnUrl = cdnUrl; - this->status = Status::HAS_URL; - CSPOT_LOG(info, "Received CDN URL, %s", cdnUrl.c_str()); - - this->openStream(); - this->trackReady->give(); - } - }); -} - -size_t CDNTrackStream::getPosition() { +size_t CDNAudioFile::getPosition() { return this->position; } -void CDNTrackStream::seek(size_t newPos) { +void CDNAudioFile::seek(size_t newPos) { this->enableRequestMargin = true; this->position = newPos; } -void CDNTrackStream::openStream() { +void CDNAudioFile::openStream() { CSPOT_LOG(info, "Opening HTTP stream to %s", this->cdnUrl.c_str()); // Open connection, read first 128 bytes @@ -121,10 +71,9 @@ void CDNTrackStream::openStream() { this->position = 0; this->lastRequestPosition = 0; this->lastRequestCapacity = 0; - this->isConnected = true; } -size_t CDNTrackStream::readBytes(uint8_t* dst, size_t bytes) { +size_t CDNAudioFile::readBytes(uint8_t* dst, size_t bytes) { size_t offsetPosition = position + SPOTIFY_OPUS_HEADER; size_t actualFileSize = this->totalFileSize + SPOTIFY_OPUS_HEADER; @@ -199,11 +148,11 @@ size_t CDNTrackStream::readBytes(uint8_t* dst, size_t bytes) { return bytes; } -size_t CDNTrackStream::getSize() { +size_t CDNAudioFile::getSize() { return this->totalFileSize; } -void CDNTrackStream::decrypt(uint8_t* dst, size_t nbytes, size_t pos) { +void CDNAudioFile::decrypt(uint8_t* dst, size_t nbytes, size_t pos) { auto calculatedIV = bigNumAdd(audioAESIV, pos / 16); this->crypto->aesCTRXcrypt(this->audioKey, calculatedIV, dst, nbytes); diff --git a/components/spotify/cspot/src/TrackProvider.cpp b/components/spotify/cspot/src/TrackProvider.cpp deleted file mode 100644 index 14b6ffd7..00000000 --- a/components/spotify/cspot/src/TrackProvider.cpp +++ /dev/null @@ -1,245 +0,0 @@ -#include "TrackProvider.h" - -#include // for assert -#include // for strlen -#include // for uint8_t -#include // for __base -#include // for shared_ptr, weak_ptr, make_shared -#include // for string, operator+ -#include // for remove_extent_t - -#include "AccessKeyFetcher.h" // for AccessKeyFetcher -#include "BellLogger.h" // for AbstractLogger -#include "CDNTrackStream.h" // for CDNTrackStream, CDNTrackStream::Tr... -#include "CSpotContext.h" // for Context::ConfigState, Context (ptr... -#include "Logger.h" // for CSPOT_LOG -#include "MercurySession.h" // for MercurySession, MercurySession::Da... -#include "NanoPBHelper.h" // for pbArrayToVector, pbDecode -#include "Packet.h" // for cspot -#include "TrackReference.h" // for TrackReference, TrackReference::Type -#include "Utils.h" // for bytesToHexString, string_format -#include "WrappedSemaphore.h" // for WrappedSemaphore -#include "pb_decode.h" // for pb_release -#include "protobuf/metadata.pb.h" // for Track, _Track, AudioFile, Episode - -using namespace cspot; - -TrackProvider::TrackProvider(std::shared_ptr ctx) { - this->accessKeyFetcher = std::make_shared(ctx); - this->ctx = ctx; - this->cdnStream = - std::make_unique(this->accessKeyFetcher); - - this->trackInfo = {}; -} - -TrackProvider::~TrackProvider() { - pb_release(Track_fields, &trackInfo); - pb_release(Episode_fields, &trackInfo); -} - -std::shared_ptr TrackProvider::loadFromTrackRef( - TrackReference& trackRef) { - auto track = std::make_shared(this->accessKeyFetcher); - this->currentTrackReference = track; - this->trackIdInfo = trackRef; - - queryMetadata(); - return track; -} - -void TrackProvider::queryMetadata() { - std::string requestUrl = string_format( - "hm://metadata/3/%s/%s", - trackIdInfo.type == TrackReference::Type::TRACK ? "track" : "episode", - bytesToHexString(trackIdInfo.gid).c_str()); - CSPOT_LOG(debug, "Requesting track metadata from %s", requestUrl.c_str()); - - auto responseHandler = [this](MercurySession::Response& res) { - this->onMetadataResponse(res); - }; - - // Execute the request - ctx->session->execute(MercurySession::RequestType::GET, requestUrl, - responseHandler); -} - -void TrackProvider::onMetadataResponse(MercurySession::Response& res) { - CSPOT_LOG(debug, "Got track metadata response"); - - int alternativeCount, filesCount = 0; - bool canPlay = false; - AudioFile* selectedFiles; - std::vector trackId, fileId; - - if (trackIdInfo.type == TrackReference::Type::TRACK) { - pb_release(Track_fields, &trackInfo); - assert(res.parts.size() > 0); - pbDecode(trackInfo, Track_fields, res.parts[0]); - CSPOT_LOG(info, "Track name: %s", trackInfo.name); - CSPOT_LOG(info, "Track duration: %d", trackInfo.duration); - - CSPOT_LOG(debug, "trackInfo.restriction.size() = %d", - trackInfo.restriction_count); - - if (doRestrictionsApply(trackInfo.restriction, - trackInfo.restriction_count)) { - // Go through alternatives - for (int x = 0; x < trackInfo.alternative_count; x++) { - if (!doRestrictionsApply(trackInfo.alternative[x].restriction, - trackInfo.alternative[x].restriction_count)) { - selectedFiles = trackInfo.alternative[x].file; - filesCount = trackInfo.alternative[x].file_count; - trackId = pbArrayToVector(trackInfo.alternative[x].gid); - break; - } - } - } else { - selectedFiles = trackInfo.file; - filesCount = trackInfo.file_count; - trackId = pbArrayToVector(trackInfo.gid); - } - - // Set track's metadata - auto trackRef = this->currentTrackReference.lock(); - - auto imageId = - pbArrayToVector(trackInfo.album.cover_group.image[0].file_id); - - trackRef->trackInfo.trackId = bytesToHexString(trackIdInfo.gid); - trackRef->trackInfo.name = std::string(trackInfo.name); - trackRef->trackInfo.album = std::string(trackInfo.album.name); - trackRef->trackInfo.artist = std::string(trackInfo.artist[0].name); - trackRef->trackInfo.imageUrl = - "https://i.scdn.co/image/" + bytesToHexString(imageId); - trackRef->trackInfo.duration = trackInfo.duration; - } else { - pb_release(Episode_fields, &episodeInfo); - assert(res.parts.size() > 0); - pbDecode(episodeInfo, Episode_fields, res.parts[0]); - - CSPOT_LOG(info, "Episode name: %s", episodeInfo.name); - CSPOT_LOG(info, "Episode duration: %d", episodeInfo.duration); - - CSPOT_LOG(debug, "episodeInfo.restriction.size() = %d", - episodeInfo.restriction_count); - if (!doRestrictionsApply(episodeInfo.restriction, - episodeInfo.restriction_count)) { - selectedFiles = episodeInfo.file; - filesCount = episodeInfo.file_count; - trackId = pbArrayToVector(episodeInfo.gid); - } - - auto trackRef = this->currentTrackReference.lock(); - - auto imageId = pbArrayToVector(episodeInfo.covers->image[0].file_id); - - trackRef->trackInfo.trackId = bytesToHexString(trackIdInfo.gid); - trackRef->trackInfo.name = std::string(episodeInfo.name); - trackRef->trackInfo.album = ""; - trackRef->trackInfo.artist = "", - trackRef->trackInfo.imageUrl = - "https://i.scdn.co/image/" + bytesToHexString(imageId); - trackRef->trackInfo.duration = episodeInfo.duration; - } - - for (int x = 0; x < filesCount; x++) { - CSPOT_LOG(debug, "File format: %d", selectedFiles[x].format); - if (selectedFiles[x].format == ctx->config.audioFormat) { - fileId = pbArrayToVector(selectedFiles[x].file_id); - break; // If file found stop searching - } - - // Fallback to OGG Vorbis 96kbps - if (fileId.size() == 0 && - selectedFiles[x].format == AudioFormat_OGG_VORBIS_96) { - fileId = pbArrayToVector(selectedFiles[x].file_id); - } - } - - // No viable files found for playback - if (fileId.size() == 0) { - CSPOT_LOG(info, "File not available for playback"); - // no alternatives for song - if (!this->currentTrackReference.expired()) { - auto trackRef = this->currentTrackReference.lock(); - trackRef->status = CDNTrackStream::Status::FAILED; - trackRef->trackReady->give(); - } - return; - } - - this->fetchFile(fileId, trackId); -} - -void TrackProvider::fetchFile(const std::vector& fileId, - const std::vector& trackId) { - ctx->session->requestAudioKey( - trackId, fileId, - [this, fileId](bool success, const std::vector& audioKey) { - if (success) { - CSPOT_LOG(info, "Got audio key"); - if (!this->currentTrackReference.expired()) { - auto ref = this->currentTrackReference.lock(); - ref->fetchFile(fileId, audioKey); - } - - } else { - CSPOT_LOG(error, "Failed to get audio key"); - if (!this->currentTrackReference.expired()) { - auto ref = this->currentTrackReference.lock(); - ref->fail(); - } - } - }); -} - -bool countryListContains(char* countryList, char* country) { - uint16_t countryList_length = strlen(countryList); - for (int x = 0; x < countryList_length; x += 2) { - if (countryList[x] == country[0] && countryList[x + 1] == country[1]) { - return true; - } - } - return false; -} - -bool TrackProvider::doRestrictionsApply(Restriction* restrictions, int count) { - for (int x = 0; x < count; x++) { - if (restrictions[x].countries_allowed != nullptr) { - return !countryListContains(restrictions[x].countries_allowed, - (char*)ctx->config.countryCode.c_str()); - } - - if (restrictions[x].countries_forbidden != nullptr) { - return countryListContains(restrictions[x].countries_forbidden, - (char*)ctx->config.countryCode.c_str()); - } - } - - return false; -} - -bool TrackProvider::canPlayTrack(int altIndex) { - if (altIndex < 0) { - - } else { - for (int x = 0; x < trackInfo.alternative[altIndex].restriction_count; - x++) { - if (trackInfo.alternative[altIndex].restriction[x].countries_allowed != - nullptr) { - return countryListContains( - trackInfo.alternative[altIndex].restriction[x].countries_allowed, - (char*)ctx->config.countryCode.c_str()); - } - - if (trackInfo.alternative[altIndex].restriction[x].countries_forbidden != - nullptr) { - return !countryListContains( - trackInfo.alternative[altIndex].restriction[x].countries_forbidden, - (char*)ctx->config.countryCode.c_str()); - } - } - } - return true; -} diff --git a/components/spotify/cspot/src/TrackQueue.cpp b/components/spotify/cspot/src/TrackQueue.cpp new file mode 100644 index 00000000..f2e440d6 --- /dev/null +++ b/components/spotify/cspot/src/TrackQueue.cpp @@ -0,0 +1,603 @@ +#include "TrackQueue.h" +#include + +#include +#include +#include +#include + +#include "AccessKeyFetcher.h" +#include "BellTask.h" +#include "CDNAudioFile.h" +#include "CSpotContext.h" +#include "HTTPClient.h" +#include "Logger.h" +#include "Utils.h" +#include "WrappedSemaphore.h" +#ifdef BELL_ONLY_CJSON +#include "cJSON.h" +#else +#include "nlohmann/json.hpp" // for basic_json<>::object_t, basic_json +#include "nlohmann/json_fwd.hpp" // for json +#endif +#include "protobuf/metadata.pb.h" + +using namespace cspot; +namespace TrackDataUtils { +bool countryListContains(char* countryList, const char* country) { + uint16_t countryList_length = strlen(countryList); + for (int x = 0; x < countryList_length; x += 2) { + if (countryList[x] == country[0] && countryList[x + 1] == country[1]) { + return true; + } + } + return false; +} + +bool doRestrictionsApply(Restriction* restrictions, int count, + const char* country) { + for (int x = 0; x < count; x++) { + if (restrictions[x].countries_allowed != nullptr) { + return !countryListContains(restrictions[x].countries_allowed, country); + } + + if (restrictions[x].countries_forbidden != nullptr) { + return countryListContains(restrictions[x].countries_forbidden, country); + } + } + + return false; +} + +bool canPlayTrack(Track& trackInfo, int altIndex, const char* country) { + if (altIndex < 0) { + + } else { + for (int x = 0; x < trackInfo.alternative[altIndex].restriction_count; + x++) { + if (trackInfo.alternative[altIndex].restriction[x].countries_allowed != + nullptr) { + return countryListContains( + trackInfo.alternative[altIndex].restriction[x].countries_allowed, + country); + } + + if (trackInfo.alternative[altIndex].restriction[x].countries_forbidden != + nullptr) { + return !countryListContains( + trackInfo.alternative[altIndex].restriction[x].countries_forbidden, + country); + } + } + } + return true; +} +} // namespace TrackDataUtils + +void TrackInfo::loadPbTrack(Track* pbTrack, const std::vector& gid) { + // Generate ID based on GID + trackId = bytesToHexString(gid); + + name = std::string(pbTrack->name); + + if (pbTrack->artist_count > 0) { + // Handle artist data + artist = std::string(pbTrack->artist[0].name); + } + + if (pbTrack->has_album) { + // Handle album data + album = std::string(pbTrack->album.name); + + if (pbTrack->album.has_cover_group && + pbTrack->album.cover_group.image_count > 0) { + auto imageId = + pbArrayToVector(pbTrack->album.cover_group.image[0].file_id); + imageUrl = "https://i.scdn.co/image/" + bytesToHexString(imageId); + } + } + + duration = pbTrack->duration; +} + +void TrackInfo::loadPbEpisode(Episode* pbEpisode, + const std::vector& gid) { + // Generate ID based on GID + trackId = bytesToHexString(gid); + + name = std::string(pbEpisode->name); + + if (pbEpisode->covers->image_count > 0) { + // Handle episode info + auto imageId = pbArrayToVector(pbEpisode->covers->image[0].file_id); + imageUrl = "https://i.scdn.co/image/" + bytesToHexString(imageId); + } + + duration = pbEpisode->duration; +} + +QueuedTrack::QueuedTrack(TrackReference& ref, + std::shared_ptr ctx, + uint32_t requestedPosition) + : requestedPosition(requestedPosition), ctx(ctx) { + this->ref = ref; + + loadedSemaphore = std::make_shared(); + state = State::QUEUED; +} + +QueuedTrack::~QueuedTrack() { + state = State::FAILED; + loadedSemaphore->give(); + + if (pendingMercuryRequest != 0) { + ctx->session->unregister(pendingMercuryRequest); + } + + if (pendingAudioKeyRequest != 0) { + ctx->session->unregisterAudioKey(pendingAudioKeyRequest); + } +} + +std::shared_ptr QueuedTrack::getAudioFile() { + if (state != State::READY) { + return nullptr; + } + + return std::make_shared(cdnUrl, audioKey); +} + +void QueuedTrack::stepParseMetadata(Track* pbTrack, Episode* pbEpisode) { + int alternativeCount, filesCount = 0; + bool canPlay = false; + AudioFile* selectedFiles = nullptr; + + const char* countryCode = ctx->config.countryCode.c_str(); + + if (ref.type == TrackReference::Type::TRACK) { + CSPOT_LOG(info, "Track name: %s", pbTrack->name); + CSPOT_LOG(info, "Track duration: %d", pbTrack->duration); + + CSPOT_LOG(debug, "trackInfo.restriction.size() = %d", + pbTrack->restriction_count); + + // Check if we can play the track, if not, try alternatives + if (TrackDataUtils::doRestrictionsApply( + pbTrack->restriction, pbTrack->restriction_count, countryCode)) { + // Go through alternatives + for (int x = 0; x < pbTrack->alternative_count; x++) { + if (!TrackDataUtils::doRestrictionsApply( + pbTrack->alternative[x].restriction, + pbTrack->alternative[x].restriction_count, countryCode)) { + selectedFiles = pbTrack->alternative[x].file; + filesCount = pbTrack->alternative[x].file_count; + trackId = pbArrayToVector(pbTrack->alternative[x].gid); + break; + } + } + } else { + // We can play the track + selectedFiles = pbTrack->file; + filesCount = pbTrack->file_count; + trackId = pbArrayToVector(pbTrack->gid); + } + + if (trackId.size() > 0) { + // Load track information + trackInfo.loadPbTrack(pbTrack, trackId); + } + } else { + // Handle episodes + CSPOT_LOG(info, "Episode name: %s", pbEpisode->name); + CSPOT_LOG(info, "Episode duration: %d", pbEpisode->duration); + + CSPOT_LOG(debug, "episodeInfo.restriction.size() = %d", + pbEpisode->restriction_count); + + // Check if we can play the episode + if (!TrackDataUtils::doRestrictionsApply(pbEpisode->restriction, + pbEpisode->restriction_count, + countryCode)) { + selectedFiles = pbEpisode->file; + filesCount = pbEpisode->file_count; + trackId = pbArrayToVector(pbEpisode->gid); + + // Load track information + trackInfo.loadPbEpisode(pbEpisode, trackId); + } + } + + // Find playable file + for (int x = 0; x < filesCount; x++) { + CSPOT_LOG(debug, "File format: %d", selectedFiles[x].format); + if (selectedFiles[x].format == ctx->config.audioFormat) { + fileId = pbArrayToVector(selectedFiles[x].file_id); + break; // If file found stop searching + } + + // Fallback to OGG Vorbis 96kbps + if (fileId.size() == 0 && + selectedFiles[x].format == AudioFormat_OGG_VORBIS_96) { + fileId = pbArrayToVector(selectedFiles[x].file_id); + } + } + + // No viable files found for playback + if (fileId.size() == 0) { + CSPOT_LOG(info, "File not available for playback"); + + // no alternatives for song + state = State::FAILED; + loadedSemaphore->give(); + return; + } + + // Assign track identifier + identifier = bytesToHexString(fileId); + + state = State::KEY_REQUIRED; +} + +void QueuedTrack::stepLoadAudioFile( + std::mutex& trackListMutex, + std::shared_ptr updateSemaphore) { + // Request audio key + this->pendingAudioKeyRequest = ctx->session->requestAudioKey( + trackId, fileId, + [this, &trackListMutex, updateSemaphore]( + bool success, const std::vector& audioKey) { + std::scoped_lock lock(trackListMutex); + + if (success) { + CSPOT_LOG(info, "Got audio key"); + this->audioKey = + std::vector(audioKey.begin() + 4, audioKey.end()); + + state = State::CDN_REQUIRED; + } else { + CSPOT_LOG(error, "Failed to get audio key"); + state = State::FAILED; + loadedSemaphore->give(); + } + updateSemaphore->give(); + }); + + state = State::PENDING_KEY; +} + +void QueuedTrack::stepLoadCDNUrl(const std::string& accessKey) { + if (accessKey.size() == 0) { + // Wait for access key + return; + } + + // Request CDN URL + CSPOT_LOG(info, "Received access key, fetching CDN URL..."); + + try { + + std::string requestUrl = string_format( + "https://api.spotify.com/v1/storage-resolve/files/audio/interactive/" + "%s?alt=json&product=9", + bytesToHexString(fileId).c_str()); + + auto req = bell::HTTPClient::get( + requestUrl, {bell::HTTPClient::ValueHeader( + {"Authorization", "Bearer " + accessKey})}); + + // Wait for response + std::string_view result = req->body(); + +#ifdef BELL_ONLY_CJSON + cJSON* jsonResult = cJSON_Parse(result.data()); + cdnUrl = cJSON_GetArrayItem(cJSON_GetObjectItem(jsonResult, "cdnurl"), 0) + ->valuestring; + cJSON_Delete(jsonResult); +#else + auto jsonResult = nlohmann::json::parse(result); + cdnUrl = jsonResult["cdnurl"][0]; +#endif + + CSPOT_LOG(info, "Received CDN URL, %s", cdnUrl.c_str()); + state = State::READY; + loadedSemaphore->give(); + } catch (...) { + CSPOT_LOG(error, "Cannot fetch CDN URL"); + state = State::FAILED; + loadedSemaphore->give(); + } +} + +void QueuedTrack::expire() { + if (state != State::QUEUED) { + state = State::FAILED; + loadedSemaphore->give(); + } +} + +void QueuedTrack::stepLoadMetadata( + Track* pbTrack, Episode* pbEpisode, std::mutex& trackListMutex, + std::shared_ptr updateSemaphore) { + + // Prepare request ID + std::string requestUrl = string_format( + "hm://metadata/3/%s/%s", + ref.type == TrackReference::Type::TRACK ? "track" : "episode", + bytesToHexString(ref.gid).c_str()); + + auto responseHandler = [this, pbTrack, pbEpisode, &trackListMutex, + updateSemaphore](MercurySession::Response& res) { + std::scoped_lock lock(trackListMutex); + + if (res.parts.size() == 0) { + // Invalid metadata, cannot proceed + state = State::FAILED; + updateSemaphore->give(); + loadedSemaphore->give(); + return; + } + + // Parse the metadata + if (ref.type == TrackReference::Type::TRACK) { + pb_release(Track_fields, pbTrack); + pbDecode(*pbTrack, Track_fields, res.parts[0]); + } else { + pb_release(Episode_fields, pbEpisode); + pbDecode(*pbEpisode, Episode_fields, res.parts[0]); + } + + // Parse received metadata + stepParseMetadata(pbTrack, pbEpisode); + + updateSemaphore->give(); + }; + // Execute the request + pendingMercuryRequest = ctx->session->execute( + MercurySession::RequestType::GET, requestUrl, responseHandler); + + // Set the state to pending + state = State::PENDING_META; +} + +TrackQueue::TrackQueue(std::shared_ptr ctx, + std::shared_ptr state) + : bell::Task("CSpotTrackQueue", 1024 * 32, 2, 1), + playbackState(state), + ctx(ctx) { + accessKeyFetcher = std::make_shared(ctx); + processSemaphore = std::make_shared(); + playableSemaphore = std::make_shared(); + + // Assign encode callback to track list + playbackState->innerFrame.state.track.funcs.encode = + &TrackReference::pbEncodeTrackList; + playbackState->innerFrame.state.track.arg = ¤tTracks; + pbTrack = Track_init_zero; + pbEpisode = Episode_init_zero; + + // Start the task + startTask(); +}; + +TrackQueue::~TrackQueue() { + stopTask(); + + std::scoped_lock lock(tracksMutex); + + pb_release(Track_fields, &pbTrack); + pb_release(Episode_fields, &pbEpisode); +} + +TrackInfo TrackQueue::getTrackInfo(std::string_view identifier) { + for (auto& track : preloadedTracks) { + if (track->identifier == identifier) + return track->trackInfo; + } + return TrackInfo{}; +} + +void TrackQueue::runTask() { + isRunning = true; + + std::scoped_lock lock(runningMutex); + + std::deque> trackQueue; + + while (isRunning) { + processSemaphore->twait(100); + + // Make sure we have the newest access key + accessKey = accessKeyFetcher->getAccessKey(); + + int loadedIndex = currentTracksIndex; + + // No tracks loaded yet + if (loadedIndex < 0) { + continue; + } else { + std::scoped_lock lock(tracksMutex); + + trackQueue = preloadedTracks; + } + + for (auto& track : trackQueue) { + if (track) { + this->processTrack(track); + } + } + } +} + +void TrackQueue::stopTask() { + if (isRunning) { + isRunning = false; + processSemaphore->give(); + std::scoped_lock lock(runningMutex); + } +} + +std::shared_ptr TrackQueue::consumeTrack( + std::shared_ptr prevTrack, int& offset) { + std::scoped_lock lock(tracksMutex); + + if (currentTracksIndex == -1 || currentTracksIndex >= currentTracks.size()) { + return nullptr; + } + + // No previous track, return head + if (prevTrack == nullptr) { + offset = 0; + + return preloadedTracks[0]; + } + + // if (currentTracksIndex + preloadedTracks.size() >= currentTracks.size()) { + // offset = -1; + + // // Last track in queue + // return nullptr; + // } + + auto prevTrackIter = + std::find(preloadedTracks.begin(), preloadedTracks.end(), prevTrack); + + if (prevTrackIter != preloadedTracks.end()) { + // Get offset of next track + offset = prevTrackIter - preloadedTracks.begin() + 1; + } else { + offset = 0; + } + + if (offset >= preloadedTracks.size()) { + // Last track in preloaded queue + return nullptr; + } + + // Return the current track + return preloadedTracks[offset]; +} + +void TrackQueue::processTrack(std::shared_ptr track) { + switch (track->state) { + case QueuedTrack::State::QUEUED: + track->stepLoadMetadata(&pbTrack, &pbEpisode, tracksMutex, + processSemaphore); + break; + case QueuedTrack::State::KEY_REQUIRED: + track->stepLoadAudioFile(tracksMutex, processSemaphore); + break; + case QueuedTrack::State::CDN_REQUIRED: + track->stepLoadCDNUrl(accessKey); + + if (track->state == QueuedTrack::State::READY) { + if (preloadedTracks.size() < MAX_TRACKS_PRELOAD) { + // Queue a new track to preload + queueNextTrack(preloadedTracks.size()); + } + } + break; + default: + // Do not perform any action + break; + } +} + +bool TrackQueue::queueNextTrack(int offset, uint32_t positionMs) { + const int requestedRefIndex = offset + currentTracksIndex; + if (requestedRefIndex < 0 || requestedRefIndex >= currentTracks.size()) { + return false; + } + + if (offset < 0) { + preloadedTracks.push_front(std::make_shared( + currentTracks[requestedRefIndex], ctx, positionMs)); + } else { + preloadedTracks.push_back(std::make_shared( + currentTracks[requestedRefIndex], ctx, positionMs)); + } + + return true; +} + +bool TrackQueue::skipTrack(SkipDirection dir, bool expectNotify) { + bool canSkipNext = currentTracks.size() > currentTracksIndex + 1; + bool canSkipPrev = currentTracksIndex > 0; + + if ((dir == SkipDirection::NEXT && canSkipNext) || + (dir == SkipDirection::PREV && canSkipPrev)) { + std::scoped_lock lock(tracksMutex); + if (dir == SkipDirection::NEXT) { + preloadedTracks.pop_front(); + + if (!queueNextTrack(preloadedTracks.size() + 1)) { + CSPOT_LOG(info, "Failed to queue next track"); + } + + currentTracksIndex++; + } else { + queueNextTrack(-1); + + if (preloadedTracks.size() > MAX_TRACKS_PRELOAD) { + preloadedTracks.pop_back(); + } + + currentTracksIndex--; + } + + // Update frame data + playbackState->innerFrame.state.playing_track_index = currentTracksIndex; + + if (expectNotify) { + // Reset position to zero + notifyPending = true; + } + + return true; + } + + return false; +} + +bool TrackQueue::hasTracks() { + std::scoped_lock lock(tracksMutex); + + return currentTracks.size() > 0; +} + +bool TrackQueue::isFinished() { + std::scoped_lock lock(tracksMutex); + return currentTracksIndex >= currentTracks.size() - 1; +} + +void TrackQueue::updateTracks(uint32_t requestedPosition, bool initial) { + std::scoped_lock lock(tracksMutex); + + if (initial) { + // Clear preloaded tracks + preloadedTracks.clear(); + + // Copy requested track list + currentTracks = playbackState->remoteTracks; + + currentTracksIndex = playbackState->innerFrame.state.playing_track_index; + + if (currentTracksIndex < currentTracks.size()) { + // Push a song on the preloaded queue + queueNextTrack(0, requestedPosition); + } + + // We already updated track meta, mark it + notifyPending = true; + + playableSemaphore->give(); + } else { + // Clear preloaded tracks + preloadedTracks.clear(); + + // Copy requested track list + currentTracks = playbackState->remoteTracks; + + // Push a song on the preloaded queue + queueNextTrack(0, requestedPosition); + } +} diff --git a/components/spotify/cspot/src/TrackReference.cpp b/components/spotify/cspot/src/TrackReference.cpp new file mode 100644 index 00000000..4c399dba --- /dev/null +++ b/components/spotify/cspot/src/TrackReference.cpp @@ -0,0 +1,156 @@ +#include "TrackReference.h" + +#include "NanoPBExtensions.h" +#include "Utils.h" +#include "protobuf/spirc.pb.h" + +using namespace cspot; + +static constexpr auto base62Alphabet = + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +TrackReference::TrackReference() : type(Type::TRACK) {} + +void TrackReference::decodeURI() { + if (gid.size() == 0) { + // Episode GID is being fetched via base62 encoded URI + auto idString = uri.substr(uri.find_last_of(":") + 1, uri.size()); + gid = {0}; + + std::string_view alphabet(base62Alphabet); + for (int x = 0; x < idString.size(); x++) { + size_t d = alphabet.find(idString[x]); + gid = bigNumMultiply(gid, 62); + gid = bigNumAdd(gid, d); + } + +#if __cplusplus >= 202002L + if (uri.starts_with("episode")) { +#else + if (uri.find("episode") == 0) { +#endif + type = Type::EPISODE; + } + } +} + +bool TrackReference::operator==(const TrackReference& other) const { + return other.gid == gid && other.uri == uri; +} + +bool TrackReference::pbEncodeTrackList(pb_ostream_t* stream, + const pb_field_t* field, + void* const* arg) { + auto trackQueue = *static_cast*>(*arg); + static TrackRef msg = TrackRef_init_zero; + + // Prepare nanopb callbacks + msg.context.funcs.encode = &bell::nanopb::encodeString; + msg.uri.funcs.encode = &bell::nanopb::encodeString; + msg.gid.funcs.encode = &bell::nanopb::encodeVector; + msg.queued.funcs.encode = &bell::nanopb::encodeBoolean; + + for (auto trackRef : trackQueue) { + if (!pb_encode_tag_for_field(stream, field)) { + return false; + } + + msg.gid.arg = &trackRef.gid; + msg.uri.arg = &trackRef.uri; + msg.context.arg = &trackRef.context; + msg.queued.arg = &trackRef.queued; + + if (!pb_encode_submessage(stream, TrackRef_fields, &msg)) { + return false; + } + } + + return true; +} + +bool TrackReference::pbDecodeTrackList(pb_istream_t* stream, + const pb_field_t* field, void** arg) { + auto trackQueue = static_cast*>(*arg); + + // Push a new reference + trackQueue->push_back(TrackReference()); + + auto& track = trackQueue->back(); + + bool eof = false; + pb_wire_type_t wire_type; + pb_istream_t substream; + uint32_t tag; + + while (!eof) { + if (!pb_decode_tag(stream, &wire_type, &tag, &eof)) { + // Decoding failed and not eof + if (!eof) { + return false; + } + // EOF + } else { + switch (tag) { + case TrackRef_uri_tag: + case TrackRef_context_tag: + case TrackRef_gid_tag: { + // Make substream + if (!pb_make_string_substream(stream, &substream)) { + + return false; + } + + uint8_t* destBuffer = nullptr; + + // Handle GID + if (tag == TrackRef_gid_tag) { + track.gid.resize(substream.bytes_left); + destBuffer = &track.gid[0]; + } else if (tag == TrackRef_context_tag) { + track.context.resize(substream.bytes_left); + + destBuffer = reinterpret_cast(&track.context[0]); + } else if (tag == TrackRef_uri_tag) { + track.uri.resize(substream.bytes_left); + + destBuffer = reinterpret_cast(&track.uri[0]); + } + + if (!pb_read(&substream, destBuffer, substream.bytes_left)) { + return false; + } + + // Close substream + if (!pb_close_string_substream(stream, &substream)) { + return false; + } + + break; + } + case TrackRef_queued_tag: { + uint32_t queuedValue; + + // Decode boolean + if (!pb_decode_varint32(stream, &queuedValue)) { + return false; + } + + // Cast down to bool + track.queued = (bool)queuedValue; + + break; + } + default: + // Field not known, skip + pb_skip_field(stream, wire_type); + + break; + } + } + } + + // Fill in GID when only URI is provided + track.decodeURI(); + + return true; +} From 2f9b506e9bc79b58eb914f39bed5c9790d424955 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Sun, 7 May 2023 00:28:17 +0200 Subject: [PATCH 07/29] include typos --- components/spotify/cspot/src/CDNAudioFile.cpp | 2 +- components/spotify/cspot/src/LoginBlob.cpp | 2 +- components/spotify/cspot/src/Utils.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/spotify/cspot/src/CDNAudioFile.cpp b/components/spotify/cspot/src/CDNAudioFile.cpp index f45c3237..2fbd0742 100644 --- a/components/spotify/cspot/src/CDNAudioFile.cpp +++ b/components/spotify/cspot/src/CDNAudioFile.cpp @@ -16,7 +16,7 @@ #include "Utils.h" // for bigNumAdd, bytesToHexString, string... #include "WrappedSemaphore.h" // for WrappedSemaphore #ifdef BELL_ONLY_CJSON -#include "cJSON.h " +#include "cJSON.h" #else #include "nlohmann/json.hpp" // for basic_json<>::object_t, basic_json #include "nlohmann/json_fwd.hpp" // for json diff --git a/components/spotify/cspot/src/LoginBlob.cpp b/components/spotify/cspot/src/LoginBlob.cpp index fb2cf919..feb5e8d2 100644 --- a/components/spotify/cspot/src/LoginBlob.cpp +++ b/components/spotify/cspot/src/LoginBlob.cpp @@ -8,7 +8,7 @@ #include "Logger.h" // for CSPOT_LOG #include "protobuf/authentication.pb.h" // for AuthenticationType_AUTHE... #ifdef BELL_ONLY_CJSON -#include "cJSON.h " +#include "cJSON.h" #else #include "nlohmann/detail/json_pointer.hpp" // for json_pointer<>::string_t #include "nlohmann/json.hpp" // for basic_json<>::object_t, basic_json diff --git a/components/spotify/cspot/src/Utils.cpp b/components/spotify/cspot/src/Utils.cpp index 329f358f..6983e3ae 100644 --- a/components/spotify/cspot/src/Utils.cpp +++ b/components/spotify/cspot/src/Utils.cpp @@ -8,7 +8,7 @@ #include // for enable_if<>::type #include #ifndef _WIN32 -#include +#include #endif unsigned long long getCurrentTimestamp() { From fa91879535a1ca8ad4e436c706a5b3aece5f394a Mon Sep 17 00:00:00 2001 From: philippe44 Date: Sun, 7 May 2023 11:50:46 +0200 Subject: [PATCH 08/29] more missing stuff --- .../bell/main/utilities/NanoPBExtensions.cpp | 59 +++++++++++++++++++ .../main/utilities/include/NanoPBExtensions.h | 15 +++++ 2 files changed, 74 insertions(+) create mode 100644 components/spotify/cspot/bell/main/utilities/NanoPBExtensions.cpp create mode 100644 components/spotify/cspot/bell/main/utilities/include/NanoPBExtensions.h diff --git a/components/spotify/cspot/bell/main/utilities/NanoPBExtensions.cpp b/components/spotify/cspot/bell/main/utilities/NanoPBExtensions.cpp new file mode 100644 index 00000000..222a8699 --- /dev/null +++ b/components/spotify/cspot/bell/main/utilities/NanoPBExtensions.cpp @@ -0,0 +1,59 @@ +#include "NanoPBExtensions.h" + +#include // for optional +#include // for string +#include // for vector + +#include +#include + +bool bell::nanopb::encodeString(pb_ostream_t* stream, const pb_field_t* field, + void* const* arg) { + auto& str = *static_cast(*arg); + + if (str.size() > 0) { + if (!pb_encode_tag_for_field(stream, field)) { + return false; + } + + if (!pb_encode_string(stream, (uint8_t*)str.c_str(), str.size())) { + return false; + } + } + + return true; +} + +bool bell::nanopb::encodeBoolean(pb_ostream_t* stream, const pb_field_t* field, + void* const* arg) { + auto& boolean = *static_cast*>(*arg); + + if (boolean.has_value()) { + if (!pb_encode_tag_for_field(stream, field)) { + return false; + } + + if (!pb_encode_varint(stream, boolean.value())) { + return false; + } + } + + return true; +} + +bool bell::nanopb::encodeVector(pb_ostream_t* stream, const pb_field_t* field, + void* const* arg) { + auto& vector = *static_cast*>(*arg); + + if (vector.size() > 0) { + if (!pb_encode_tag_for_field(stream, field)) { + return false; + } + + if (!pb_encode_string(stream, vector.data(), vector.size())) { + return false; + } + } + + return true; +} \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/utilities/include/NanoPBExtensions.h b/components/spotify/cspot/bell/main/utilities/include/NanoPBExtensions.h new file mode 100644 index 00000000..0672eb7f --- /dev/null +++ b/components/spotify/cspot/bell/main/utilities/include/NanoPBExtensions.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +/// Set of helper methods that simplify nanopb usage in C++. +namespace bell::nanopb { +bool encodeString(pb_ostream_t* stream, const pb_field_t* field, + void* const* arg); + +bool encodeVector(pb_ostream_t* stream, const pb_field_t* field, + void* const* arg); + +bool encodeBoolean(pb_ostream_t* stream, const pb_field_t* field, + void* const* arg); +} // namespace bell::nanopb \ No newline at end of file From bc4e56eabce8d33c52a6e60fab80a8941bcd8d98 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Wed, 10 May 2023 12:26:43 +0200 Subject: [PATCH 09/29] unify Host and Embedded versions --- components/spotify/CMakeLists.txt | 1 + components/spotify/cspot/bell/CMakeLists.txt | 16 ++++++++++------ .../spotify/cspot/bell/main/io/BellTar.cpp | 4 ++++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/components/spotify/CMakeLists.txt b/components/spotify/CMakeLists.txt index 63c65cd4..9b78f37a 100644 --- a/components/spotify/CMakeLists.txt +++ b/components/spotify/CMakeLists.txt @@ -18,6 +18,7 @@ set(BELL_DISABLE_FMT ON) set(BELL_DISABLE_REGEX ON) set(BELL_ONLY_CJSON ON) set(BELL_DISABLE_MQTT ON) +set(BELL_DISABLE_WEBSERVER ON) set(CSPOT_TARGET_ESP32 ON) # because CMake is so broken, the cache set below overrides a normal "set" for the first build diff --git a/components/spotify/cspot/bell/CMakeLists.txt b/components/spotify/cspot/bell/CMakeLists.txt index d2aaab08..f2244bf5 100644 --- a/components/spotify/cspot/bell/CMakeLists.txt +++ b/components/spotify/cspot/bell/CMakeLists.txt @@ -8,6 +8,7 @@ option(BELL_DISABLE_CODECS "Disable the entire audio codec wrapper" OFF) option(BELL_CODEC_AAC "Support libhelix-aac codec" ON) option(BELL_CODEC_MP3 "Support libhelix-mp3 codec" ON) option(BELL_DISABLE_MQTT "Disable the built-in MQTT wrapper" OFF) +option(BELL_DISABLE_WEBSERVER "Disable the built-in Web server" OFF) option(BELL_CODEC_VORBIS "Support tremor Vorbis codec" ON) option(BELL_CODEC_ALAC "Support Apple ALAC codec" ON) option(BELL_CODEC_OPUS "Support Opus codec" ON) @@ -66,6 +67,7 @@ message(STATUS " Use cJSON only: ${BELL_ONLY_CJSON}") message(STATUS " Disable Fmt: ${BELL_DISABLE_FMT}") message(STATUS " Disable Mqtt: ${BELL_DISABLE_MQTT}") message(STATUS " Disable Regex: ${BELL_DISABLE_REGEX}") +message(STATUS " Disable Web server: ${BELL_DISABLE_WEBSERVER}") # Include nanoPB library set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/external/nanopb/extra") @@ -95,8 +97,6 @@ file(GLOB SOURCES "main/io/*.cpp" "main/io/*.c" ) -list(REMOVE_ITEM SOURCES "${IO_DIR}/BellTar.cpp" "${IO_DIR}/BellHTTPServer.cpp") - list(APPEND EXTRA_INCLUDES "main/audio-codec/include") list(APPEND EXTRA_INCLUDES "main/audio-dsp/include") list(APPEND EXTRA_INCLUDES "main/audio-sinks/include") @@ -276,6 +276,7 @@ if(NOT BELL_DISABLE_SINKS) endif() if(NOT BELL_ONLY_CJSON) + set(JSON_SystemInclude ON CACHE INTERNAL "") add_subdirectory(external/nlohmann_json) list(APPEND EXTRA_LIBS nlohmann_json::nlohmann_json) endif() @@ -296,10 +297,13 @@ if(WIN32 OR UNIX) list(APPEND EXTERNAL_INCLUDES "external/mdnssvc") endif() -# file(GLOB CIVET_SRC "external/civetweb/*.c" "external/civetweb/*.inl" "external/civetweb/*.cpp") - -# list(APPEND SOURCES ${CIVET_SRC}) -# list(APPEND EXTRA_INCLUDES "external/civetweb/include") +if(NOT BELL_DISABLE_WEBSERVER) + file(GLOB CIVET_SRC "external/civetweb/*.c" "external/civetweb/*.inl" "external/civetweb/*.cpp") + list(APPEND SOURCES ${CIVET_SRC}) + list(APPEND EXTRA_INCLUDES "external/civetweb/include") +else() + list(REMOVE_ITEM SOURCES "${IO_DIR}/BellHTTPServer.cpp") +endif() add_library(bell STATIC ${SOURCES}) diff --git a/components/spotify/cspot/bell/main/io/BellTar.cpp b/components/spotify/cspot/bell/main/io/BellTar.cpp index 4ab0237a..61b41039 100644 --- a/components/spotify/cspot/bell/main/io/BellTar.cpp +++ b/components/spotify/cspot/bell/main/io/BellTar.cpp @@ -288,7 +288,11 @@ void reader::extract_all_files(std::string dest_directory) { auto fileName = get_next_file_name(); // 0 is the normal file type, skip apple's ._ files +#if __cplusplus >= 202002L if (fileType == '0' && !fileName.starts_with("._")) { +#else + if (fileType == '0' && fileName.find("._") != 0) { +#endif std::string path = dest_directory + "/" + fileName; size_t pos = 0; From 93a2c0969c5b1ca8febe304fe7946ab7ab67501e Mon Sep 17 00:00:00 2001 From: philippe44 Date: Wed, 10 May 2023 16:45:50 +0200 Subject: [PATCH 10/29] synchronizing with host versions --- .../bell/external/nanopb/extra/FindNanopb.cmake | 13 ++++++++++--- .../spotify/cspot/protobuf/authentication.proto | 2 ++ components/spotify/cspot/protobuf/keyexchange.proto | 2 ++ components/spotify/cspot/protobuf/mercury.proto | 2 ++ components/spotify/cspot/protobuf/spirc.proto | 2 ++ 5 files changed, 18 insertions(+), 3 deletions(-) diff --git a/components/spotify/cspot/bell/external/nanopb/extra/FindNanopb.cmake b/components/spotify/cspot/bell/external/nanopb/extra/FindNanopb.cmake index 3f82b7b8..84016b31 100644 --- a/components/spotify/cspot/bell/external/nanopb/extra/FindNanopb.cmake +++ b/components/spotify/cspot/bell/external/nanopb/extra/FindNanopb.cmake @@ -118,12 +118,15 @@ # #============================================================================= - function(NANOPB_GENERATE_CPP SRCS HDRS) cmake_parse_arguments(NANOPB_GENERATE_CPP "" "RELPATH" "" ${ARGN}) if(NOT NANOPB_GENERATE_CPP_UNPARSED_ARGUMENTS) return() endif() + + if(MSVC) + set(CALL_PREFIX call) + endif() if(NANOPB_GENERATE_CPP_APPEND_PATH) # Create an include path for each file specified @@ -184,7 +187,7 @@ function(NANOPB_GENERATE_CPP SRCS HDRS) set(GENERATOR_CORE_PYTHON_SRC ${GENERATOR_CORE_PYTHON_SRC} ${output}) add_custom_command( OUTPUT ${output} - COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} + COMMAND ${CALL_PREFIX} ${PROTOBUF_PROTOC_EXECUTABLE} ARGS -I${GENERATOR_PATH}/proto --python_out=${GENERATOR_CORE_DIR} ${ABS_FIL} DEPENDS ${ABS_FIL} @@ -276,7 +279,7 @@ function(NANOPB_GENERATE_CPP SRCS HDRS) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${FIL_PATH_REL}/${FIL_WE}.pb.c" "${CMAKE_CURRENT_BINARY_DIR}/${FIL_PATH_REL}/${FIL_WE}.pb.h" - COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} + COMMAND ${CALL_PREFIX} ${PROTOBUF_PROTOC_EXECUTABLE} ARGS -I${GENERATOR_PATH} -I${GENERATOR_CORE_DIR} -I${CMAKE_CURRENT_BINARY_DIR} ${_nanopb_include_path} --plugin=protoc-gen-nanopb=${NANOPB_GENERATOR_PLUGIN} @@ -292,6 +295,10 @@ function(NANOPB_GENERATE_CPP SRCS HDRS) set_source_files_properties(${${SRCS}} ${${HDRS}} PROPERTIES GENERATED TRUE) set(${SRCS} ${${SRCS}} ${NANOPB_SRCS} PARENT_SCOPE) set(${HDRS} ${${HDRS}} ${NANOPB_HDRS} PARENT_SCOPE) + + if(MSVC) + unset(CALL_PREFIX) + endif() endfunction() diff --git a/components/spotify/cspot/protobuf/authentication.proto b/components/spotify/cspot/protobuf/authentication.proto index d3896147..543331a4 100644 --- a/components/spotify/cspot/protobuf/authentication.proto +++ b/components/spotify/cspot/protobuf/authentication.proto @@ -1,3 +1,5 @@ +syntax = "proto2"; + enum CpuFamily { CPU_UNKNOWN = 0x0; CPU_X86 = 0x1; diff --git a/components/spotify/cspot/protobuf/keyexchange.proto b/components/spotify/cspot/protobuf/keyexchange.proto index a816d1ee..55cd802a 100644 --- a/components/spotify/cspot/protobuf/keyexchange.proto +++ b/components/spotify/cspot/protobuf/keyexchange.proto @@ -1,3 +1,5 @@ +syntax = "proto2"; + message LoginCryptoDiffieHellmanChallenge { required bytes gs = 0xa; } diff --git a/components/spotify/cspot/protobuf/mercury.proto b/components/spotify/cspot/protobuf/mercury.proto index 72138948..60c752aa 100644 --- a/components/spotify/cspot/protobuf/mercury.proto +++ b/components/spotify/cspot/protobuf/mercury.proto @@ -1,3 +1,5 @@ +syntax = "proto2"; + message Header { optional string uri = 0x01; optional string method = 0x03; diff --git a/components/spotify/cspot/protobuf/spirc.proto b/components/spotify/cspot/protobuf/spirc.proto index 5d54bd26..3b5448f9 100644 --- a/components/spotify/cspot/protobuf/spirc.proto +++ b/components/spotify/cspot/protobuf/spirc.proto @@ -1,3 +1,5 @@ +syntax = "proto2"; + enum MessageType { kMessageTypeHello = 0x1; kMessageTypeGoodbye = 0x2; From ef692b1b50801970e00ee9cd1fc62f2170d92f2f Mon Sep 17 00:00:00 2001 From: philippe44 Date: Fri, 12 May 2023 21:16:20 +0200 Subject: [PATCH 11/29] more aggressive handling of Spotify loudness war... - release --- .../cspot/bell/external/nanopb/extra/FindNanopb.cmake | 8 ++++---- components/squeezelite/decode_external.c | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/spotify/cspot/bell/external/nanopb/extra/FindNanopb.cmake b/components/spotify/cspot/bell/external/nanopb/extra/FindNanopb.cmake index 84016b31..d8373926 100644 --- a/components/spotify/cspot/bell/external/nanopb/extra/FindNanopb.cmake +++ b/components/spotify/cspot/bell/external/nanopb/extra/FindNanopb.cmake @@ -125,7 +125,7 @@ function(NANOPB_GENERATE_CPP SRCS HDRS) endif() if(MSVC) - set(CALL_PREFIX call) + set(CUSTOM_COMMAND_PREFIX call) endif() if(NANOPB_GENERATE_CPP_APPEND_PATH) @@ -187,7 +187,7 @@ function(NANOPB_GENERATE_CPP SRCS HDRS) set(GENERATOR_CORE_PYTHON_SRC ${GENERATOR_CORE_PYTHON_SRC} ${output}) add_custom_command( OUTPUT ${output} - COMMAND ${CALL_PREFIX} ${PROTOBUF_PROTOC_EXECUTABLE} + COMMAND ${CUSTOM_COMMAND_PREFIX} ${PROTOBUF_PROTOC_EXECUTABLE} ARGS -I${GENERATOR_PATH}/proto --python_out=${GENERATOR_CORE_DIR} ${ABS_FIL} DEPENDS ${ABS_FIL} @@ -279,7 +279,7 @@ function(NANOPB_GENERATE_CPP SRCS HDRS) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${FIL_PATH_REL}/${FIL_WE}.pb.c" "${CMAKE_CURRENT_BINARY_DIR}/${FIL_PATH_REL}/${FIL_WE}.pb.h" - COMMAND ${CALL_PREFIX} ${PROTOBUF_PROTOC_EXECUTABLE} + COMMAND ${CUSTOM_COMMAND_PREFIX} ${PROTOBUF_PROTOC_EXECUTABLE} ARGS -I${GENERATOR_PATH} -I${GENERATOR_CORE_DIR} -I${CMAKE_CURRENT_BINARY_DIR} ${_nanopb_include_path} --plugin=protoc-gen-nanopb=${NANOPB_GENERATOR_PLUGIN} @@ -297,7 +297,7 @@ function(NANOPB_GENERATE_CPP SRCS HDRS) set(${HDRS} ${${HDRS}} ${NANOPB_HDRS} PARENT_SCOPE) if(MSVC) - unset(CALL_PREFIX) + unset(CUSTOM_COMMAND_PREFIX) endif() endfunction() diff --git a/components/squeezelite/decode_external.c b/components/squeezelite/decode_external.c index 71536347..d7437526 100644 --- a/components/squeezelite/decode_external.c +++ b/components/squeezelite/decode_external.c @@ -406,7 +406,7 @@ static bool cspot_cmd_handler(cspot_event_t cmd, va_list args) case CSPOT_VOLUME: { u32_t volume = va_arg(args, u32_t); LOG_INFO("CSpot volume %u", volume); - volume = 65536 * powf(volume / 65536.0f, 2); + volume = 65536 * powf(volume / 65536.0f, 4); set_volume(volume, volume); break; default: From e4ecb842aa6008d69644f5c518257308df9ae50f Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 12 May 2023 19:19:32 +0000 Subject: [PATCH 12/29] Update prebuilt objects [skip actions] --- ...c.bundle.d.ts => index.997af2.bundle.d.ts} | 0 ...e.d.ts => node_vendors.997af2.bundle.d.ts} | 0 .../wifi-manager/webapp/dist/index.html | 2 +- .../wifi-manager/webapp/dist/index.html.gz | Bin 9954 -> 9952 bytes ...97af2.bundle.js => index.1e1c60.bundle.js} | 2 +- ...bundle.js.gz => index.1e1c60.bundle.js.gz} | Bin 15572 -> 15571 bytes ...ndle.js.map => index.1e1c60.bundle.js.map} | 2 +- ...undle.js => node_vendors.1e1c60.bundle.js} | 4 +- .../dist/js/node_vendors.1e1c60.bundle.js.gz | Bin 0 -> 88945 bytes .../dist/js/node_vendors.1e1c60.bundle.js.map | 1 + .../dist/js/node_vendors.997af2.bundle.js.gz | Bin 89708 -> 0 bytes .../dist/js/node_vendors.997af2.bundle.js.map | 1 - .../wifi-manager/webapp/dist/src/js/test.d.ts | 3 + components/wifi-manager/webapp/webapp.cmake | 4 +- components/wifi-manager/webapp/webpack.c | 20 +++--- components/wifi-manager/webapp/webpack.h | 2 +- server_certs/DigiCertGlobalRootCA.crt.36 | Bin 0 -> 947 bytes server_certs/github.pem | 62 +++++++++--------- server_certs/r2m01.cer.8 | Bin 0 -> 1122 bytes server_certs/s3-amazon-com.pem | 62 +++++++++--------- 20 files changed, 86 insertions(+), 79 deletions(-) rename components/wifi-manager/webapp/dist/dist/js/{index.1a3b6c.bundle.d.ts => index.997af2.bundle.d.ts} (100%) rename components/wifi-manager/webapp/dist/dist/js/{node_vendors.1a3b6c.bundle.d.ts => node_vendors.997af2.bundle.d.ts} (100%) rename components/wifi-manager/webapp/dist/js/{index.997af2.bundle.js => index.1e1c60.bundle.js} (99%) rename components/wifi-manager/webapp/dist/js/{index.997af2.bundle.js.gz => index.1e1c60.bundle.js.gz} (99%) rename components/wifi-manager/webapp/dist/js/{index.997af2.bundle.js.map => index.1e1c60.bundle.js.map} (99%) rename components/wifi-manager/webapp/dist/js/{node_vendors.997af2.bundle.js => node_vendors.1e1c60.bundle.js} (57%) create mode 100644 components/wifi-manager/webapp/dist/js/node_vendors.1e1c60.bundle.js.gz create mode 100644 components/wifi-manager/webapp/dist/js/node_vendors.1e1c60.bundle.js.map delete mode 100644 components/wifi-manager/webapp/dist/js/node_vendors.997af2.bundle.js.gz delete mode 100644 components/wifi-manager/webapp/dist/js/node_vendors.997af2.bundle.js.map create mode 100644 server_certs/DigiCertGlobalRootCA.crt.36 create mode 100644 server_certs/r2m01.cer.8 diff --git a/components/wifi-manager/webapp/dist/dist/js/index.1a3b6c.bundle.d.ts b/components/wifi-manager/webapp/dist/dist/js/index.997af2.bundle.d.ts similarity index 100% rename from components/wifi-manager/webapp/dist/dist/js/index.1a3b6c.bundle.d.ts rename to components/wifi-manager/webapp/dist/dist/js/index.997af2.bundle.d.ts diff --git a/components/wifi-manager/webapp/dist/dist/js/node_vendors.1a3b6c.bundle.d.ts b/components/wifi-manager/webapp/dist/dist/js/node_vendors.997af2.bundle.d.ts similarity index 100% rename from components/wifi-manager/webapp/dist/dist/js/node_vendors.1a3b6c.bundle.d.ts rename to components/wifi-manager/webapp/dist/dist/js/node_vendors.997af2.bundle.d.ts diff --git a/components/wifi-manager/webapp/dist/index.html b/components/wifi-manager/webapp/dist/index.html index 2d912b86..4de342f0 100644 --- a/components/wifi-manager/webapp/dist/index.html +++ b/components/wifi-manager/webapp/dist/index.html @@ -1 +1 @@ -
Software Updates
VersionDate/TimePlatformBranchBit Depth
Local Firmware Upload
KeyValue
Usage Templates

Supported: flac,pcm,mp3,ogg (mad,mpg for specific mp3 codec)
Close output device after timeout seconds, default is to keep it open while player is 'on'
Logs: all|slimproto|stream|decode|output, level: info|debug|sdebug
Supported: flac,pcm,mp3,ogg (mad,mpg for specific mp3 codec)
Format: ab:cd:ef:12:34:56
<maxrate>|<minrate><maxrate><rate1><rate2><rate3>

WiFi Status
Logs
TimestampMessage
Tasks
#Task NameCPUStateMin StackBase PriorityCur Priority
Credits

squeezelite-esp32
© 2020, philippe44, sle118, daduke
This software is released under the MIT License.

This app would not be possible without the following libraries:

  • squeezelite, © 2012-2019, Adrian Smith and Ralph Irving. Licensed under the GPL License.
  • esp32-wifi-manager, © 2017-2019, Tony Pottier. Licensed under the MIT License.
  • SpinKit, © 2015, Tobias Ahlin. Licensed under the MIT License.
  • jQuery, The jQuery Foundation. Licensed under the MIT License.
  • cJSON, © 2009-2017, Dave Gamble and cJSON contributors. Licensed under the MIT License.
  • esp32-rotary-encoder, © 2011-2019, David Antliff and Ben Buxton. Licensed under the GPL License.
  • tarablessd1306, © 2017-2018, Tara Keeling. Licensed under the MIT license.
  • CSpot, © 2020 feelfreelinux & alufers. Licensed under the GPL License
Extras/Overrides
\ No newline at end of file +
Software Updates
VersionDate/TimePlatformBranchBit Depth
Local Firmware Upload
KeyValue
Usage Templates

Supported: flac,pcm,mp3,ogg (mad,mpg for specific mp3 codec)
Close output device after timeout seconds, default is to keep it open while player is 'on'
Logs: all|slimproto|stream|decode|output, level: info|debug|sdebug
Supported: flac,pcm,mp3,ogg (mad,mpg for specific mp3 codec)
Format: ab:cd:ef:12:34:56
<maxrate>|<minrate><maxrate><rate1><rate2><rate3>

WiFi Status
Logs
TimestampMessage
Tasks
#Task NameCPUStateMin StackBase PriorityCur Priority
Credits

squeezelite-esp32
© 2020, philippe44, sle118, daduke
This software is released under the MIT License.

This app would not be possible without the following libraries:

  • squeezelite, © 2012-2019, Adrian Smith and Ralph Irving. Licensed under the GPL License.
  • esp32-wifi-manager, © 2017-2019, Tony Pottier. Licensed under the MIT License.
  • SpinKit, © 2015, Tobias Ahlin. Licensed under the MIT License.
  • jQuery, The jQuery Foundation. Licensed under the MIT License.
  • cJSON, © 2009-2017, Dave Gamble and cJSON contributors. Licensed under the MIT License.
  • esp32-rotary-encoder, © 2011-2019, David Antliff and Ben Buxton. Licensed under the GPL License.
  • tarablessd1306, © 2017-2018, Tara Keeling. Licensed under the MIT license.
  • CSpot, © 2020 feelfreelinux & alufers. Licensed under the GPL License
Extras/Overrides
\ No newline at end of file diff --git a/components/wifi-manager/webapp/dist/index.html.gz b/components/wifi-manager/webapp/dist/index.html.gz index 1a4417447430d569f4a2a45b86e7dbc345828eb1..3b45afb4acae73c50c7fb1b6bd37f4cb07d9675d 100644 GIT binary patch delta 4492 zcmeI!c|X$u0|4+1P0YGhl$((b3C%>Suq5WrRT#N)jVH&*#?PF6$Te5eQb;XHLaw2x zT#cAv=9pCO97Bq&=M6kB;Q7P%ReZl$a4j-rh0H108E_x4M)oFrNP>A@ei>-r_jx*g zcAyiIxMS+5VvvBn^PY(%+0~NYB)QkflnC9w(BQ+dI2zw zZC`-zJ$&oTs0;itxjx@Ir-!GbTKq1%bEb_{_*G_+%WL?v*&O}T@6XuXw2Up{7-=N< zk}S6!HJ}2O58vT)#2ey%S7|q^5#$cw;OJV#JdEv2#o42$iU5uAq;g0wBRC!X-g5+2 z&9>hb{^px&H-#Q=+7yN>tn8$o zP++qsMJQAk?bceLW#ZB_Eu_=vmmwImSFCPqy9o;{GSk*BykF#_OD&P3_+NJytBby} z=lTzDG+0yC40)}eYG3zkurnb!vEVL`~6ksQygfGqh=SID^rx-(1VrFuFq}&nCJI72qPYrog5+I@2J9xMu-QALk`e z7uP+2C}rkg5Ue;c8uti?9!u0sEVpF%S6Fw95(EFTbtlhfVjR&WL9MWLNzSlG%stMRh>YekKTKVQ5CFlcX zX)ePGX7c7YmFD1MYg-IFX?W+Npe%xs$|EGAVocKUsM9ZT?QxS;^&hBVYS@Ap;Fe}= z86n_a$pBma&A!=$w(JdKpAY&&s<@$By2p<9*7uu#ym!SVfI^m*PHmPhTt;FpzN#C^ zm(4jFZv#rUDWA||H4@hd48a9j$Mv9q+LZ2dPl|lATO?Q1CTPJG1rMZtE)G)n#MH#M zr_p?S#c|&FOC32SfzVHTJm-U60#hENgLtiWVSj5^UE+6R_V|sl6Uq4E_=GaOBl&Nv z5$j|vwaU8DWAlnTGn>A{3;wrXu$DHf%=*=xjmmi*h7pl>;?6oYMF11(S%P%uhCYXD z@F7Rxxj|*Zz!`fO>rhR_olAQmzaDN@^sVgdiuQ^oQ(C)XssmFvAdGOWMAtM!uu+Wi;!OSPm7>>g;eT3)5XEG#YOs>EMs3J zVbLu4VecAR>Wd@@}4 zdi1*4%kGdVw$rO*zn`DLjnf{V5d{cRs41*mFPAgTMKN9E?01vBr~?tGflU6?acIxXrqQ(<3BXOqjcMdLu~HrU@yMajbn5DTQ>mQ^cD*FT^Lm&9@UB{jTFoLc?)x(B+} zY*24q9bzAd1^_+at80!k21}O}lFs!Sm#Q>anx)@O>Vw@b_atqwYYzlIJEdC1>hDl~ z^s1MAOLPLIb~CwjJ}7SWb9kqvU5n-H3|h5A8W#F(ty#fT&|5&jX8pB}{Kx;pmRZxc z5-N*=ixuO|htSglVP*5ZZKtmg7)l}E;|f`&{0#|V0O#!He?N5yyC!rx8u=%BC|fvF zCTG0sk5w~$?}Gtcv!fz0@*pnU4UKsYysi(U(?PkWOM11#uN;?Cwqrz+$!Y zZv}W}X6~_@I|PGm=;hFR^St*lgC#8sPV1WgJ##j| zfgGD#W>Nl^oAX}1{5Z8zTwm&t=1c=J>73389xL>9NkV-(V+^?+6*n#-n>n{?v=UYx zG{GnK4vI^+UHDx{&$J#7nhRoKww|*KhheDAmJdqNwHV7g9W%s-+`uQ_1C6({DXMw^ zb3%oj>sz68AZ2Oh(_qgj6ddtLF}*bO3rYoEBw7A2j5WP>L0_dou>Wn z7yAOhnlT?hNnV~Qo3UH4)b8c{Qc`ZghS zJ%??qI4IC(k8XztSxw@)+s{4?N6{f**X81lDBE$XFEz7D3Y-B_jPTRIf18s&S=`kv z%fH2sH0Z@yQ#97mYM)I7YsaZ9-O=dh5!c_i=MSr=eHBV)pC>iyio5qi52K=Hw9mfd ze~uZOSq+-wx^eUT1G4dO#ofZ}7G}3FyM@^;%x+Hq Ang9R* delta 4492 zcmeI!2S3yg0|0Q&ILb&F*<@s^jMLfa$e!UsgzgY>)}8r78Fg7ngEXwib~+AcDqZmrITCh9-aezQNC6JISRA-^4Z`eXt)F1X08*9cP_1xGlCo| zp_tA@Ex)Xm=w6PX$Wf`2%4!otRBDshq*1=Vzpf~dPmRoqzEx$Ltnd#*K*%N^yvNZM zrZg-AL%UL6I>}v-5=MMc*vGvhtJJ!c4-+G4hKd=Q^6ZAoY1XLcQYh?x^j1^{U=hB` zt7yGdCvKBvQFujduj5eSaW+kh$_G!G^B`Y`X4$ z+EBA1<7+9~XJUvF`o~15c;=zt&qc*AkFBkE*rg3<^+V1O<1F^6cYge7(aRI(f`l zCCqDMS6$Y(eo%Cn+oe+Wt+it+DuYRh#kPO$biK#?r?Uy+d-pZo87+J`%*O{QFTSpd z$!|K6aL)RqE-3P>vaJ$G!v;Dxck}R9m_|`Iaz*Z=k*E?0!V|vp1uHI|Uv6X$fqUMz z&jcYW9__rRlz7!w7kzYXru3@7@WI@*16U5~d^dNe=jO~w4F2XH{m^A24_RAxZ{AX8 z?4)J0i;j!+5TWNt=3>i(>1Lx*V{5MDwWpIEpuMMMO%VYsyb8eR6v@a@tuzI!;mu9R zoeYb+Ik0KzlyZ`1pyQd7-*>xdmL-h0SdcHI`wTP8fF{uD&cf(285mME|IHI)G&x#A z%A=IAR2ly5oTef!i;tjMRwl;`n}riba&D(4YAgjuM?>WsoFSj^yFVke_2;GHRRdab6+^-k>c4t~K+sQLa=W zry~6tq{`ojfd9?Hie~#~?er_?)U3FKWa{`L?DFrti)5{f(zt)K5f)OC zjY9k6C5IY3jO$OT0Y5bnYoHPHpoJYAgqfQakjzpvAXC9ru|5Q)d20R=O&`ZVCHb%B zG31J;K%T;?0YhViI@SCW74LH2Wu4&R0Bsu89?(0K-44%$tXXMpfs?r3bX;r5O#JnH z8dMPsgoZ38&(?vrB-!s?5Yrw$OgB7novo-%h!qX>YxVZr-cIc;Y}3&EegK-k(f{*y z1N|6SJ~l-=hVrV@!?^-no=17NgPKZqS452yYW5|gcxcsUY-&`%Ev0jvDfX}0ni zgsbq~XV#-{s`Ss+pPSN2|DJm`)HRPWp~VAWLJ}&kYTVKMakge}cnh$AFH_iyU82#Fw9s2ou}w;>%vHx6SRYEse1PIsEaEGO!_xl=!yL#@z7tAk0~03jqvjT)Ler0s)R6ZXhzR6C2H_|#**{Q3-Fu{40~!15ZQOb?ql>9?Yp&FG=E2)a zA??TLEl7;)MQ6}7BjFCavof<(7Exmg>QhAD#aqv%;@r*vLC@*ke9FU{Tk-qvYb_jxnl>D za|6FHOjyN96RUL_W70hMw<3>;hRtg{RXHmb9t8b)rods%&>mccLt4f*VPEOYA2vlIWS?(c&tgq zNz|vNgY|#jb#}Z+_r9?njNt_lXJFa;rNsBI1BcCw2xUjdoHwTaZhr2+L|UX5?ZK{l zVPJA!ovW`R`ZtU})P0eA*vPcoF83j6)WgL-a>W1Hdmy%*4RYTTP05^T?ZbwAZje5R oY4?C0iu9&YuwX|Pmea$W9_I8gr-wN`%;{lH4|95$|Hs4p4~LP%-v9sr diff --git a/components/wifi-manager/webapp/dist/js/index.997af2.bundle.js b/components/wifi-manager/webapp/dist/js/index.1e1c60.bundle.js similarity index 99% rename from components/wifi-manager/webapp/dist/js/index.997af2.bundle.js rename to components/wifi-manager/webapp/dist/js/index.1e1c60.bundle.js index 2b4d76ef..c2e0b94a 100644 --- a/components/wifi-manager/webapp/dist/js/index.997af2.bundle.js +++ b/components/wifi-manager/webapp/dist/js/index.1e1c60.bundle.js @@ -1,2 +1,2 @@ (()=>{"use strict";var t,e={322:(t,e,n)=>{n.r(e);var a=n(531),s=n(152),o=n(687),i=n.n(o),c=n(955),r=n(755);function l(t,e){var n="undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(!n){if(Array.isArray(t)||(n=function(t,e){if(!t)return;if("string"==typeof t)return u(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);"Object"===n&&t.constructor&&(n=t.constructor.name);if("Map"===n||"Set"===n)return Array.from(t);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return u(t,e)}(t))||e&&t&&"number"==typeof t.length){n&&(t=n);var a=0,s=function(){};return{s,n:function(){return a>=t.length?{done:!0}:{done:!1,value:t[a++]}},e:function(t){throw t},f:s}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,i=!0,c=!1;return{s:function(){n=n.call(t)},n:function(){var t=n.next();return i=t.done,t},e:function(t){c=!0,o=t},f:function(){try{i||null==n.return||n.return()}finally{if(c)throw o}}}}function u(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,a=new Array(e);n")}}),Object.assign(Date.prototype,{toLocalShort:function(){return this.toLocaleString(void 0,{dateStyle:"short",timeStyle:"short"})}});var v=1,b=17,g=2,S=18,_=4,y=20,w=8,T=24,E={bt_playing:{label:"",icon:"media_bluetooth_on"},bt_disconnected:{label:"",icon:"media_bluetooth_off"},bt_neutral:{label:"",icon:"bluetooth"},bt_connecting:{label:"",icon:"bluetooth_searching"},bt_connected:{label:"",icon:"bluetooth_connected"},bt_disabled:{label:"",icon:"bluetooth_disabled"},play_arrow:{label:"",icon:"play_circle_filled"},pause:{label:"",icon:"pause_circle"},stop:{label:"",icon:"stop_circle"},"":{label:"",icon:""}},O=[{icon:"battery_0_bar",label:"â–Ē",ranges:[{f:5.8,t:6.8},{f:8.8,t:10.2}]},{icon:"battery_2_bar",label:"â–Ēâ–Ē",ranges:[{f:6.8,t:7.4},{f:10.2,t:11.1}]},{icon:"battery_3_bar",label:"â–Ēâ–Ēâ–Ē",ranges:[{f:7.4,t:7.5},{f:11.1,t:11.25}]},{icon:"battery_4_bar",label:"â–Ēâ–Ēâ–Ēâ–Ē",ranges:[{f:7.5,t:7.8},{f:11.25,t:11.7}]}],A=[{desc:"Idle",sub:["bt_neutral"]},{desc:"Discovering",sub:["bt_connecting"]},{desc:"Discovered",sub:["bt_connecting"]},{desc:"Unconnected",sub:["bt_disconnected"]},{desc:"Connecting",sub:["bt_connecting"]},{desc:"Connected",sub:["bt_connected","play_arrow","bt_playing","pause","stop"]},{desc:"Disconnecting",sub:["bt_disconnected"]}],k={MESSAGING_INFO:"badge-success",MESSAGING_WARNING:"badge-warning",MESSAGING_ERROR:"badge-danger"},x={OK:0,FAIL:1,DISC:2,LOST:3,RESTORE:4,ETH:5},N={0:"eRunning",1:"eReady",2:"eBlocked",3:"eSuspended",4:"eDeleted"},R={NONE:0,REBOOT_TO_RECOVERY:2,SET_FWURL:5,FLASHING:6,DONE:7,UPLOADING:8,ERROR:9,UPLOADCOMPLETE:10,_state:-1,olderRecovery:!1,statusText:"",flashURL:"",flashFileName:"",statusPercent:0,Completed:!1,recovery:!1,prevRecovery:!1,updateModal:new bootstrap.Modal(document.getElementById("otadiv"),{}),reset:function(){return this.olderRecovery=!1,this.statusText="",this.statusPercent=-1,this.flashURL="",this.flashFileName=void 0,this.UpdateProgress(),r("#rTable tr.release").removeClass("table-success table-warning"),r(".flact").prop("disabled",!1),r("#flashfilename").value=null,r("#fw-url-input").value=null,this.isStateError()||(r("span#flash-status").html(""),r("#fwProgressLabel").parent().removeClass("bg-danger")),this._state=this.NONE,this},isStateUploadComplete:function(){return this._state==this.UPLOADCOMPLETE},isStateError:function(){return this._state==this.ERROR},isStateNone:function(){return this._state==this.NONE},isStateRebootRecovery:function(){return this._state==this.REBOOT_TO_RECOVERY},isStateSetUrl:function(){return this._state==this.SET_FWURL},isStateFlashing:function(){return this._state==this.FLASHING},isStateDone:function(){return this._state==this.DONE},isStateUploading:function(){return this._state==this.UPLOADING},init:function(){return this._state=this.NONE,this},SetStateError:function(){return this._state=this.ERROR,r("#fwProgressLabel").parent().addClass("bg-danger"),this},SetStateNone:function(){return this._state=this.NONE,this},SetStateRebootRecovery:function(){return this._state=this.REBOOT_TO_RECOVERY,this.SetStatusText("Starting recovery mode."),r.ajax({url:"/recovery.json",context:this,dataType:"text",method:"POST",cache:!1,contentType:"application/json; charset=utf-8",data:JSON.stringify({timestamp:Date.now()}),error:function(t,e,n){var a;this.setOTAError("Unexpected error while trying to restart to recovery. (status=".concat(null!==(a=t.status)&&void 0!==a?a:"",", error=").concat(null!=n?n:""," ) "))},complete:function(t){this.SetStatusText("Waiting for system to boot.")}}),this},SetStateSetUrl:function(){return this._state=this.SET_FWURL,this.statusText="Sending firmware download location.",G({fwurl:{value:this.flashURL,type:33}}),this},SetStateFlashing:function(){return this._state=this.FLASHING,this},SetStateDone:function(){return this._state=this.DONE,this.reset(),this},SetStateUploading:function(){return this._state=this.UPLOADING,this.SetStatusText("Sending file to device.")},SetStateUploadComplete:function(){return this._state=this.UPLOADCOMPLETE,this},isFlashExecuting:function(){return!0==(this._state!=this.UPLOADING&&(""!==this.statusText||this.statusPercent>=0))},toString:function(){var t=this;return Object.keys(this).find((function(e){return t[e]===t._state}))},setOTATargets:function(){this.flashURL="",this.flashFileName="",this.flashURL=r("#fw-url-input").val();var t=r("#flashfilename")[0].files;return t.length>0&&(this.flashFileName=t[0]),0==this.flashFileName.length&&0==this.flashURL.length&&this.setOTAError("Invalid url or file. Cannot start OTA"),this},setOTAError:function(t){return this.SetStateError().SetStatusPercent(0).SetStatusText(t).reset(),this},ShowDialog:function(){return this.isStateNone()||(this.updateModal.show(),r(".flact").prop("disabled",!0)),this},SetStatusPercent:function(t){var e=this.statusPercent!=t;return this.statusPercent=t,e&&(this.isStateUploading()||this.isStateFlashing()||this.SetStateFlashing(),100==t&&(this.isStateFlashing()?this.SetStateDone():this.isStateUploading()&&(this.statusPercent=0,this.SetStateFlashing())),this.UpdateProgress().ShowDialog()),this},SetStatusText:function(t){var e=this.statusText!=t;return this.statusText=t,e&&(r("span#flash-status").html(this.statusText),this.ShowDialog()),this},UpdateProgress:function(){return r(".progress-bar").css("width",this.statusPercent+"%").attr("aria-valuenow",this.statusPercent).text(this.statusPercent+"%"),r(".progress-bar").html((this.isStateDone()?100:this.statusPercent)+"%"),this},StartOTA:function(){return this.logEvent(this.StartOTA.name),r("#fwProgressLabel").parent().removeClass("bg-danger"),this.setOTATargets(),this.isStateError()||(W?this.SetStateFlashing().TargetReadyStartOTA():this.SetStateRebootRecovery()),this},UploadLocalFile:function(){this.SetStateUploading();var t=new XMLHttpRequest;t.context=this;var e=this.HandleUploadProgressEvent.bind(this),n=this.setOTAError.bind(this);t.upload.addEventListener("progress",e,!1),t.onreadystatechange=function(){4===t.readyState&&(0!==t.status&&404!==t.status||n("Upload Failed. Recovery version might not support uploading. Please use web update instead."))},t.open("POST","/flash.json",!0),t.send(this.flashFileName)},TargetReadyStartOTA:function(){return W&&this.prevRecovery&&!this.isStateRebootRecovery()&&!this.isStateFlashing()?this:(this.logEvent(this.TargetReadyStartOTA.name),W?(this.prevRecovery=!0,void(""!==this.flashFileName?this.UploadLocalFile():""!=this.flashURL?this.SetStateSetUrl():this.setOTAError("Invalid URL or file name while trying to start the OTa process"))):(console.error("Event TargetReadyStartOTA fired in the wrong mode "),this))},HandleUploadProgressEvent:function(t){this.logEvent(this.HandleUploadProgressEvent.name),this.SetStateUploading().SetStatusPercent(Math.round(t.loaded/t.total*100)).SetStatusText("Uploading file to device")},EventTargetStatus:function(t){var e,n;this.isStateNone()||this.logEvent(this.EventTargetStatus.name),null!==(e=t.ota_pct)&&void 0!==e&&e&&(this.olderRecovery=!0,this.SetStatusPercent(t.ota_pct)),""!=(null!==(n=t.ota_dsc)&&void 0!==n?n:"")&&(this.olderRecovery=!0,this.SetStatusText(t.ota_dsc)),null!=t.recovery&&(this.recovery=1===t.recovery),this.isStateRebootRecovery()&&this.recovery&&this.TargetReadyStartOTA()},EventOTAMessageClass:function(t){this.logEvent(this.EventOTAMessageClass.name);var e=JSON.parse(t);this.SetStatusPercent(e.ota_pct).SetStatusText(e.ota_dsc)},logEvent:function(t){console.log("".concat(t,", flash state ").concat(this.toString(),", recovery: ").concat(this.recovery,", ota pct: ").concat(this.statusPercent,", ota desc: ").concat(this.statusText))}};window.hideSurrounding=function(t){r(t).parent().parent().hide()};var C=!1,I=2500;function G(t){var e={timestamp:Date.now(),config:t};r.ajax({url:"/config.json",dataType:"text",method:"POST",cache:!1,contentType:"application/json; charset=utf-8",data:JSON.stringify(e),error:L})}function j(t){for(var e,n,a={},s="",o=t.match(/("[^"]+"|'[^']+'|\S+)/g),i=0;i0&&(e=e.substring(0,e.indexOf(" ")));return e}(a),n=function(t){var e;t.n&&(e=t.n.replace(/"/g,"").replace(/'/g,""));return e}(a);var u={btname:null,n:null};if(a.o&&"BT"===e.toUpperCase()){var d=j(a.o);d.name&&(u.btname=d.name),delete a.o}return a.n&&(u.n=a.n,delete a.n),{name:n,output:e,options:a,otherValues:s,otherOptions:u}}function M(){return it.hasOwnProperty("ip")&&"0.0.0.0"!=it.ip&&""!=it.ip}function P(t){return M()?t.icon:t.label}function U(t){r("#o_type").children("span").css({display:"none"});var e=!1;"bt"===t?(e="bt"!==Q&&""!==Q,Q="bt"):"spdif"===t?(e="spdif"!==Q&&""!==Q,Q="spdif"):(e="i2s"!==Q&&""!==Q,Q="i2s"),r("#"+Q).prop("checked",!0),r("#o_"+Q).css({display:"inline"}),e&&Object.keys(q[Q]).forEach((function(t){r("#cmd_opt_".concat(t)).val(q[Q][t])}))}function L(t,e,n){console.log(t.status),console.log(n),""!==n&&xt(n,"MESSAGING_ERROR")}function F(t,e,n){var a=arguments.length>3&&void 0!==arguments[3]&&arguments[3],s="table-success";"MESSAGING_WARNING"===e?s="table-warning":"MESSAGING_ERROR"===e&&(s="table-danger"),r("#toast_"+t).removeClass("table-success").removeClass("table-warning").removeClass("table-danger").addClass(s).addClass("show");var o=n.substring(0,n.length-1).encodeHTML().replace(/\n/g,"
");o=(r("#msg_"+t).html().length>0&&a?r("#msg_"+t).html()+"
":"")+o,r("#msg_"+t).html(o)}window.hFlash=function(){r("#flashfilename").value=null,R.StartOTA()},window.handleReboot=function(t){"reboot_ota"==t?(r("#reboot_ota_nav").removeClass("active").prop("disabled",!0),dt(500,"","reboot_ota")):(r("#reboot_nav").removeClass("active"),dt(500,"",t))};var D,J="https://api.github.com/repos/sle118/squeezelite-esp32/releases",W=!1,H=!1,B="",q={i2s:{b:"500:2000",C:"30",W:"",Z:"96000",o:"I2S"},spdif:{b:"500:2000",C:"30",W:"",Z:"48000",o:"SPDIF"},bt:{b:"500:2000",C:"30",W:"",Z:"44100",o:"BT"}},Y={codecs:["flac","pcm","mp3","ogg","aac","wma","alac","dsd","mad","mpg"]},z=0,Z="MESSAGING_INFO",V={},K=null,Q="",X="",$="Squeezelite-ESP32",tt="",et=$,nt="",at=$,st="",ot="#cfg-audio-bt_source-sink_name",it={},ct={},rt="",lt={CONN:0,MAN:1,STS:2};function ut(t){var e={};r("input.nvs").each((function(n,a){if(t)e[a.id]=a.value;else{var s=parseInt(a.attributes.nvs_type.value,10);""!==a.id&&(e[a.id]={},e[a.id].value=s===v||s===b||s===g||s===S||s===_||s===y||s===w||s===T?parseInt(a.value):a.value,e[a.id].type=s)}}));var n=r("#nvs-new-key").val(),a=r("#nvs-new-value").val();return""!==n&&(t?e[n]=a:(e[n]={},e[n].value=a,e[n].type=33)),e}function dt(t,e){var n="/"+(arguments.length>2&&void 0!==arguments[2]?arguments[2]:"reboot")+".json";r("tbody#tasks").empty(),r("#tasks_sect").css("visibility","collapse"),h.resolve({cmdname:e,url:n}).delay(t).then((function(t){t.cmdname.length>0?F(t.cmdname,"MESSAGING_WARNING","System is rebooting.\n",!0):xt("System is rebooting.\n","MESSAGING_WARNING"),console.log("now triggering reboot"),r("button[onclick*='handleReboot']").addClass("rebooting"),r.ajax({url:t.url,dataType:"text",method:"POST",cache:!1,contentType:"application/json; charset=utf-8",data:JSON.stringify({timestamp:Date.now()}),error:L,complete:function(){console.log("reboot call completed"),h.resolve(t).delay(6e3).then((function(t){t.cmdname.length>0&&function(t){r("#toast_"+t).removeClass("table-success").removeClass("table-warning").removeClass("table-danger").addClass("table-success").removeClass("show"),r("#msg_"+t).html("")}(t.cmdname),At(),kt()}))}})}))}function ht(t){return r(".upf").filter((function(){return r(this).text().toUpperCase()===t.toUpperCase()})).length>0&&(r("#splf").val(t).trigger("input"),!0)}function pt(t,e){var n="cmd_opt_".concat(t),a="".concat(n,"-error"),s=r("#".concat(a)),o=r("#".concat(n));return s&&0!=s.length||(o.after('
')),s=r("#".concat(a))),0==e.length?(s.hide(),o.removeClass("is-invalid"),o.addClass("is-valid"),s.text("")):(s.show(),s.text(e),o.removeClass("is-valid"),o.addClass("is-invalid")),s}function ft(t){return t>=-55?{label:"****",icon:"signal_wifi_statusbar_4_bar"}:t>=-60?{label:"***",icon:"network_wifi_3_bar"}:t>=-65?{label:"**",icon:"network_wifi_2_bar"}:t>=-70?{label:"*",icon:"network_wifi_1_bar"}:{label:".",icon:"signal_wifi_statusbar_null"}}function mt(){var t;(null===(t=it)||void 0===t?void 0:t.urc)!==x.ETH&&(r.ajaxSetup({timeout:3e3}),r.getJSON("/scan.json",(0,a.Z)(i().mark((function t(){return i().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return t.next=2,Rt(2e3);case 2:r.getJSON("/ap.json",(function(t){t.length>0&&(t.sort((function(t,e){var n=t.rssi,a=e.rssi;return na?-1:0})),bt(t))}));case 3:case"end":return t.stop()}}),t)})))))}function vt(t,e,n){var a=ft(e),s={label:0==n?"🔓":"🔒",icon:0==n?"no_encryption":"lock"};return''.concat(t,'\n ').concat(P(a),'\n \t\n ').concat(P(s),"\n ")}function bt(t){var e,n="";if(r("#wifiTable tr td:first-of-type").text(""),r("#wifiTable tr").removeClass("table-success table-warning"),t&&(t.forEach((function(t){n+=vt(t.ssid,t.rssi,t.auth)})),r("#wifiTable").html(n)),0==r(".manual_add").length&&(r("#wifiTable").append(vt("Manual add",0,0)),r("#wifiTable tr:last").addClass("table-light text-dark").addClass("manual_add")),!it.ssid||it.urc!==x.OK&&it.urc!==x.RESTORE)(null===(e=it)||void 0===e?void 0:e.urc)!==x.ETH&&r("span#foot-if").html("");else{var a,s='#wifiTable td:contains("'.concat(it.ssid,'")');if(0==r(s).filter((function(){return r(this).text()===it.ssid})).length)r("#wifiTable").prepend("".concat(vt(it.ssid,null!==(a=it.rssi)&&void 0!==a?a:0,0)));r(s).filter((function(){return r(this).text()===it.ssid})).siblings().first().html("✓").parent().addClass(it.urc===x.OK?"table-success":"table-warning"),r("span#foot-if").html("SSID: ".concat(it.ssid,", IP: ").concat(it.ip,"")),r("#wifiStsIcon").html(ft(it.rssi))}}function gt(t){console.debug(this.toLocaleString()+"\t"+t.nme+"\t"+t.cpu+"\t"+N[t.st]+"\t"+t.minstk+"\t"+t.bprio+"\t"+t.cprio+"\t"+t.num),r("tbody#tasks").append(''+t.num+""+t.nme+""+t.cpu+""+N[t.st]+""+t.minstk+""+t.bprio+""+t.cprio+"")}function St(t){return r("".concat(ot," option:contains('").concat(t,"')"))}function _t(){r.ajaxSetup({timeout:I}),r.getJSON("/messages.json",function(){var t=(0,a.Z)(i().mark((function t(e){var n,a,s,o,c,u,d,h,p,f;return i().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:n=l(e),t.prev=1,s=i().mark((function t(){var e,n;return i().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:e=a.value,n=e.current_time-e.sent_time,(o=new Date).setTime(o.getTime()-n),t.t0=e.class,t.next="MESSAGING_CLASS_OTA"===t.t0?7:"MESSAGING_CLASS_STATS"===t.t0?9:"MESSAGING_CLASS_SYSTEM"===t.t0?14:"MESSAGING_CLASS_CFGCMD"===t.t0?16:"MESSAGING_CLASS_BT"===t.t0?19:23;break;case 7:return R.EventOTAMessageClass(e.message),t.abrupt("break",24);case 9:return c=JSON.parse(e.message),console.debug(o.toLocalShort()+" - Number of running tasks: "+c.ntasks),console.debug(o.toLocalShort()+"\tname\tcpu\tstate\tminstk\tbprio\tcprio\tnum"),c.tasks?("collapse"===r("#tasks_sect").css("visibility")&&r("#tasks_sect").css("visibility","visible"),r("tbody#tasks").html(""),c.tasks.sort((function(t,e){return e.cpu-t.cpu})).forEach(gt,o)):"visible"===r("#tasks_sect").css("visibility")&&(r("tbody#tasks").empty(),r("#tasks_sect").css("visibility","collapse")),t.abrupt("break",24);case 14:return Nt(e,o),t.abrupt("break",24);case 16:return F((u=e.message.split(/([^\n]*)\n([\s\S]*)/g))[1],e.type,u[2],!0),t.abrupt("break",24);case 19:if(r("#cfg-audio-bt_source-sink_name").is("input")){for(d=r("#cfg-audio-bt_source-sink_name")[0].attributes,h="",p=0;p "))}return JSON.parse(e.message).forEach((function(t){St(t.name).length>0||(r("#cfg-audio-bt_source-sink_name").append("")),Nt({type:e.type,message:"BT Audio device found: ".concat(t.name," RSSI: ").concat(t.rssi," ")},o)),St(t.name).attr("data-bs-description","".concat(t.name," (").concat(t.rssi,"dB)")).attr("rssi",t.rssi).attr("value",t.name).text("".concat(t.name," [").concat(t.rssi,"dB]")).trigger("change")})),r(ot).append(r("".concat(ot," option")).remove().sort((function(t,e){return console.log("".concat(parseInt(r(t).attr("rssi"))," < ").concat(parseInt(r(e).attr("rssi"))," ? ")),parseInt(r(t).attr("rssi"))".concat(it.ip,""))):(r(".if_wifi").show(),bt())),yt(t)}function Et(){r.ajaxSetup({timeout:2e3}),r.getJSON("/status.json",(function(t){var e;if(function(t){var e;1===(null!==(e=t.recovery)&&void 0!==e?e:0)?(W=!0,r(".recovery_element").show(),r(".ota_element").hide(),r("#boot-button").html("Reboot"),r("#boot-form").attr("action","/reboot_ota.json")):(!W&&H&&(H=!1,setTimeout(_t,I)),W=!1,r(".recovery_element").hide(),r(".ota_element").show(),r("#boot-button").html("Recovery"),r("#boot-form").attr("action","/recovery.json"))}(t),f(),Tt(t),function(t){var e="",n="";if(void 0!==t.bt_status&&void 0!==t.bt_sub_status){var a=A[t.bt_status].sub[t.bt_sub_status];a?(e=E[a],n=A[t.bt_status].desc):(e=E.bt_connected,n="Output status")}r("#o_type").attr("title",n),r("#o_bt").html(M()?e.label:e.text)}(t),R.EventTargetStatus(t),t.depth&&(16==t.depth?r("#cmd_opt_R").show():r("#cmd_opt_R").hide()),t.project_name&&""!==t.project_name&&(et=t.project_name),t.platform_name&&""!==t.platform_name&&(at=t.platform_name),""===nt&&(nt=et),""===nt&&(nt="Squeezelite-ESP32"),t.version&&""!==t.version?($=t.version,r("#navtitle").html("".concat(nt).concat(W?"
[recovery]":"")),r("span#foot-fw").html("fw: ".concat($,", mode: ").concat(W?"Recovery":et,""))):r("span#flash-status").html(""),t.Voltage){var n=function(t){for(var e=0,n=O;e
'.concat(e.help.encodeHTML().replace(/\n/g,"
"),'
'),e.argtable&&e.argtable.forEach((function(n){var a=n.datatype||"",s=e.name+"-"+n.longopts,i=Ot(t,e.name,n.longopts),c="hasvalue="+n.hasvalue+" ";c+='longopts="'+n.longopts+'" ',c+='shortopts="'+n.shortopts+'" ',c+="checkbox="+n.checkbox+" ",c+='cmdname="'+e.name+'" ',c+='id="'+s+'" name="'+s+'" hasvalue="'+n.hasvalue+'" ';var r=n.mincount>0?"bg-success":"";"hidden"===n.glossary&&(c+=' style="visibility: hidden;"'),n.checkbox?o+='
"):(o+='
"),a.includes("|")?(r=a.startsWith("+")?" multiple ":"",a=a.replace("<","").replace("=","").replace(">",""),o+=""):o+='")),o+="".concat(n.checkbox?"
":"",'Previous value: ').concat(n.checkbox?i?"Checked":"Unchecked":i||"","").concat(n.checkbox?"":"
")})),o+='
\n '),o+=a?'\n'):''),o+="
",a?r(s).append(o):r("#commands-list").append(o)}})),r(".sclk").off("click").on("click",(function(){runCommand(this,!1)})),r(".cclk").off("click").on("click",(function(){runCommand(this,!0)})),t.commands.forEach((function(e){r("[cmdname="+e.name+"]:input").val(""),r("[cmdname="+e.name+"]:checkbox").prop("checked",!1),e.argtable&&e.argtable.forEach((function(n){var a="#"+e.name+"-"+n.longopts,s=Ot(t,e.name,n.longopts);n.checkbox?r(a)[0].checked=s:(void 0!==s&&r(a).val(s).trigger("change"),0===r(a)[0].value.length&&(n.datatype||"").includes("|")&&(r(a)[0].value="--"))}))})),0!=r("#cfg-hw-preset-model_config").length&&(C||(C=!0,r("#cfg-hw-preset-model_config").html(""),r.getJSON("https://gist.githubusercontent.com/sle118/dae585e157b733a639c12dc70f0910c5/raw/",{_:(new Date).getTime()},(function(t){r.each(t,(function(t,e){r("#cfg-hw-preset-model_config").append("")),""!==st&&st==e.name&&r("#cfg-hw-preset-model_config").val(st)})),""!==st&&"#prev_preset".show().val(st)})).fail((function(t,e,n){var a=e+", "+n;console.log("Request Failed: "+a)}))))})).fail((function(t,e,n){404==t.status?r(".orec").hide():L(t,0,n),r("#commands-list").empty()}))}function kt(){r.ajaxSetup({timeout:7e3}),r.getJSON("/config.json",(function(t){r("#nvsTable tr").remove();var e=t.config?t.config:t;V=e,B="",Object.keys(e).sort().forEach((function(t){var n=e[t].value;"autoexec"===t?"0"===e.autoexec.value?r("#disable-squeezelite")[0].checked=!0:r("#disable-squeezelite")[0].checked=!1:"autoexec1"===t?function(t){var e=j(t);e.output.toUpperCase().startsWith("I2S")?U("i2s"):e.output.toUpperCase().startsWith("SPDIF")?U("spdif"):e.output.toUpperCase().startsWith("BT")&&(e.otherOptions.btname&&(B=e.otherOptions.btname),U("bt"));if(Object.keys(e.options).forEach((function(t){var n=e.options[t];r("#cmd_opt_".concat(t)).hasOwnProperty("checked")?r("#cmd_opt_".concat(t))[0].checked=n:r("#cmd_opt_".concat(t)).val(n)})),e.options.hasOwnProperty("u")){var n=e.options.u.split(":"),a=(0,s.Z)(n,2),o=a[0],i=a[1];r("#resample_".concat(o)).prop("checked",!0),i&&r("#resample_i").prop("checked",!0)}}(n):"host_name"===t?(n=n.replaceAll('"',""),r("input#dhcp-name1").val(n),r("input#dhcp-name2").val(n),0==r("#cmd_opt_n").length&&r("#cmd_opt_n").val(n),document.title=n,X=n):"rel_api"===t?J=n:"enable_airplay"===t?r("#s_airplay").css({display:m(n)?"inline":"none"}):"enable_cspot"===t?r("#s_cspot").css({display:m(n)?"inline":"none"}):"preset_name"==t?st=n:"board_model"==t&&(nt=n),r("tbody#nvsTable").append(""+t+""),r("input#"+t).val(e[t].value)})),B.length>0&&r("#cfg-audio-bt_source-sink_name").val(B),r("tbody#nvsTable").append(""),t.gpio?(r("#pins").show(),r("tbody#gpiotable tr").remove(),t.gpio.forEach((function(t){r("tbody#gpiotable").append("'+t.group+""+t.name+""+t.gpio+""+(t.fixed?"Fixed":"Configuration")+"")}))):r("#pins").hide()})).fail((function(t,e,n){L(t,0,n)}))}function xt(t,e){Nt({message:t,type:e},new Date)}function Nt(t,e){var n="table-success";"MESSAGING_WARNING"===t.type?(n="table-warning","MESSAGING_INFO"===Z&&(Z="MESSAGING_WARNING")):"MESSAGING_ERROR"===t.type&&("MESSAGING_INFO"!==Z&&"MESSAGING_WARNING"!==Z||(Z="MESSAGING_ERROR"),n="table-danger"),++z>0&&(r("#msgcnt").removeClass("badge-success"),r("#msgcnt").removeClass("badge-warning"),r("#msgcnt").removeClass("badge-danger"),r("#msgcnt").addClass(k[Z]),r("#msgcnt").text(z)),r("#syslogTable").append(""+e.toLocalShort()+""+t.message.encodeHTML()+"")}function Rt(t){return new h((function(e){return setTimeout(e,t)}))}h.prototype.delay=function(t){return this.then((function(e){return new h((function(n){setTimeout((function(){n(e)}),t)}))}),(function(e){return new h((function(n,a){setTimeout((function(){a(e)}),t)}))}))},window.saveAutoexec1=function(t){F("cfg-audio-tmpl","MESSAGING_INFO","Saving.\n",!1);var e="".concat("squeezelite "," -o ").concat(Q," ");r(".sqcmd").each((function(){var t=p(r(this)),n=t.opt,a=t.val;if(n&&n.length>0&&"boolean"==typeof a||a.length>0){var s=":"===n?n:" -".concat(n," ");a="boolean"==typeof a?"":a,e+="".concat(s," ").concat(a)}}));var n=r("#cmd_opt_R input[name=resample]:checked");n.length>0&&""!==n.attr("suffix")&&(e+=n.attr("suffix"),r("#resample_i").is(":checked")&&"true"==n.attr("aint")&&(e+=r("#resample_i").attr("suffix"))),"bt"===Q&&F("cfg-audio-tmpl","MESSAGING_INFO","Remember to configure the Bluetooth audio device name.\n",!0),e+=function(t){for(var e=" ",n=0,a=Object.entries(t);n8&&(r(this).val().startsWith("http://")||r(this).val().startsWith("https://"))?r("#start-flash").show():r("#start-flash").hide()})),r(".upSrch").on("input",(function(){var t=this.value;r("#rTable tr").removeClass(this.id+"_hide"),t.length>0&&r("#rTable td:nth-child(".concat(r(this).parent().index()+1,")")).filter((function(){return!r(this).text().toUpperCase().includes(t.toUpperCase())})).parent().addClass(this.id+"_hide"),r('[class*="_hide"]').hide(),r("#rTable tr").not('[class*="_hide"]').show()})),setTimeout(mt,1500),r("#options input").on("input",(function(){var t=p(this),e=t.opt,n=t.val;if("c"===e||"e"===e){"cmd_opt_".concat(e,"_codec-error");var a=n.split(",").map((function(t){return t.trim()})).filter((function(t){return!Y.codecs.includes(t)}));pt(e,a.length>0?"Invalid codec(s) ".concat(a.join(", ")):"")}if("m"===e){pt(e,/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/.test(n)?"":"Invalid MAC address")}if("r"===e){pt(e,/^(\d+\.?\d*|\.\d+)-(\d+\.?\d*|\.\d+)$|^(\d+\.?\d*)$|^(\d+\.?\d*,)+\d+\.?\d*$/.test(n)?"":"Invalid rate(s) ".concat(n,". Acceptable format: |-|,,"))}})),r("#WifiConnectDialog")[0].addEventListener("shown.bs.modal",(function(t){r("*[class*='connecting']").hide(),null!=t&&t.relatedTarget&&(ct.Action=lt.CONN,r(t.relatedTarget).children("td:eq(1)").text()==it.ssid?ct.Action=lt.STS:r(t.relatedTarget).is(":last-child")?(ct.Action=lt.MAN,ct.ssid="",r("#manual_ssid").val(ct.ssid)):(ct.ssid=r(t.relatedTarget).children("td:eq(1)").text(),r("#manual_ssid").val(ct.ssid))),ct.Action!==lt.STS?(r(".connecting-init").show(),r("#manual_ssid").trigger("focus")):yt()})),r("#WifiConnectDialog")[0].addEventListener("hidden.bs.modal",(function(){r("#WifiConnectDialog input").val("")})),r("#uCnfrm")[0].addEventListener("shown.bs.modal",(function(){r("#selectedFWURL").text(r("#fw-url-input").val())})),r("input#show-commands")[0].checked=1===K,r('a[href^="#tab-commands"]').hide(),r("#load-nvs").on("click",(function(){r("#nvsfilename").trigger("click")})),r("#nvsfilename").on("change",(function(){if("function"!=typeof window.FileReader)throw"The file API isn't supported on this browser.";if(!this.files)throw"This browser does not support the `files` property of the file input.";if(this.files[0]){var t=this.files[0],e=new FileReader;e.onload=function(t){var e={};try{e=JSON.parse(t.target.result)}catch(t){alert("Parsing failed!\r\n "+t)}r("input.nvs").each((function(t,n){r(this).parent().removeClass("bg-warning").removeClass("bg-success"),e[n.id]&&(e[n.id]!==n.value?(console.log("Changed "+n.id+" "+n.value+"==>"+e[n.id]),r(this).parent().addClass("bg-warning"),r(this).val(e[n.id])):r(this).parent().addClass("bg-success"))})),r("input.nvs").children(".bg-warning")&&alert("Highlighted values were changed. Press Commit to change on the device")},e.readAsText(t),this.value=null}})),r("#clear-syslog").on("click",(function(){z=0,Z="MESSAGING_INFO",r("#msgcnt").text(""),r("#syslogTable").html("")})),r("#ok-credits").on("click",(function(){r("#credits").slideUp("fast",(function(){})),r("#app").slideDown("fast",(function(){}))})),r("#acredits").on("click",(function(t){t.preventDefault(),r("#app").slideUp("fast",(function(){})),r("#credits").slideDown("fast",(function(){}))})),r("input#show-commands").on("click",(function(){this.checked=this.checked?1:0,this.checked?(r('a[href^="#tab-commands"]').show(),K=1):(K=0,r('a[href^="#tab-commands"]').hide())})),r("input#show-nvs").on("click",(function(){this.checked=this.checked?1:0,c.Z.set("show-nvs",this.checked?"Y":"N"),f()})),r("#btn_reboot_recovery").on("click",(function(){handleReboot("recovery")})),r("#btn_reboot").on("click",(function(){handleReboot("reboot")})),r("#btn_flash").on("click",(function(){hFlash()})),r("#save-autoexec1").on("click",(function(){saveAutoexec1(!1)})),r("#commit-autoexec1").on("click",(function(){saveAutoexec1(!0)})),r("#btn_disconnect").on("click",(function(){it={},bt(),r.ajax({url:"/connect.json",dataType:"text",method:"DELETE",cache:!1,contentType:"application/json; charset=utf-8",data:JSON.stringify({timestamp:Date.now()})})})),r("#btnJoin").on("click",(function(){handleConnect()})),r("#reboot_nav").on("click",(function(){handleReboot("reboot")})),r("#reboot_ota_nav").on("click",(function(){handleReboot("reboot_ota")})),r("#save-as-nvs").on("click",(function(){var t=ut(!0),e=document.createElement("a");e.href=URL.createObjectURL(new Blob([JSON.stringify(t,null,2)],{type:"text/plain"})),e.setAttribute("download","nvs_config_"+X+"_"+Date.now()+"json"),document.body.appendChild(e),e.click(),document.body.removeChild(e)})),r("#save-nvs").on("click",(function(){G(ut(!1))})),r("#fwUpload").on("click",(function(){0===document.getElementById("flashfilename").files.length?alert("No file selected!"):(r("#fw-url-input").value=null,R.StartOTA())})),r("[name=output-tmpl]").on("click",(function(){U(this.id)})),r("#chkUpdates").on("click",(function(){r("#rTable").html(""),r.getJSON(J,(function(t){var e=[];t.forEach((function(t){var n=t.name.split("#")[3];e.includes(n)||e.push(n)}));var n="";e.forEach((function(t){n+='"})),r("#fwbranch").append(n),t.forEach((function(t){var e="";t.assets.forEach((function(t){t.name.match(/\.bin$/)&&(e=t.browser_download_url)}));var n=t.name.split("#"),a=n[0],s=n[2],o=n[3],i=a.substr(a.lastIndexOf("-")+1);i="32"==i||"16"==i?i:"";var c=t.body;c=(c=(c=c.replace(/'/gi,'"')).replace(/[\s\S]+(### Revision Log[\s\S]+)### ESP-IDF Version Used[\s\S]+/,"$1")).replace(/- \(.+?\) /g,"- ").encodeHTML(),r("#rTable").append("\n ").concat(a,"").concat(new Date(t.created_at).toLocalShort(),"\n ").concat(s,"").concat(o,"").concat(i,""))})),r("#searchfw").css("display","inline"),ht(at)||ht(et),r("#rTable tr.release").on("click",(function(){var t=this.attributes.fwurl.value;D&&(t=t.replace(/.*\/download\//,D+"/plugins/SqueezeESP32/firmware/")),r("#fw-url-input").val(t),r("#start-flash").show(),r("#rTable tr.release").removeClass("table-success table-warning"),r(this).addClass("table-success table-warning")}))})).fail((function(){alert("failed to fetch release history!")}))})),r("#fwcheck").on("click",(function(){r("#releaseTable").html(""),r("#fwbranch").empty(),r.getJSON(J,(function(t){var e,n=0,a=[];t.forEach((function(t){var e=t.name.split("#")[3];a.includes(e)||a.push(e)})),a.forEach((function(t){e+='"})),r("#fwbranch").append(e),t.forEach((function(t){var e="";t.assets.forEach((function(t){t.name.match(/\.bin$/)&&(e=t.browser_download_url)}));var a=t.name.split("#"),s=a[0],o=a[1],i=a[2],c=a[3],l=t.body;l=(l=(l=l.replace(/'/gi,'"')).replace(/[\s\S]+(### Revision Log[\s\S]+)### ESP-IDF Version Used[\s\S]+/,"$1")).replace(/- \(.+?\) /g,"- ");var u=n++>6?" hide":"";r("#releaseTable").append(""+s+""+new Date(t.created_at).toLocalShort()+""+i+""+o+""+c+"")})),n>7&&(r("#releaseTable").append(""),r("#showallbutton").on("click",(function(){r("tr.hide").removeClass("hide"),r("tr#showall").addClass("hide")}))),r("#searchfw").css("display","inline")})).fail((function(){alert("failed to fetch release history!")}))})),r("#updateAP").on("click",(function(){mt(),console.log("refresh AP")})),kt(),At(),_t(),Et()})),window.setURL=function(t){var e=t.dataset.url;r('[data-bs-url^="http"]').addClass("btn-success").removeClass("btn-danger"),r('[data-bs-url="'+e+'"]').addClass("btn-danger").removeClass("btn-success"),D&&(e=e.replace(/.*\/download\//,D+"/plugins/SqueezeESP32/firmware/")),r("#fwurl").val(e)},window.runCommand=function(t,e){var n=t.attributes.cmdname.value;F(t.attributes.cmdname.value,"MESSAGING_INFO","Executing.",!1);var a=document.getElementById("flds-"+n),o=null==a?void 0:a.querySelectorAll("select,input");if("cfg-hw-preset"===n)return function(t,e){var n=JSON.parse(t[0].value),a=t[0].attributes.cmdname.value;console.log("selected model: ".concat(n.name));for(var o={timestamp:Date.now(),config:{model_config:{value:n.name,type:33}}},i=0,c=Object.entries(n.config);i{n.r(e)},607:(t,e,n)=>{n(138),n(393),n(861),n(322)},861:t=>{t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAb1BMVEXIycuswsKMjI4rqqZyc3RQlpQ6jIEmJifW2dq5ursppJ8Om4zC0NAFdGYmmpb///8Hg3O4x8cHkoEggX0jko5Ks6/P0dM5r6ocoZb3+PgiiYVevrp/y8bg4uOS09FtxMDs7+7M6um529qoysik2tiNn72gAAAAF3RSTlP94Fr/Wf39BP26/////////////////kibhL0AAAGjSURBVDjLbZMJkoMgEEWtmETEJWpkiSC45P5nnF4wk7HmW2jLfzYIdFYUxbXUYp5nIbTOUFoLAR2ivIKZFQXYuu6TahSHmdAlAqWub0/QNI1jSxrHacKeWw9EdtH1xHbbyiRgCJn67JqVAr9nO2fJnBDMoUuYEvsfmxnJBM66Zj8/iYmaAPKlOvRNJAC/fz8OefINEAngAbYPEMiHTJCCAZrACciVMpCCgDEBKwsAowymMO3IAP3Btqa5vYJx0ZlcOSUZaE/AWznvnTHOyfZ/wMUQvAIg/wb27QNEH94BgGj+APsZiF8AXAhQQEMwkIYYLW7xvsENoyUoF0I0ysf0F2O743kDQNXzXM8+j8Eb6byzDEz7gtpsO1PgrXG5Nd6btNTP+YXarKTny1uQ9JiAN6vbqT9au+BzMQjAWtlq6BiYttdjiVVVqfXxWFWFkk6Cz0DTdYOFPmpHAAK/YQCJoTppQJ8A3TAxVAAhR439Bg5tKe7NgSDEje3mDsf+ovuGCUbYZb/BwoHS6ykHMYfo/U6lx8Xb/+qo3U/x/lf+VP9c/j9c3zy20WEMxgAAAABJRU5ErkJggg=="}},n={};function a(t){var s=n[t];if(void 0!==s)return s.exports;var o=n[t]={id:t,loaded:!1,exports:{}};return e[t].call(o.exports,o,o.exports,a),o.loaded=!0,o.exports}a.m=e,t=[],a.O=(e,n,s,o)=>{if(!n){var i=1/0;for(u=0;u=o)&&Object.keys(a.O).every((t=>a.O[t](n[r])))?n.splice(r--,1):(c=!1,o0&&t[u-1][2]>o;u--)t[u]=t[u-1];t[u]=[n,s,o]},a.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return a.d(e,{a:e}),e},a.d=(t,e)=>{for(var n in e)a.o(e,n)&&!a.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},a.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),a.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),a.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},a.nmd=t=>(t.paths=[],t.children||(t.children=[]),t),(()=>{var t={826:0};a.O.j=e=>0===t[e];var e=(e,n)=>{var s,o,[i,c,r]=n,l=0;if(i.some((e=>0!==t[e]))){for(s in c)a.o(c,s)&&(a.m[s]=c[s]);if(r)var u=r(a)}for(e&&e(n);la(607)));s=a.O(s)})(); -//# sourceMappingURL=index.997af2.bundle.js.map \ No newline at end of file +//# sourceMappingURL=index.1e1c60.bundle.js.map \ No newline at end of file diff --git a/components/wifi-manager/webapp/dist/js/index.997af2.bundle.js.gz b/components/wifi-manager/webapp/dist/js/index.1e1c60.bundle.js.gz similarity index 99% rename from components/wifi-manager/webapp/dist/js/index.997af2.bundle.js.gz rename to components/wifi-manager/webapp/dist/js/index.1e1c60.bundle.js.gz index 54aeff11c812a94ead67a5ce87298a356ac18c7c..f10093f29e039bc4f1a66e5e07b87e0210a37e08 100644 GIT binary patch delta 33 rcmV++0N(%9dDD5Yx;z<8*6eOH?9LQ@OF)vqQwRSK@EDy!^s@i}DOeCF delta 34 qcmcayd8Km0E*mNRIGgXkvdhxD_Ngj(K3LiOpM672)a@^u85jUI&JipC diff --git a/components/wifi-manager/webapp/dist/js/index.997af2.bundle.js.map b/components/wifi-manager/webapp/dist/js/index.1e1c60.bundle.js.map similarity index 99% rename from components/wifi-manager/webapp/dist/js/index.997af2.bundle.js.map rename to components/wifi-manager/webapp/dist/js/index.1e1c60.bundle.js.map index a1668af3..0bc42ab0 100644 --- a/components/wifi-manager/webapp/dist/js/index.997af2.bundle.js.map +++ b/components/wifi-manager/webapp/dist/js/index.1e1c60.bundle.js.map @@ -1 +1 @@ -{"version":3,"file":"./js/index.997af2.bundle.js","mappings":"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","sources":["webpack://squeezelite-esp32/webpack/runtime/chunk loaded","webpack://squeezelite-esp32/./src/js/custom.js","webpack://squeezelite-esp32/./src/index.ts","webpack://squeezelite-esp32/webpack/bootstrap","webpack://squeezelite-esp32/webpack/runtime/compat get default export","webpack://squeezelite-esp32/webpack/runtime/define property getters","webpack://squeezelite-esp32/webpack/runtime/global","webpack://squeezelite-esp32/webpack/runtime/hasOwnProperty shorthand","webpack://squeezelite-esp32/webpack/runtime/make namespace object","webpack://squeezelite-esp32/webpack/runtime/node module decorator","webpack://squeezelite-esp32/webpack/runtime/jsonp chunk loading","webpack://squeezelite-esp32/webpack/startup"],"sourcesContent":["var deferred = [];\n__webpack_require__.O = (result, chunkIds, fn, priority) => {\n\tif(chunkIds) {\n\t\tpriority = priority || 0;\n\t\tfor(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];\n\t\tdeferred[i] = [chunkIds, fn, priority];\n\t\treturn;\n\t}\n\tvar notFulfilled = Infinity;\n\tfor (var i = 0; i < deferred.length; i++) {\n\t\tvar [chunkIds, fn, priority] = deferred[i];\n\t\tvar fulfilled = true;\n\t\tfor (var j = 0; j < chunkIds.length; j++) {\n\t\t\tif ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) {\n\t\t\t\tchunkIds.splice(j--, 1);\n\t\t\t} else {\n\t\t\t\tfulfilled = false;\n\t\t\t\tif(priority < notFulfilled) notFulfilled = priority;\n\t\t\t}\n\t\t}\n\t\tif(fulfilled) {\n\t\t\tdeferred.splice(i--, 1)\n\t\t\tvar r = fn();\n\t\t\tif (r !== undefined) result = r;\n\t\t}\n\t}\n\treturn result;\n};","var he = require('he');\nvar Promise = require('es6-promise').Promise;\nwindow.bootstrap = require('bootstrap');\nimport Cookies from 'js-cookie';\n\n\n\nif (!String.prototype.format) {\n Object.assign(String.prototype, {\n format() {\n const args = arguments;\n return this.replace(/{(\\d+)}/g, function (match, number) {\n return typeof args[number] !== 'undefined' ? args[number] : match;\n });\n },\n });\n}\nif (!String.prototype.encodeHTML) {\n Object.assign(String.prototype, {\n encodeHTML() {\n return he.encode(this).replace(/\\n/g, '
');\n },\n });\n}\nObject.assign(Date.prototype, {\n toLocalShort() {\n const opt = { dateStyle: 'short', timeStyle: 'short' };\n return this.toLocaleString(undefined, opt);\n },\n});\nfunction get_control_option_value(obj) {\n let ctrl,id,val,opt;\n let radio = false;\n let checked = false;\n if (typeof (obj) === 'string') {\n id = obj;\n ctrl = $(`#${id}`);\n } else {\n id = $(obj).attr('id');\n ctrl = $(obj);\n }\n if(ctrl.attr('type') === 'checkbox'){\n opt = $(obj).checked?id.replace('cmd_opt_', ''):'';\n val = true;\n }\n else {\n opt = id.replace('cmd_opt_', '');\n val = $(obj).val();\n val = `${val.includes(\" \") ? '\"' : ''}${val}${val.includes(\" \") ? '\"' : ''}`;\n }\n\n return { opt, val };\n}\nfunction handleNVSVisible() {\n let nvs_previous_checked = isEnabled(Cookies.get(\"show-nvs\"));\n $('input#show-nvs')[0].checked = nvs_previous_checked;\n if ($('input#show-nvs')[0].checked || recovery) {\n $('*[href*=\"-nvs\"]').show();\n } else {\n $('*[href*=\"-nvs\"]').hide();\n }\n}\nfunction concatenateOptions(options) {\n let commandLine = ' ';\n for (const [option, value] of Object.entries(options)) {\n if (option !== 'n' && option !== 'o') {\n commandLine += `-${option} `;\n if (value !== true) {\n commandLine += `${value} `;\n }\n }\n }\n return commandLine;\n}\n\nfunction isEnabled(val) {\n return val != undefined && typeof val === 'string' && val.match(\"[Yy1]\");\n}\n\nconst nvsTypes = {\n NVS_TYPE_U8: 0x01,\n /*! < Type uint8_t */\n NVS_TYPE_I8: 0x11,\n /*! < Type int8_t */\n NVS_TYPE_U16: 0x02,\n /*! < Type uint16_t */\n NVS_TYPE_I16: 0x12,\n /*! < Type int16_t */\n NVS_TYPE_U32: 0x04,\n /*! < Type uint32_t */\n NVS_TYPE_I32: 0x14,\n /*! < Type int32_t */\n NVS_TYPE_U64: 0x08,\n /*! < Type uint64_t */\n NVS_TYPE_I64: 0x18,\n /*! < Type int64_t */\n NVS_TYPE_STR: 0x21,\n /*! < Type string */\n NVS_TYPE_BLOB: 0x42,\n /*! < Type blob */\n NVS_TYPE_ANY: 0xff /*! < Must be last */,\n};\nconst btIcons = {\n bt_playing: { 'label': '', 'icon': 'media_bluetooth_on' },\n bt_disconnected: { 'label': '', 'icon': 'media_bluetooth_off' },\n bt_neutral: { 'label': '', 'icon': 'bluetooth' },\n bt_connecting: { 'label': '', 'icon': 'bluetooth_searching' },\n bt_connected: { 'label': '', 'icon': 'bluetooth_connected' },\n bt_disabled: { 'label': '', 'icon': 'bluetooth_disabled' },\n play_arrow: { 'label': '', 'icon': 'play_circle_filled' },\n pause: { 'label': '', 'icon': 'pause_circle' },\n stop: { 'label': '', 'icon': 'stop_circle' },\n '': { 'label': '', 'icon': '' }\n};\nconst batIcons = [\n { icon: \"battery_0_bar\", label: 'â–Ē', ranges: [{ f: 5.8, t: 6.8 }, { f: 8.8, t: 10.2 }] },\n { icon: \"battery_2_bar\", label: 'â–Ēâ–Ē', ranges: [{ f: 6.8, t: 7.4 }, { f: 10.2, t: 11.1 }] },\n { icon: \"battery_3_bar\", label: 'â–Ēâ–Ēâ–Ē', ranges: [{ f: 7.4, t: 7.5 }, { f: 11.1, t: 11.25 }] },\n { icon: \"battery_4_bar\", label: 'â–Ēâ–Ēâ–Ēâ–Ē', ranges: [{ f: 7.5, t: 7.8 }, { f: 11.25, t: 11.7 }] }\n];\nconst btStateIcons = [\n { desc: 'Idle', sub: ['bt_neutral'] },\n { desc: 'Discovering', sub: ['bt_connecting'] },\n { desc: 'Discovered', sub: ['bt_connecting'] },\n { desc: 'Unconnected', sub: ['bt_disconnected'] },\n { desc: 'Connecting', sub: ['bt_connecting'] },\n {\n desc: 'Connected',\n sub: ['bt_connected', 'play_arrow', 'bt_playing', 'pause', 'stop'],\n },\n { desc: 'Disconnecting', sub: ['bt_disconnected'] },\n];\n\nconst pillcolors = {\n MESSAGING_INFO: 'badge-success',\n MESSAGING_WARNING: 'badge-warning',\n MESSAGING_ERROR: 'badge-danger',\n};\nconst connectReturnCode = {\n OK: 0,\n FAIL: 1,\n DISC: 2,\n LOST: 3,\n RESTORE: 4,\n ETH: 5\n}\nconst taskStates = {\n 0: 'eRunning',\n /*! < A task is querying the state of itself, so must be running. */\n 1: 'eReady',\n /*! < The task being queried is in a read or pending ready list. */\n 2: 'eBlocked',\n /*! < The task being queried is in the Blocked state. */\n 3: 'eSuspended',\n /*! < The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */\n 4: 'eDeleted',\n};\nlet flashState = {\n NONE: 0,\n REBOOT_TO_RECOVERY: 2,\n SET_FWURL: 5,\n FLASHING: 6,\n DONE: 7,\n UPLOADING: 8,\n ERROR: 9,\n UPLOADCOMPLETE: 10,\n _state: -1,\n olderRecovery: false,\n statusText: '',\n flashURL: '',\n flashFileName: '',\n statusPercent: 0,\n Completed: false,\n recovery: false,\n prevRecovery: false,\n updateModal: new bootstrap.Modal(document.getElementById('otadiv'), {}),\n reset: function () {\n\n this.olderRecovery = false;\n this.statusText = '';\n this.statusPercent = -1;\n this.flashURL = '';\n this.flashFileName = undefined;\n this.UpdateProgress();\n $('#rTable tr.release').removeClass('table-success table-warning');\n $('.flact').prop('disabled', false);\n $('#flashfilename').value = null;\n $('#fw-url-input').value = null;\n if (!this.isStateError()) {\n $('span#flash-status').html('');\n $('#fwProgressLabel').parent().removeClass('bg-danger');\n }\n this._state = this.NONE\n return this;\n },\n isStateUploadComplete: function () {\n return this._state == this.UPLOADCOMPLETE;\n },\n isStateError: function () {\n return this._state == this.ERROR;\n },\n isStateNone: function () {\n return this._state == this.NONE;\n },\n isStateRebootRecovery: function () {\n return this._state == this.REBOOT_TO_RECOVERY;\n },\n isStateSetUrl: function () {\n return this._state == this.SET_FWURL;\n },\n isStateFlashing: function () {\n return this._state == this.FLASHING;\n },\n isStateDone: function () {\n return this._state == this.DONE;\n },\n isStateUploading: function () {\n return this._state == this.UPLOADING;\n },\n init: function () {\n this._state = this.NONE;\n return this;\n },\n\n SetStateError: function () {\n this._state = this.ERROR;\n $('#fwProgressLabel').parent().addClass('bg-danger');\n return this;\n },\n SetStateNone: function () {\n this._state = this.NONE;\n return this;\n },\n SetStateRebootRecovery: function () {\n this._state = this.REBOOT_TO_RECOVERY;\n // Reboot system to recovery mode\n this.SetStatusText('Starting recovery mode.')\n $.ajax({\n url: '/recovery.json',\n context: this,\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n timestamp: Date.now(),\n }),\n error: function (xhr, _ajaxOptions, thrownError) {\n this.setOTAError(`Unexpected error while trying to restart to recovery. (status=${xhr.status ?? ''}, error=${thrownError ?? ''} ) `);\n },\n complete: function (response) {\n this.SetStatusText('Waiting for system to boot.')\n },\n });\n return this;\n },\n SetStateSetUrl: function () {\n this._state = this.SET_FWURL;\n this.statusText = 'Sending firmware download location.';\n let confData = {\n fwurl: {\n value: this.flashURL,\n type: 33,\n }\n };\n post_config(confData);\n return this;\n },\n SetStateFlashing: function () {\n this._state = this.FLASHING;\n return this;\n },\n SetStateDone: function () {\n this._state = this.DONE;\n this.reset();\n return this;\n },\n SetStateUploading: function () {\n this._state = this.UPLOADING;\n return this.SetStatusText('Sending file to device.');\n },\n SetStateUploadComplete: function () {\n this._state = this.UPLOADCOMPLETE;\n return this;\n },\n\n isFlashExecuting: function () {\n return true === (this._state != this.UPLOADING && (this.statusText !== '' || this.statusPercent >= 0));\n },\n\n\n\n toString: function () {\n let keys = Object.keys(this);\n return keys.find(x => this[x] === this._state);\n },\n\n setOTATargets: function () {\n this.flashURL = '';\n this.flashFileName = '';\n this.flashURL = $('#fw-url-input').val();\n let fileInput = $('#flashfilename')[0].files;\n if (fileInput.length > 0) {\n this.flashFileName = fileInput[0];\n }\n if (this.flashFileName.length == 0 && this.flashURL.length == 0) {\n this.setOTAError('Invalid url or file. Cannot start OTA');\n }\n return this;\n },\n\n setOTAError: function (message) {\n this.SetStateError().SetStatusPercent(0).SetStatusText(message).reset();\n return this;\n },\n\n ShowDialog: function () {\n if (!this.isStateNone()) {\n this.updateModal.show();\n $('.flact').prop('disabled', true);\n }\n return this;\n },\n\n SetStatusPercent: function (pct) {\n var pctChanged = (this.statusPercent != pct);\n this.statusPercent = pct;\n if (pctChanged) {\n if (!this.isStateUploading() && !this.isStateFlashing()) {\n this.SetStateFlashing();\n }\n if (pct == 100) {\n if (this.isStateFlashing()) {\n this.SetStateDone();\n }\n else if (this.isStateUploading()) {\n this.statusPercent = 0;\n this.SetStateFlashing();\n }\n }\n this.UpdateProgress().ShowDialog();\n }\n return this;\n },\n SetStatusText: function (txt) {\n var changed = (this.statusText != txt);\n this.statusText = txt;\n if (changed) {\n $('span#flash-status').html(this.statusText);\n this.ShowDialog();\n }\n\n return this;\n },\n UpdateProgress: function () {\n $('.progress-bar')\n .css('width', this.statusPercent + '%')\n .attr('aria-valuenow', this.statusPercent)\n .text(this.statusPercent + '%')\n $('.progress-bar').html((this.isStateDone() ? 100 : this.statusPercent) + '%');\n return this;\n },\n StartOTA: function () {\n this.logEvent(this.StartOTA.name);\n $('#fwProgressLabel').parent().removeClass('bg-danger');\n this.setOTATargets();\n if (this.isStateError()) {\n return this;\n }\n if (!recovery) {\n this.SetStateRebootRecovery();\n }\n else {\n this.SetStateFlashing().TargetReadyStartOTA();\n }\n\n return this;\n },\n UploadLocalFile: function () {\n this.SetStateUploading();\n const xhttp = new XMLHttpRequest();\n xhttp.context = this;\n var boundHandleUploadProgressEvent = this.HandleUploadProgressEvent.bind(this);\n var boundsetOTAError = this.setOTAError.bind(this);\n xhttp.upload.addEventListener(\"progress\", boundHandleUploadProgressEvent, false);\n xhttp.onreadystatechange = function () {\n if (xhttp.readyState === 4) {\n if (xhttp.status === 0 || xhttp.status === 404) {\n boundsetOTAError(`Upload Failed. Recovery version might not support uploading. Please use web update instead.`);\n }\n }\n };\n xhttp.open('POST', '/flash.json', true);\n xhttp.send(this.flashFileName);\n },\n TargetReadyStartOTA: function () {\n if (recovery && this.prevRecovery && !this.isStateRebootRecovery() && !this.isStateFlashing()) {\n // this should only execute once, while being in a valid state\n return this;\n }\n\n this.logEvent(this.TargetReadyStartOTA.name);\n if (!recovery) {\n console.error('Event TargetReadyStartOTA fired in the wrong mode ');\n return this;\n }\n this.prevRecovery = true;\n\n if (this.flashFileName !== '') {\n this.UploadLocalFile();\n }\n else if (this.flashURL != '') {\n this.SetStateSetUrl();\n }\n else {\n this.setOTAError('Invalid URL or file name while trying to start the OTa process')\n }\n },\n HandleUploadProgressEvent: function (data) {\n this.logEvent(this.HandleUploadProgressEvent.name);\n this.SetStateUploading().SetStatusPercent(Math.round(data.loaded / data.total * 100)).SetStatusText('Uploading file to device');\n },\n EventTargetStatus: function (data) {\n if (!this.isStateNone()) {\n this.logEvent(this.EventTargetStatus.name);\n }\n if (data.ota_pct ?? -1 >= 0) {\n this.olderRecovery = true;\n this.SetStatusPercent(data.ota_pct);\n }\n if ((data.ota_dsc ?? '') != '') {\n this.olderRecovery = true;\n this.SetStatusText(data.ota_dsc);\n }\n\n if (data.recovery != undefined) {\n this.recovery = data.recovery === 1 ? true : false;\n }\n if (this.isStateRebootRecovery() && this.recovery) {\n this.TargetReadyStartOTA();\n }\n },\n EventOTAMessageClass: function (data) {\n this.logEvent(this.EventOTAMessageClass.name);\n var otaData = JSON.parse(data);\n this.SetStatusPercent(otaData.ota_pct).SetStatusText(otaData.ota_dsc);\n },\n logEvent: function (fun) {\n console.log(`${fun}, flash state ${this.toString()}, recovery: ${this.recovery}, ota pct: ${this.statusPercent}, ota desc: ${this.statusText}`);\n }\n\n};\nwindow.hideSurrounding = function (obj) {\n $(obj).parent().parent().hide();\n}\n\nlet presetsloaded = false;\nlet is_i2c_locked = false;\nlet statusInterval = 2000;\nlet messageInterval = 2500;\nfunction post_config(data) {\n let confPayload = {\n timestamp: Date.now(),\n config: data\n };\n $.ajax({\n url: '/config.json',\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify(confPayload),\n error: handleExceptionResponse,\n });\n}\n\n\nwindow.hFlash = function () {\n // reset file upload selection if any;\n $('#flashfilename').value = null\n flashState.StartOTA();\n}\nwindow.handleReboot = function (link) {\n if (link == 'reboot_ota') {\n $('#reboot_ota_nav').removeClass('active').prop(\"disabled\", true); delayReboot(500, '', 'reboot_ota');\n }\n else {\n $('#reboot_nav').removeClass('active'); delayReboot(500, '', link);\n }\n}\n\nfunction parseSqueezeliteCommandLine(commandLine) {\n const options = {};\n let output, name;\n let otherValues = '';\n\n const argRegex = /(\"[^\"]+\"|'[^']+'|\\S+)/g;\n const args = commandLine.match(argRegex);\n\n let i = 0;\n\n while (i < args.length) {\n const arg = args[i];\n\n if (arg.startsWith('-')) {\n const option = arg.slice(1);\n\n if (option === '') {\n otherValues += args.slice(i).join(' ');\n break;\n }\n\n let value = true;\n\n if (i + 1 < args.length && !args[i + 1].startsWith('-')) {\n value = args[i + 1].replace(/\"/g, '').replace(/'/g, '');\n i++;\n }\n\n options[option] = value;\n } else {\n otherValues += arg + ' ';\n }\n\n i++;\n }\n\n otherValues = otherValues.trim();\n output = getOutput(options);\n name = getName(options);\n let otherOptions={btname:null,n:null};\n // assign o and n options to otheroptions if present\n if (options.o && output.toUpperCase() === 'BT') {\n let temp = parseSqueezeliteCommandLine(options.o);\n if(temp.name) {\n otherOptions.btname = temp.name;\n }\n delete options.o;\n }\n if (options.n) {\n otherOptions['n'] = options.n;\n delete options.n;\n }\n return { name, output, options, otherValues,otherOptions }; \n}\n\nfunction getOutput(options) {\n let output;\n if (options.o){\n output = options.o.replace(/\"/g, '').replace(/'/g, '');\n /* set output as the first alphanumerical word in the command line */\n if (output.indexOf(' ') > 0) {\n output = output.substring(0, output.indexOf(' '));\n }\n }\n return output;\n}\n\nfunction getName(options) {\n let name;\n /* if n option present, assign to name variable */\n if (options.n){\n name = options.n.replace(/\"/g, '').replace(/'/g, '');\n }\n return name;\n}\n\n\nfunction isConnected() {\n return ConnectedTo.hasOwnProperty('ip') && ConnectedTo.ip != '0.0.0.0' && ConnectedTo.ip != '';\n}\nfunction getIcon(icons) {\n return isConnected() ? icons.icon : icons.label;\n}\nfunction handlebtstate(data) {\n let icon = '';\n let tt = '';\n if (data.bt_status !== undefined && data.bt_sub_status !== undefined) {\n const iconindex = btStateIcons[data.bt_status].sub[data.bt_sub_status];\n if (iconindex) {\n icon = btIcons[iconindex];\n tt = btStateIcons[data.bt_status].desc;\n } else {\n icon = btIcons.bt_connected;\n tt = 'Output status';\n }\n }\n\n $('#o_type').attr('title', tt);\n $('#o_bt').html(isConnected() ? icon.label : icon.text);\n}\nfunction handleTemplateTypeRadio(outtype) {\n $('#o_type').children('span').css({ display: 'none' });\n let changed = false;\n if (outtype === 'bt') {\n changed = output !== 'bt' && output !== '';\n output = 'bt';\n } else if (outtype === 'spdif') {\n changed = output !== 'spdif' && output !== '';\n output = 'spdif';\n } else {\n changed = output !== 'i2s' && output !== '';\n output = 'i2s';\n }\n $('#' + output).prop('checked', true);\n $('#o_' + output).css({ display: 'inline' });\n if (changed) {\n Object.keys(commandDefaults[output]).forEach(function (key) {\n $(`#cmd_opt_${key}`).val(commandDefaults[output][key]);\n });\n }\n}\n\nfunction handleExceptionResponse(xhr, _ajaxOptions, thrownError) {\n console.log(xhr.status);\n console.log(thrownError);\n if (thrownError !== '') {\n showLocalMessage(thrownError, 'MESSAGING_ERROR');\n }\n}\nfunction HideCmdMessage(cmdname) {\n $('#toast_' + cmdname)\n .removeClass('table-success')\n .removeClass('table-warning')\n .removeClass('table-danger')\n .addClass('table-success')\n .removeClass('show');\n $('#msg_' + cmdname).html('');\n}\nfunction showCmdMessage(cmdname, msgtype, msgtext, append = false) {\n let color = 'table-success';\n if (msgtype === 'MESSAGING_WARNING') {\n color = 'table-warning';\n } else if (msgtype === 'MESSAGING_ERROR') {\n color = 'table-danger';\n }\n $('#toast_' + cmdname)\n .removeClass('table-success')\n .removeClass('table-warning')\n .removeClass('table-danger')\n .addClass(color)\n .addClass('show');\n let escapedtext = msgtext\n .substring(0, msgtext.length - 1)\n .encodeHTML()\n .replace(/\\n/g, '
');\n escapedtext =\n ($('#msg_' + cmdname).html().length > 0 && append\n ? $('#msg_' + cmdname).html() + '
'\n : '') + escapedtext;\n $('#msg_' + cmdname).html(escapedtext);\n}\n\nlet releaseURL =\n 'https://api.github.com/repos/sle118/squeezelite-esp32/releases';\n\nlet recovery = false;\nlet messagesHeld = false;\nlet commandBTSinkName = '';\nconst commandHeader = 'squeezelite ';\nconst commandDefaults = {\n i2s: { b: \"500:2000\", C: \"30\", W: \"\", Z: \"96000\", o: \"I2S\" },\n spdif: { b: \"500:2000\", C: \"30\", W: \"\", Z: \"48000\", o: \"SPDIF\" },\n bt: { b: \"500:2000\", C: \"30\", W: \"\", Z: \"44100\", o: \"BT\" },\n};\nlet validOptions = {\n codecs: ['flac', 'pcm', 'mp3', 'ogg', 'aac', 'wma', 'alac', 'dsd', 'mad', 'mpg']\n};\n\n//let blockFlashButton = false;\nlet apList = null;\n//let selectedSSID = '';\n//let checkStatusInterval = null;\nlet messagecount = 0;\nlet messageseverity = 'MESSAGING_INFO';\nlet SystemConfig = {};\nlet LastCommandsState = null;\nvar output = '';\nlet hostName = '';\nlet versionName = 'Squeezelite-ESP32';\nlet prevmessage = '';\nlet project_name = versionName;\nlet depth = 16;\nlet board_model = '';\nlet platform_name = versionName;\nlet preset_name = '';\nlet btSinkNamesOptSel = '#cfg-audio-bt_source-sink_name';\nlet ConnectedTo = {};\nlet ConnectingToSSID = {};\nlet lmsBaseUrl;\nlet prevLMSIP = '';\nconst ConnectingToActions = {\n 'CONN': 0, 'MAN': 1, 'STS': 2,\n}\n\nPromise.prototype.delay = function (duration) {\n return this.then(\n function (value) {\n return new Promise(function (resolve) {\n setTimeout(function () {\n resolve(value);\n }, duration);\n });\n },\n function (reason) {\n return new Promise(function (_resolve, reject) {\n setTimeout(function () {\n reject(reason);\n }, duration);\n });\n }\n );\n};\n\nfunction getConfigJson(slimMode) {\n const config = {};\n $('input.nvs').each(function (_index, entry) {\n if (!slimMode) {\n const nvsType = parseInt(entry.attributes.nvs_type.value, 10);\n if (entry.id !== '') {\n config[entry.id] = {};\n if (\n nvsType === nvsTypes.NVS_TYPE_U8 ||\n nvsType === nvsTypes.NVS_TYPE_I8 ||\n nvsType === nvsTypes.NVS_TYPE_U16 ||\n nvsType === nvsTypes.NVS_TYPE_I16 ||\n nvsType === nvsTypes.NVS_TYPE_U32 ||\n nvsType === nvsTypes.NVS_TYPE_I32 ||\n nvsType === nvsTypes.NVS_TYPE_U64 ||\n nvsType === nvsTypes.NVS_TYPE_I64\n ) {\n config[entry.id].value = parseInt(entry.value);\n } else {\n config[entry.id].value = entry.value;\n }\n config[entry.id].type = nvsType;\n }\n } else {\n config[entry.id] = entry.value;\n }\n });\n const key = $('#nvs-new-key').val();\n const val = $('#nvs-new-value').val();\n if (key !== '') {\n if (!slimMode) {\n config[key] = {};\n config[key].value = val;\n config[key].type = 33;\n } else {\n config[key] = val;\n }\n }\n return config;\n}\n\nfunction handleHWPreset(allfields, reboot) {\n\n const selJson = JSON.parse(allfields[0].value);\n var cmd = allfields[0].attributes.cmdname.value;\n\n console.log(`selected model: ${selJson.name}`);\n let confPayload = {\n timestamp: Date.now(),\n config: { model_config: { value: selJson.name, type: 33 } }\n };\n for (const [name, value] of Object.entries(selJson.config)) {\n const storedval = (typeof value === 'string' || value instanceof String) ? value : JSON.stringify(value);\n confPayload.config[name] = {\n value: storedval,\n type: 33,\n }\n showCmdMessage(\n cmd,\n 'MESSAGING_INFO',\n `Setting ${name}=${storedval} `,\n true\n );\n }\n\n showCmdMessage(\n cmd,\n 'MESSAGING_INFO',\n `Committing `,\n true\n );\n $.ajax({\n url: '/config.json',\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify(confPayload),\n error: function (xhr, _ajaxOptions, thrownError) {\n handleExceptionResponse(xhr, _ajaxOptions, thrownError);\n showCmdMessage(\n cmd,\n 'MESSAGING_ERROR',\n `Unexpected error ${(thrownError !== '') ? thrownError : 'with return status = ' + xhr.status} `,\n true\n );\n },\n success: function (response) {\n showCmdMessage(\n cmd,\n 'MESSAGING_INFO',\n `Saving complete `,\n true\n );\n console.log(response);\n if (reboot) {\n delayReboot(2500, cmd);\n }\n },\n });\n}\n\n\n// pull json file from https://gist.githubusercontent.com/sle118/dae585e157b733a639c12dc70f0910c5/raw/b462691f69e2ad31ac95c547af6ec97afb0f53db/squeezelite-esp32-presets.json and\nfunction loadPresets() {\n if ($(\"#cfg-hw-preset-model_config\").length == 0) return;\n if (presetsloaded) return;\n presetsloaded = true;\n $('#cfg-hw-preset-model_config').html('');\n $.getJSON(\n 'https://gist.githubusercontent.com/sle118/dae585e157b733a639c12dc70f0910c5/raw/',\n { _: new Date().getTime() },\n function (data) {\n $.each(data, function (key, val) {\n $('#cfg-hw-preset-model_config').append(``);\n if (preset_name !== '' && preset_name == val.name) {\n $('#cfg-hw-preset-model_config').val(preset_name);\n }\n });\n if (preset_name !== '') {\n ('#prev_preset').show().val(preset_name);\n }\n }\n\n ).fail(function (jqxhr, textStatus, error) {\n const err = textStatus + ', ' + error;\n console.log('Request Failed: ' + err);\n }\n );\n}\n\nfunction delayReboot(duration, cmdname, ota = 'reboot') {\n const url = '/' + ota + '.json';\n $('tbody#tasks').empty();\n $('#tasks_sect').css('visibility', 'collapse');\n Promise.resolve({ cmdname: cmdname, url: url })\n .delay(duration)\n .then(function (data) {\n if (data.cmdname.length > 0) {\n showCmdMessage(\n data.cmdname,\n 'MESSAGING_WARNING',\n 'System is rebooting.\\n',\n true\n );\n } else {\n showLocalMessage('System is rebooting.\\n', 'MESSAGING_WARNING');\n }\n console.log('now triggering reboot');\n $(\"button[onclick*='handleReboot']\").addClass('rebooting');\n $.ajax({\n url: data.url,\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n timestamp: Date.now(),\n }),\n error: handleExceptionResponse,\n complete: function () {\n console.log('reboot call completed');\n Promise.resolve(data)\n .delay(6000)\n .then(function (rdata) {\n if (rdata.cmdname.length > 0) {\n HideCmdMessage(rdata.cmdname);\n }\n getCommands();\n getConfig();\n });\n },\n });\n });\n}\n// eslint-disable-next-line no-unused-vars\nwindow.saveAutoexec1 = function (apply) {\n showCmdMessage('cfg-audio-tmpl', 'MESSAGING_INFO', 'Saving.\\n', false);\n let commandLine = `${commandHeader} -o ${output} `;\n $('.sqcmd').each(function () {\n let { opt, val } = get_control_option_value($(this));\n if ((opt && opt.length>0 ) && typeof(val) == 'boolean' || val.length > 0) {\n const optStr=opt===':'?opt:(` -${opt} `);\n val = typeof(val) == 'boolean'?'':val;\n commandLine += `${optStr} ${val}`;\n }\n });\n const resample=$('#cmd_opt_R input[name=resample]:checked');\n if (resample.length>0 && resample.attr('suffix')!=='') {\n commandLine += resample.attr('suffix');\n // now check resample_i option and if checked, add suffix to command line\n if ($('#resample_i').is(\":checked\") && resample.attr('aint') =='true') {\n commandLine += $('#resample_i').attr('suffix');\n }\n}\n\n \n if (output === 'bt') {\n showCmdMessage(\n 'cfg-audio-tmpl',\n 'MESSAGING_INFO',\n 'Remember to configure the Bluetooth audio device name.\\n',\n true\n );\n }\n commandLine += concatenateOptions(options);\n const data = {\n timestamp: Date.now(),\n };\n data.config = {\n autoexec1: { value: commandLine, type: 33 },\n autoexec: {\n value: $('#disable-squeezelite').prop('checked') ? '0' : '1',\n type: 33,\n },\n };\n\n $.ajax({\n url: '/config.json',\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify(data),\n error: handleExceptionResponse,\n complete: function (response) {\n if (\n response.responseText &&\n JSON.parse(response.responseText).result === 'OK'\n ) {\n showCmdMessage('cfg-audio-tmpl', 'MESSAGING_INFO', 'Done.\\n', true);\n if (apply) {\n delayReboot(1500, 'cfg-audio-tmpl');\n }\n } else if (JSON.parse(response.responseText).result) {\n showCmdMessage(\n 'cfg-audio-tmpl',\n 'MESSAGING_WARNING',\n JSON.parse(response.responseText).Result + '\\n',\n true\n );\n } else {\n showCmdMessage(\n 'cfg-audio-tmpl',\n 'MESSAGING_ERROR',\n response.statusText + '\\n'\n );\n }\n console.log(response.responseText);\n },\n });\n console.log('sent data:', JSON.stringify(data));\n}\nwindow.handleDisconnect = function () {\n $.ajax({\n url: '/connect.json',\n dataType: 'text',\n method: 'DELETE',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n timestamp: Date.now(),\n }),\n });\n}\nfunction setPlatformFilter(val) {\n if ($('.upf').filter(function () { return $(this).text().toUpperCase() === val.toUpperCase() }).length > 0) {\n $('#splf').val(val).trigger('input');\n return true;\n }\n return false;\n}\nwindow.handleConnect = function () {\n ConnectingToSSID.ssid = $('#manual_ssid').val();\n ConnectingToSSID.pwd = $('#manual_pwd').val();\n ConnectingToSSID.dhcpname = $('#dhcp-name2').val();\n $(\"*[class*='connecting']\").hide();\n $('#ssid-wait').text(ConnectingToSSID.ssid);\n $('.connecting').show();\n $.ajax({\n url: '/connect.json',\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n timestamp: Date.now(),\n ssid: ConnectingToSSID.ssid,\n pwd: ConnectingToSSID.pwd\n }),\n error: handleExceptionResponse,\n });\n\n // now we can re-set the intervals regardless of result\n\n}\nfunction renderError(opt,error){\n const fieldname = `cmd_opt_${opt}`;\n let errorFieldName=`${fieldname}-error`;\n let errorField=$(`#${errorFieldName}`);\n let field=$(`#${fieldname}`);\n \n if (!errorField || errorField.length ==0) {\n field.after(`
`);\n errorField=$(`#${errorFieldName}`);\n }\n if(error.length ==0){\n errorField.hide();\n field.removeClass('is-invalid');\n field.addClass('is-valid');\n errorField.text('');\n }\n else { \n errorField.show();\n errorField.text(error);\n field.removeClass('is-valid');\n field.addClass('is-invalid');\n }\n return errorField;\n}\n$(document).ready(function () {\n $('.material-icons').each(function (_index, entry) {\n entry.attributes['icon'] = entry.textContent;\n });\n setIcons(true);\n handleNVSVisible();\n flashState.init();\n $('#fw-url-input').on('input', function () {\n if ($(this).val().length > 8 && ($(this).val().startsWith('http://') || $(this).val().startsWith('https://'))) {\n $('#start-flash').show();\n }\n else {\n $('#start-flash').hide();\n }\n });\n $('.upSrch').on('input', function () {\n const val = this.value;\n $(\"#rTable tr\").removeClass(this.id + '_hide');\n if (val.length > 0) {\n $(`#rTable td:nth-child(${$(this).parent().index() + 1})`).filter(function () {\n return !$(this).text().toUpperCase().includes(val.toUpperCase());\n }).parent().addClass(this.id + '_hide');\n }\n $('[class*=\"_hide\"]').hide();\n $('#rTable tr').not('[class*=\"_hide\"]').show()\n\n });\n setTimeout(refreshAP, 1500);\n /* add validation for cmd_opt_c, which accepts a comma separated list. \n getting known codecs from validOptions.codecs array\n use bootstrap classes to highlight the error with an overlay message */\n $('#options input').on('input', function () {\n const { opt, val } = get_control_option_value(this);\n if (opt === 'c' || opt === 'e') {\n const fieldname = `cmd_opt_${opt}_codec-error`;\n \n const values = val.split(',').map(function (item) {\n return item.trim();\n });\n /* get a list of invalid codecs */\n const invalid = values.filter(function (item) {\n return !validOptions.codecs.includes(item);\n });\n renderError(opt,invalid.length > 0 ? `Invalid codec(s) ${invalid.join(', ')}` : '');\n }\n /* add validation for cmd_opt_m, which accepts a mac_address */\n if (opt === 'm') {\n const mac_regex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;\n renderError(opt,mac_regex.test(val) ? '' : 'Invalid MAC address');\n }\n if (opt === 'r') {\n const rateRegex = /^(\\d+\\.?\\d*|\\.\\d+)-(\\d+\\.?\\d*|\\.\\d+)$|^(\\d+\\.?\\d*)$|^(\\d+\\.?\\d*,)+\\d+\\.?\\d*$/;\n renderError(opt,rateRegex.test(val)?'':`Invalid rate(s) ${val}. Acceptable format: |-|,,`);\n }\n\n\n\n }\n\n\n );\n\n\n\n\n\n $('#WifiConnectDialog')[0].addEventListener('shown.bs.modal', function (event) {\n $(\"*[class*='connecting']\").hide();\n\n if (event?.relatedTarget) {\n ConnectingToSSID.Action = ConnectingToActions.CONN;\n if ($(event.relatedTarget).children('td:eq(1)').text() == ConnectedTo.ssid) {\n ConnectingToSSID.Action = ConnectingToActions.STS;\n }\n else {\n if (!$(event.relatedTarget).is(':last-child')) {\n ConnectingToSSID.ssid = $(event.relatedTarget).children('td:eq(1)').text();\n $('#manual_ssid').val(ConnectingToSSID.ssid);\n }\n else {\n ConnectingToSSID.Action = ConnectingToActions.MAN;\n ConnectingToSSID.ssid = '';\n $('#manual_ssid').val(ConnectingToSSID.ssid);\n }\n }\n }\n\n\n if (ConnectingToSSID.Action !== ConnectingToActions.STS) {\n $('.connecting-init').show();\n $('#manual_ssid').trigger('focus');\n }\n else {\n handleWifiDialog();\n }\n });\n\n $('#WifiConnectDialog')[0].addEventListener('hidden.bs.modal', function () {\n $('#WifiConnectDialog input').val('');\n });\n\n $('#uCnfrm')[0].addEventListener('shown.bs.modal', function () {\n $('#selectedFWURL').text($('#fw-url-input').val());\n });\n\n $('input#show-commands')[0].checked = LastCommandsState === 1;\n $('a[href^=\"#tab-commands\"]').hide();\n $('#load-nvs').on('click', function () {\n $('#nvsfilename').trigger('click');\n });\n $('#nvsfilename').on('change', function () {\n if (typeof window.FileReader !== 'function') {\n throw \"The file API isn't supported on this browser.\";\n }\n if (!this.files) {\n throw 'This browser does not support the `files` property of the file input.';\n }\n if (!this.files[0]) {\n return undefined;\n }\n\n const file = this.files[0];\n let fr = new FileReader();\n fr.onload = function (e) {\n let data = {};\n try {\n data = JSON.parse(e.target.result);\n } catch (ex) {\n alert('Parsing failed!\\r\\n ' + ex);\n }\n $('input.nvs').each(function (_index, entry) {\n $(this).parent().removeClass('bg-warning').removeClass('bg-success');\n if (data[entry.id]) {\n if (data[entry.id] !== entry.value) {\n console.log(\n 'Changed ' + entry.id + ' ' + entry.value + '==>' + data[entry.id]\n );\n $(this).parent().addClass('bg-warning');\n $(this).val(data[entry.id]);\n }\n else {\n $(this).parent().addClass('bg-success');\n }\n }\n });\n var changed = $(\"input.nvs\").children('.bg-warning');\n if (changed) {\n alert('Highlighted values were changed. Press Commit to change on the device');\n }\n }\n fr.readAsText(file);\n this.value = null;\n\n }\n );\n $('#clear-syslog').on('click', function () {\n messagecount = 0;\n messageseverity = 'MESSAGING_INFO';\n $('#msgcnt').text('');\n $('#syslogTable').html('');\n });\n\n $('#ok-credits').on('click', function () {\n $('#credits').slideUp('fast', function () { });\n $('#app').slideDown('fast', function () { });\n });\n\n $('#acredits').on('click', function (event) {\n event.preventDefault();\n $('#app').slideUp('fast', function () { });\n $('#credits').slideDown('fast', function () { });\n });\n\n $('input#show-commands').on('click', function () {\n this.checked = this.checked ? 1 : 0;\n if (this.checked) {\n $('a[href^=\"#tab-commands\"]').show();\n LastCommandsState = 1;\n } else {\n LastCommandsState = 0;\n $('a[href^=\"#tab-commands\"]').hide();\n }\n });\n\n $('input#show-nvs').on('click', function () {\n this.checked = this.checked ? 1 : 0;\n Cookies.set(\"show-nvs\", this.checked ? 'Y' : 'N');\n handleNVSVisible();\n });\n $('#btn_reboot_recovery').on('click', function () {\n handleReboot('recovery');\n });\n $('#btn_reboot').on('click', function () {\n handleReboot('reboot');\n });\n $('#btn_flash').on('click', function () {\n hFlash();\n });\n $('#save-autoexec1').on('click', function () {\n saveAutoexec1(false);\n });\n $('#commit-autoexec1').on('click', function () {\n saveAutoexec1(true);\n });\n $('#btn_disconnect').on('click', function () {\n ConnectedTo = {};\n refreshAPHTML2();\n $.ajax({\n url: '/connect.json',\n dataType: 'text',\n method: 'DELETE',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n timestamp: Date.now(),\n }),\n });\n });\n $('#btnJoin').on('click', function () {\n handleConnect();\n });\n $('#reboot_nav').on('click', function () {\n handleReboot('reboot');\n });\n $('#reboot_ota_nav').on('click', function () {\n handleReboot('reboot_ota');\n });\n\n $('#save-as-nvs').on('click', function () {\n const config = getConfigJson(true);\n const a = document.createElement('a');\n a.href = URL.createObjectURL(\n new Blob([JSON.stringify(config, null, 2)], {\n type: 'text/plain',\n })\n );\n a.setAttribute(\n 'download',\n 'nvs_config_' + hostName + '_' + Date.now() + 'json'\n );\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n });\n\n $('#save-nvs').on('click', function () {\n post_config(getConfigJson(false));\n });\n\n $('#fwUpload').on('click', function () {\n const fileInput = document.getElementById('flashfilename').files;\n if (fileInput.length === 0) {\n alert('No file selected!');\n } else {\n $('#fw-url-input').value = null;\n flashState.StartOTA();\n }\n\n });\n $('[name=output-tmpl]').on('click', function () {\n handleTemplateTypeRadio(this.id);\n });\n\n $('#chkUpdates').on('click', function () {\n $('#rTable').html('');\n $.getJSON(releaseURL, function (data) {\n let i = 0;\n const branches = [];\n data.forEach(function (release) {\n const namecomponents = release.name.split('#');\n const branch = namecomponents[3];\n if (!branches.includes(branch)) {\n branches.push(branch);\n }\n });\n let fwb = '';\n branches.forEach(function (branch) {\n fwb += '';\n });\n $('#fwbranch').append(fwb);\n\n data.forEach(function (release) {\n let url = '';\n release.assets.forEach(function (asset) {\n if (asset.name.match(/\\.bin$/)) {\n url = asset.browser_download_url;\n }\n });\n const namecomponents = release.name.split('#');\n const ver = namecomponents[0];\n const cfg = namecomponents[2];\n const branch = namecomponents[3];\n var bits = ver.substr(ver.lastIndexOf('-') + 1);\n bits = (bits == '32' || bits == '16') ? bits : '';\n\n let body = release.body;\n body = body.replace(/'/gi, '\"');\n body = body.replace(\n /[\\s\\S]+(### Revision Log[\\s\\S]+)### ESP-IDF Version Used[\\s\\S]+/,\n '$1'\n );\n body = body.replace(/- \\(.+?\\) /g, '- ').encodeHTML();\n $('#rTable').append(`\n ${ver}${new Date(release.created_at).toLocalShort()}\n ${cfg}${branch}${bits}`\n );\n });\n if (i > 7) {\n $('#releaseTable').append(\n \"\" +\n \"\" +\n \"\" +\n '' +\n ''\n );\n $('#showallbutton').on('click', function () {\n $('tr.hide').removeClass('hide');\n $('tr#showall').addClass('hide');\n });\n }\n $('#searchfw').css('display', 'inline');\n if (!setPlatformFilter(platform_name)) {\n setPlatformFilter(project_name)\n }\n $('#rTable tr.release').on('click', function () {\n var url = this.attributes['fwurl'].value;\n if (lmsBaseUrl) {\n url = url.replace(/.*\\/download\\//, lmsBaseUrl + '/plugins/SqueezeESP32/firmware/');\n }\n $('#fw-url-input').val(url);\n $('#start-flash').show();\n $('#rTable tr.release').removeClass('table-success table-warning');\n $(this).addClass('table-success table-warning');\n });\n\n }).fail(function () {\n alert('failed to fetch release history!');\n });\n });\n $('#fwcheck').on('click', function () {\n $('#releaseTable').html('');\n $('#fwbranch').empty();\n $.getJSON(releaseURL, function (data) {\n let i = 0;\n const branches = [];\n data.forEach(function (release) {\n const namecomponents = release.name.split('#');\n const branch = namecomponents[3];\n if (!branches.includes(branch)) {\n branches.push(branch);\n }\n });\n let fwb;\n branches.forEach(function (branch) {\n fwb += '';\n });\n $('#fwbranch').append(fwb);\n\n data.forEach(function (release) {\n let url = '';\n release.assets.forEach(function (asset) {\n if (asset.name.match(/\\.bin$/)) {\n url = asset.browser_download_url;\n }\n });\n const namecomponents = release.name.split('#');\n const ver = namecomponents[0];\n const idf = namecomponents[1];\n const cfg = namecomponents[2];\n const branch = namecomponents[3];\n\n let body = release.body;\n body = body.replace(/'/gi, '\"');\n body = body.replace(\n /[\\s\\S]+(### Revision Log[\\s\\S]+)### ESP-IDF Version Used[\\s\\S]+/,\n '$1'\n );\n body = body.replace(/- \\(.+?\\) /g, '- ');\n const trclass = i++ > 6 ? ' hide' : '';\n $('#releaseTable').append(\n \"\" +\n \"\" +\n ver +\n '' +\n '' +\n new Date(release.created_at).toLocalShort() +\n '' +\n '' +\n cfg +\n '' +\n '' +\n idf +\n '' +\n '' +\n branch +\n '' +\n \"\" +\n ''\n );\n });\n if (i > 7) {\n $('#releaseTable').append(\n \"\" +\n \"\" +\n \"\" +\n '' +\n ''\n );\n $('#showallbutton').on('click', function () {\n $('tr.hide').removeClass('hide');\n $('tr#showall').addClass('hide');\n });\n }\n $('#searchfw').css('display', 'inline');\n }).fail(function () {\n alert('failed to fetch release history!');\n });\n });\n\n $('#updateAP').on('click', function () {\n refreshAP();\n console.log('refresh AP');\n });\n\n // first time the page loads: attempt to get the connection status and start the wifi scan\n getConfig();\n getCommands();\n getMessages();\n checkStatus();\n\n});\n\n// eslint-disable-next-line no-unused-vars\nwindow.setURL = function (button) {\n let url = button.dataset.url;\n\n $('[data-bs-url^=\"http\"]')\n .addClass('btn-success')\n .removeClass('btn-danger');\n $('[data-bs-url=\"' + url + '\"]')\n .addClass('btn-danger')\n .removeClass('btn-success');\n\n // if user can proxy download through LMS, modify the URL\n if (lmsBaseUrl) {\n url = url.replace(/.*\\/download\\//, lmsBaseUrl + '/plugins/SqueezeESP32/firmware/');\n }\n\n $('#fwurl').val(url);\n}\n\n\nfunction rssiToIcon(rssi) {\n if (rssi >= -55) {\n return { 'label': '****', 'icon': `signal_wifi_statusbar_4_bar` };\n } else if (rssi >= -60) {\n return { 'label': '***', 'icon': `network_wifi_3_bar` };\n } else if (rssi >= -65) {\n return { 'label': '**', 'icon': `network_wifi_2_bar` };\n } else if (rssi >= -70) {\n return { 'label': '*', 'icon': `network_wifi_1_bar` };\n } else {\n return { 'label': '.', 'icon': `signal_wifi_statusbar_null` };\n }\n}\n\nfunction refreshAP() {\n if (ConnectedTo?.urc === connectReturnCode.ETH) return;\n $.ajaxSetup({\n timeout: 3000 //Time in milliseconds\n });\n $.getJSON('/scan.json', async function () {\n await sleep(2000);\n $.getJSON('/ap.json', function (data) {\n if (data.length > 0) {\n // sort by signal strength\n data.sort(function (a, b) {\n const x = a.rssi;\n const y = b.rssi;\n // eslint-disable-next-line no-nested-ternary\n return x < y ? 1 : x > y ? -1 : 0;\n });\n apList = data;\n refreshAPHTML2(apList);\n\n }\n });\n });\n}\nfunction formatAP(ssid, rssi, auth) {\n const rssi_icon = rssiToIcon(rssi);\n const auth_icon = { label: auth == 0 ? '🔓' : '🔒', icon: auth == 0 ? 'no_encryption' : 'lock' };\n\n return `${ssid}\n ${getIcon(rssi_icon)}\n \t\n ${getIcon(auth_icon)}\n `;\n}\nfunction refreshAPHTML2(data) {\n let h = '';\n $('#wifiTable tr td:first-of-type').text('');\n $('#wifiTable tr').removeClass('table-success table-warning');\n if (data) {\n data.forEach(function (e) {\n h += formatAP(e.ssid, e.rssi, e.auth);\n });\n $('#wifiTable').html(h);\n }\n if ($('.manual_add').length == 0) {\n $('#wifiTable').append(formatAP('Manual add', 0, 0));\n $('#wifiTable tr:last').addClass('table-light text-dark').addClass('manual_add');\n }\n if (ConnectedTo.ssid && (ConnectedTo.urc === connectReturnCode.OK || ConnectedTo.urc === connectReturnCode.RESTORE)) {\n const wifiSelector = `#wifiTable td:contains(\"${ConnectedTo.ssid}\")`;\n if ($(wifiSelector).filter(function () { return $(this).text() === ConnectedTo.ssid; }).length == 0) {\n $('#wifiTable').prepend(`${formatAP(ConnectedTo.ssid, ConnectedTo.rssi ?? 0, 0)}`);\n }\n $(wifiSelector).filter(function () { return $(this).text() === ConnectedTo.ssid; }).siblings().first().html('✓').parent().addClass((ConnectedTo.urc === connectReturnCode.OK ? 'table-success' : 'table-warning'));\n $('span#foot-if').html(`SSID: ${ConnectedTo.ssid}, IP: ${ConnectedTo.ip}`);\n $('#wifiStsIcon').html(rssiToIcon(ConnectedTo.rssi));\n\n }\n else if (ConnectedTo?.urc !== connectReturnCode.ETH) {\n $('span#foot-if').html('');\n }\n\n}\nfunction refreshETH() {\n\n if (ConnectedTo.urc === connectReturnCode.ETH) {\n $('span#foot-if').html(`Network: Ethernet, IP: ${ConnectedTo.ip}`);\n }\n}\nfunction showTask(task) {\n console.debug(\n this.toLocaleString() +\n '\\t' +\n task.nme +\n '\\t' +\n task.cpu +\n '\\t' +\n taskStates[task.st] +\n '\\t' +\n task.minstk +\n '\\t' +\n task.bprio +\n '\\t' +\n task.cprio +\n '\\t' +\n task.num\n );\n $('tbody#tasks').append(\n '' +\n task.num +\n '' +\n task.nme +\n '' +\n task.cpu +\n '' +\n taskStates[task.st] +\n '' +\n task.minstk +\n '' +\n task.bprio +\n '' +\n task.cprio +\n ''\n );\n}\nfunction btExists(name) {\n return getBTSinkOpt(name).length > 0;\n}\nfunction getBTSinkOpt(name) {\n return $(`${btSinkNamesOptSel} option:contains('${name}')`);\n}\nfunction getMessages() {\n $.ajaxSetup({\n timeout: messageInterval //Time in milliseconds\n });\n $.getJSON('/messages.json', async function (data) {\n for (const msg of data) {\n const msgAge = msg.current_time - msg.sent_time;\n var msgTime = new Date();\n msgTime.setTime(msgTime.getTime() - msgAge);\n switch (msg.class) {\n case 'MESSAGING_CLASS_OTA':\n flashState.EventOTAMessageClass(msg.message);\n break;\n case 'MESSAGING_CLASS_STATS':\n // for task states, check structure : task_state_t\n var statsData = JSON.parse(msg.message);\n console.debug(\n msgTime.toLocalShort() +\n ' - Number of running tasks: ' +\n statsData.ntasks\n );\n console.debug(\n msgTime.toLocalShort() +\n '\\tname' +\n '\\tcpu' +\n '\\tstate' +\n '\\tminstk' +\n '\\tbprio' +\n '\\tcprio' +\n '\\tnum'\n );\n if (statsData.tasks) {\n if ($('#tasks_sect').css('visibility') === 'collapse') {\n $('#tasks_sect').css('visibility', 'visible');\n }\n $('tbody#tasks').html('');\n statsData.tasks\n .sort(function (a, b) {\n return b.cpu - a.cpu;\n })\n .forEach(showTask, msgTime);\n } else if ($('#tasks_sect').css('visibility') === 'visible') {\n $('tbody#tasks').empty();\n $('#tasks_sect').css('visibility', 'collapse');\n }\n break;\n case 'MESSAGING_CLASS_SYSTEM':\n showMessage(msg, msgTime);\n break;\n case 'MESSAGING_CLASS_CFGCMD':\n var msgparts = msg.message.split(/([^\\n]*)\\n(.*)/gs);\n showCmdMessage(msgparts[1], msg.type, msgparts[2], true);\n break;\n case 'MESSAGING_CLASS_BT':\n if ($(\"#cfg-audio-bt_source-sink_name\").is('input')) {\n var attr = $(\"#cfg-audio-bt_source-sink_name\")[0].attributes;\n var attrs = '';\n for (var j = 0; j < attr.length; j++) {\n if (attr.item(j).name != \"type\") {\n attrs += `${attr.item(j).name} = \"${attr.item(j).value}\" `;\n }\n }\n var curOpt = $(\"#cfg-audio-bt_source-sink_name\")[0].value;\n $(\"#cfg-audio-bt_source-sink_name\").replaceWith(` `);\n }\n JSON.parse(msg.message).forEach(function (btEntry) {\n //\n // \n if (!btExists(btEntry.name)) {\n $(\"#cfg-audio-bt_source-sink_name\").append(``);\n showMessage({ type: msg.type, message: `BT Audio device found: ${btEntry.name} RSSI: ${btEntry.rssi} ` }, msgTime);\n }\n getBTSinkOpt(btEntry.name).attr('data-bs-description', `${btEntry.name} (${btEntry.rssi}dB)`)\n .attr('rssi', btEntry.rssi)\n .attr('value', btEntry.name)\n .text(`${btEntry.name} [${btEntry.rssi}dB]`).trigger('change');\n\n });\n $(btSinkNamesOptSel).append($(`${btSinkNamesOptSel} option`).remove().sort(function (a, b) {\n console.log(`${parseInt($(a).attr('rssi'))} < ${parseInt($(b).attr('rssi'))} ? `);\n return parseInt($(a).attr('rssi')) < parseInt($(b).attr('rssi')) ? 1 : -1;\n }));\n break;\n default:\n break;\n }\n }\n setTimeout(getMessages, messageInterval);\n }).fail(function (xhr, ajaxOptions, thrownError) {\n\n if (xhr.status == 404) {\n $('.orec').hide(); // system commands won't be available either\n messagesHeld = true;\n }\n else {\n handleExceptionResponse(xhr, ajaxOptions, thrownError);\n }\n if (xhr.status == 0 && xhr.readyState == 0) {\n // probably a timeout. Target is rebooting? \n setTimeout(getMessages, messageInterval * 2); // increase duration if a failure happens\n }\n else if (!messagesHeld) {\n // 404 here means we rebooted to an old recovery\n setTimeout(getMessages, messageInterval); // increase duration if a failure happens\n }\n\n }\n );\n\n /*\n Minstk is minimum stack space left\nBprio is base priority\ncprio is current priority\nnme is name\nst is task state. I provided a \"typedef\" that you can use to convert to text\ncpu is cpu percent used\n*/\n}\nfunction handleRecoveryMode(data) {\n const locRecovery = data.recovery ?? 0;\n if (locRecovery === 1) {\n recovery = true;\n $('.recovery_element').show();\n $('.ota_element').hide();\n $('#boot-button').html('Reboot');\n $('#boot-form').attr('action', '/reboot_ota.json');\n } else {\n if (!recovery && messagesHeld) {\n messagesHeld = false;\n setTimeout(getMessages, messageInterval); // increase duration if a failure happens\n }\n recovery = false;\n\n $('.recovery_element').hide();\n $('.ota_element').show();\n $('#boot-button').html('Recovery');\n $('#boot-form').attr('action', '/recovery.json');\n }\n\n}\n\nfunction hasConnectionChanged(data) {\n // gw: \"192.168.10.1\"\n // ip: \"192.168.10.225\"\n // netmask: \"255.255.255.0\"\n // ssid: \"MyTestSSID\"\n\n return (data.urc !== ConnectedTo.urc ||\n data.ssid !== ConnectedTo.ssid ||\n data.gw !== ConnectedTo.gw ||\n data.netmask !== ConnectedTo.netmask ||\n data.ip !== ConnectedTo.ip || data.rssi !== ConnectedTo.rssi)\n}\nfunction handleWifiDialog(data) {\n if ($('#WifiConnectDialog').is(':visible')) {\n if (ConnectedTo.ip) {\n $('#ipAddress').text(ConnectedTo.ip);\n }\n if (ConnectedTo.ssid) {\n $('#connectedToSSID').text(ConnectedTo.ssid);\n }\n if (ConnectedTo.gw) {\n $('#gateway').text(ConnectedTo.gw);\n }\n if (ConnectedTo.netmask) {\n $('#netmask').text(ConnectedTo.netmask);\n }\n if (ConnectingToSSID.Action === undefined || (ConnectingToSSID.Action && ConnectingToSSID.Action == ConnectingToActions.STS)) {\n $(\"*[class*='connecting']\").hide();\n $('.connecting-status').show();\n }\n if (SystemConfig.ap_ssid) {\n $('#apName').text(SystemConfig.ap_ssid.value);\n }\n if (SystemConfig.ap_pwd) {\n $('#apPass').text(SystemConfig.ap_pwd.value);\n }\n if (!data) {\n return;\n }\n else {\n switch (data.urc) {\n case connectReturnCode.OK:\n if (data.ssid && data.ssid === ConnectingToSSID.ssid) {\n $(\"*[class*='connecting']\").hide();\n $('.connecting-success').show();\n ConnectingToSSID.Action = ConnectingToActions.STS;\n }\n break;\n case connectReturnCode.FAIL:\n // \n if (ConnectingToSSID.Action != ConnectingToActions.STS && ConnectingToSSID.ssid == data.ssid) {\n $(\"*[class*='connecting']\").hide();\n $('.connecting-fail').show();\n }\n break;\n case connectReturnCode.LOST:\n\n break;\n case connectReturnCode.RESTORE:\n if (ConnectingToSSID.Action != ConnectingToActions.STS && ConnectingToSSID.ssid != data.ssid) {\n $(\"*[class*='connecting']\").hide();\n $('.connecting-fail').show();\n }\n break;\n case connectReturnCode.DISC:\n // that's a manual disconnect\n // if ($('#wifi-status').is(':visible')) {\n // $('#wifi-status').slideUp('fast', function() {});\n // $('span#foot-wifi').html('');\n\n // } \n break;\n default:\n break;\n }\n }\n\n }\n}\nfunction setIcons(offline) {\n $('.material-icons').each(function (_index, entry) {\n entry.textContent = entry.attributes[offline ? 'aria-label' : 'icon'].value;\n });\n}\nfunction handleNetworkStatus(data) {\n setIcons(!isConnected());\n if (hasConnectionChanged(data) || !data.urc) {\n ConnectedTo = data;\n $(\".if_eth\").hide();\n $('.if_wifi').hide();\n if (!data.urc || ConnectedTo.urc != connectReturnCode.ETH) {\n $('.if_wifi').show();\n refreshAPHTML2();\n }\n else {\n $(\".if_eth\").show();\n refreshETH();\n }\n\n }\n handleWifiDialog(data);\n}\n\n\n\nfunction batteryToIcon(voltage) {\n /* Assuming Li-ion 18650s as a power source, 3.9V per cell, or above is treated\n as full charge (>75% of capacity). 3.4V is empty. The gauge is loosely\n following the graph here:\n https://learn.adafruit.com/li-ion-and-lipoly-batteries/voltages\n using the 0.2C discharge profile for the rest of the values.\n*/\n\n for (const iconEntry of batIcons) {\n for (const entryRanges of iconEntry.ranges) {\n if (inRange(voltage, entryRanges.f, entryRanges.t)) {\n return { label: iconEntry.label, icon: iconEntry.icon };\n }\n }\n }\n\n\n return { label: 'â–Ēâ–Ēâ–Ēâ–Ē', icon: \"battery_full\" };\n}\nfunction checkStatus() {\n $.ajaxSetup({\n timeout: statusInterval //Time in milliseconds\n });\n $.getJSON('/status.json', function (data) {\n handleRecoveryMode(data);\n handleNVSVisible();\n handleNetworkStatus(data);\n handlebtstate(data);\n flashState.EventTargetStatus(data);\n if(data.depth) {\n depth = data.depth;\n if(depth==16){\n $('#cmd_opt_R').show();\n }\n else{\n $('#cmd_opt_R').hide();\n }\n }\n\n\n if (data.project_name && data.project_name !== '') {\n project_name = data.project_name;\n }\n if (data.platform_name && data.platform_name !== '') {\n platform_name = data.platform_name;\n }\n if (board_model === '') board_model = project_name;\n if (board_model === '') board_model = 'Squeezelite-ESP32';\n if (data.version && data.version !== '') {\n versionName = data.version;\n $(\"#navtitle\").html(`${board_model}${recovery ? '
[recovery]' : ''}`);\n $('span#foot-fw').html(`fw: ${versionName}, mode: ${recovery ? \"Recovery\" : project_name}`);\n } else {\n $('span#flash-status').html('');\n }\n if (data.Voltage) {\n const bat_icon = batteryToIcon(data.Voltage);\n $('#battery').html(`${getIcon(bat_icon)}`);\n $('#battery').attr(\"aria-label\", bat_icon.label);\n $('#battery').attr(\"icon\", bat_icon.icon);\n $('#battery').show();\n } else {\n $('#battery').hide();\n }\n if ((data.message ?? '') != '' && prevmessage != data.message) {\n // supporting older recovery firmwares - messages will come from the status.json structure\n prevmessage = data.message;\n showLocalMessage(data.message, 'MESSAGING_INFO')\n }\n is_i2c_locked = data.is_i2c_locked;\n if (is_i2c_locked) {\n $('flds-cfg-hw-preset').hide();\n }\n else {\n $('flds-cfg-hw-preset').show();\n }\n $(\"button[onclick*='handleReboot']\").removeClass('rebooting');\n\n if (typeof lmsBaseUrl == \"undefined\" || data.lms_ip != prevLMSIP && data.lms_ip && data.lms_port) {\n const baseUrl = 'http://' + data.lms_ip + ':' + data.lms_port;\n prevLMSIP = data.lms_ip;\n $.ajax({\n url: baseUrl + '/plugins/SqueezeESP32/firmware/-check.bin',\n type: 'HEAD',\n dataType: 'text',\n cache: false,\n error: function () {\n // define the value, so we don't check it any more.\n lmsBaseUrl = '';\n },\n success: function () {\n lmsBaseUrl = baseUrl;\n }\n });\n }\n $('#o_jack').css({ display: Number(data.Jack) ? 'inline' : 'none' });\n setTimeout(checkStatus, statusInterval);\n }).fail(function (xhr, ajaxOptions, thrownError) {\n handleExceptionResponse(xhr, ajaxOptions, thrownError);\n if (xhr.status == 0 && xhr.readyState == 0) {\n // probably a timeout. Target is rebooting? \n setTimeout(checkStatus, messageInterval * 2); // increase duration if a failure happens\n }\n else {\n setTimeout(checkStatus, messageInterval); // increase duration if a failure happens\n }\n });\n}\n// eslint-disable-next-line no-unused-vars\nwindow.runCommand = function (button, reboot) {\n let cmdstring = button.attributes.cmdname.value;\n showCmdMessage(\n button.attributes.cmdname.value,\n 'MESSAGING_INFO',\n 'Executing.',\n false\n );\n const fields = document.getElementById('flds-' + cmdstring);\n const allfields = fields?.querySelectorAll('select,input');\n if (cmdstring === 'cfg-hw-preset') return handleHWPreset(allfields, reboot);\n cmdstring += ' ';\n if (fields) {\n\n for (const field of allfields) {\n let qts = '';\n let opt = '';\n let attr = field.attributes;\n let isSelect = $(field).is('select');\n const hasValue = attr?.hasvalue?.value === 'true';\n const validVal = (isSelect && field.value !== '--') || (!isSelect && field.value !== '');\n\n if (!hasValue || hasValue && validVal) {\n if (attr?.longopts?.value !== 'undefined') {\n opt += '--' + attr?.longopts?.value;\n } else if (attr?.shortopts?.value !== 'undefined') {\n opt = '-' + attr.shortopts.value;\n }\n\n if (attr?.hasvalue?.value === 'true') {\n if (attr?.value !== '') {\n qts = /\\s/.test(field.value) ? '\"' : '';\n cmdstring += opt + ' ' + qts + field.value + qts + ' ';\n }\n } else {\n // this is a checkbox\n if (field?.checked) {\n cmdstring += opt + ' ';\n }\n }\n }\n }\n }\n\n console.log(cmdstring);\n\n const data = {\n timestamp: Date.now(),\n };\n data.command = cmdstring;\n\n $.ajax({\n url: '/commands.json',\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify(data),\n error: function (xhr, _ajaxOptions, thrownError) {\n var cmd = JSON.parse(this.data).command;\n if (xhr.status == 404) {\n showCmdMessage(\n cmd.substr(0, cmd.indexOf(' ')),\n 'MESSAGING_ERROR',\n `${recovery ? 'Limited recovery mode active. Unsupported action ' : 'Unexpected error while processing command'}`,\n true\n );\n }\n else {\n handleExceptionResponse(xhr, _ajaxOptions, thrownError);\n showCmdMessage(\n cmd.substr(0, cmd.indexOf(' ') - 1),\n 'MESSAGING_ERROR',\n `Unexpected error ${(thrownError !== '') ? thrownError : 'with return status = ' + xhr.status}`,\n true\n );\n }\n },\n success: function (response) {\n $('.orec').show();\n console.log(response);\n if (\n JSON.parse(response).Result === 'Success' &&\n reboot\n ) {\n delayReboot(2500, button.attributes.cmdname.value);\n }\n },\n });\n}\nfunction getLongOps(data, name, longopts) {\n return data.values[name] !== undefined ? data.values[name][longopts] : \"\";\n}\nfunction getCommands() {\n $.ajaxSetup({\n timeout: 7000 //Time in milliseconds\n });\n $.getJSON('/commands.json', function (data) {\n console.log(data);\n $('.orec').show();\n data.commands.forEach(function (command) {\n if ($('#flds-' + command.name).length === 0) {\n const cmdParts = command.name.split('-');\n const isConfig = cmdParts[0] === 'cfg';\n const targetDiv = '#tab-' + cmdParts[0] + '-' + cmdParts[1];\n let innerhtml = '';\n innerhtml += `
${command.help.encodeHTML().replace(/\\n/g, '
')}
`;\n if (command.argtable) {\n command.argtable.forEach(function (arg) {\n let placeholder = arg.datatype || '';\n const ctrlname = command.name + '-' + arg.longopts;\n const curvalue = getLongOps(data, command.name, arg.longopts);\n\n let attributes = 'hasvalue=' + arg.hasvalue + ' ';\n attributes += 'longopts=\"' + arg.longopts + '\" ';\n attributes += 'shortopts=\"' + arg.shortopts + '\" ';\n attributes += 'checkbox=' + arg.checkbox + ' ';\n attributes += 'cmdname=\"' + command.name + '\" ';\n attributes +=\n 'id=\"' +\n ctrlname +\n '\" name=\"' +\n ctrlname +\n '\" hasvalue=\"' +\n arg.hasvalue +\n '\" ';\n let extraclass = arg.mincount > 0 ? 'bg-success' : '';\n if (arg.glossary === 'hidden') {\n attributes += ' style=\"visibility: hidden;\"';\n }\n if (arg.checkbox) {\n innerhtml += `
`;\n } else {\n innerhtml += `
`;\n if (placeholder.includes('|')) {\n extraclass = placeholder.startsWith('+') ? ' multiple ' : '';\n placeholder = placeholder\n .replace('<', '')\n .replace('=', '')\n .replace('>', '');\n innerhtml += `';\n } else {\n innerhtml += ``;\n }\n }\n\n innerhtml += `${arg.checkbox ? '
' : ''}Previous value: ${arg.checkbox ? (curvalue ? 'Checked' : 'Unchecked') : (curvalue || '')}${arg.checkbox ? '' : '
'}`;\n });\n }\n innerhtml += `
\n
\n
\n Result\n
\n
\n
`;\n if (isConfig) {\n innerhtml +=\n `\n`;\n } else {\n innerhtml += ``;\n }\n innerhtml += '
';\n if (isConfig) {\n $(targetDiv).append(innerhtml);\n } else {\n $('#commands-list').append(innerhtml);\n }\n }\n });\n $(\".sclk\").off('click').on('click', function () { runCommand(this, false); });\n $(\".cclk\").off('click').on('click', function () { runCommand(this, true); });\n data.commands.forEach(function (command) {\n $('[cmdname=' + command.name + ']:input').val('');\n $('[cmdname=' + command.name + ']:checkbox').prop('checked', false);\n if (command.argtable) {\n command.argtable.forEach(function (arg) {\n const ctrlselector = '#' + command.name + '-' + arg.longopts;\n const ctrlValue = getLongOps(data, command.name, arg.longopts);\n if (arg.checkbox) {\n $(ctrlselector)[0].checked = ctrlValue;\n } else {\n if (ctrlValue !== undefined) {\n $(ctrlselector)\n .val(ctrlValue)\n .trigger('change');\n }\n if (\n $(ctrlselector)[0].value.length === 0 &&\n (arg.datatype || '').includes('|')\n ) {\n $(ctrlselector)[0].value = '--';\n }\n }\n });\n }\n });\n loadPresets();\n }).fail(function (xhr, ajaxOptions, thrownError) {\n if (xhr.status == 404) {\n $('.orec').hide();\n }\n else {\n handleExceptionResponse(xhr, ajaxOptions, thrownError);\n }\n $('#commands-list').empty();\n\n });\n}\n\nfunction getConfig() {\n $.ajaxSetup({\n timeout: 7000 //Time in milliseconds\n });\n $.getJSON('/config.json', function (entries) {\n $('#nvsTable tr').remove();\n const data = (entries.config ? entries.config : entries);\n SystemConfig = data;\n commandBTSinkName = '';\n Object.keys(data)\n .sort()\n .forEach(function (key) {\n let val = data[key].value;\n if (key === 'autoexec') {\n if (data.autoexec.value === '0') {\n $('#disable-squeezelite')[0].checked = true;\n } else {\n $('#disable-squeezelite')[0].checked = false;\n }\n } else if (key === 'autoexec1') {\n /* call new function to parse the squeezelite options */\n processSqueezeliteCommandLine(val);\n } else if (key === 'host_name') {\n val = val.replaceAll('\"', '');\n $('input#dhcp-name1').val(val);\n $('input#dhcp-name2').val(val);\n if ($('#cmd_opt_n').length == 0) {\n $('#cmd_opt_n').val(val);\n }\n document.title = val;\n hostName = val;\n } else if (key === 'rel_api') {\n releaseURL = val;\n }\n else if (key === 'enable_airplay') {\n $(\"#s_airplay\").css({ display: isEnabled(val) ? 'inline' : 'none' })\n }\n else if (key === 'enable_cspot') {\n $(\"#s_cspot\").css({ display: isEnabled(val) ? 'inline' : 'none' })\n }\n else if (key == 'preset_name') {\n preset_name = val;\n }\n else if (key == 'board_model') {\n board_model = val;\n }\n\n $('tbody#nvsTable').append(\n '' +\n '' +\n key +\n '' +\n \"\" +\n \"' +\n '' +\n ''\n );\n $('input#' + key).val(data[key].value);\n });\n if(commandBTSinkName.length > 0) {\n // persist the sink name found in the autoexec1 command line\n $('#cfg-audio-bt_source-sink_name').val(commandBTSinkName);\n }\n $('tbody#nvsTable').append(\n \"\"\n );\n if (entries.gpio) {\n $('#pins').show();\n $('tbody#gpiotable tr').remove();\n entries.gpio.forEach(function (gpioEntry) {\n $('tbody#gpiotable').append(\n '' +\n gpioEntry.group +\n '' +\n gpioEntry.name +\n '' +\n gpioEntry.gpio +\n '' +\n (gpioEntry.fixed ? 'Fixed' : 'Configuration') +\n ''\n );\n });\n }\n else {\n $('#pins').hide();\n }\n }).fail(function (xhr, ajaxOptions, thrownError) {\n handleExceptionResponse(xhr, ajaxOptions, thrownError);\n });\n}\n\nfunction processSqueezeliteCommandLine(val) {\n const parsed = parseSqueezeliteCommandLine(val);\n if (parsed.output.toUpperCase().startsWith('I2S')) {\n handleTemplateTypeRadio('i2s');\n } else if (parsed.output.toUpperCase().startsWith('SPDIF')) {\n handleTemplateTypeRadio('spdif');\n } else if (parsed.output.toUpperCase().startsWith('BT')) {\n if(parsed.otherOptions.btname){ \n commandBTSinkName= parsed.otherOptions.btname;\n }\n handleTemplateTypeRadio('bt');\n }\n Object.keys(parsed.options).forEach(function (key) {\n const option = parsed.options[key];\n if (!$(`#cmd_opt_${key}`).hasOwnProperty('checked')) {\n $(`#cmd_opt_${key}`).val(option);\n } else {\n $(`#cmd_opt_${key}`)[0].checked = option;\n }\n });\n if (parsed.options.hasOwnProperty('u')) {\n // parse -u v[:i] and check the appropriate radio button with id #resample_v\n const [resampleValue, resampleInterpolation] = parsed.options.u.split(':');\n $(`#resample_${resampleValue}`).prop('checked', true);\n // if resampleinterpolation is set, check resample_i checkbox\n if (resampleInterpolation) {\n $('#resample_i').prop('checked', true);\n }\n }\n\n\n}\n\nfunction showLocalMessage(message, severity) {\n const msg = {\n message: message,\n type: severity,\n };\n showMessage(msg, new Date());\n}\n\nfunction showMessage(msg, msgTime) {\n let color = 'table-success';\n\n if (msg.type === 'MESSAGING_WARNING') {\n color = 'table-warning';\n if (messageseverity === 'MESSAGING_INFO') {\n messageseverity = 'MESSAGING_WARNING';\n }\n } else if (msg.type === 'MESSAGING_ERROR') {\n if (\n messageseverity === 'MESSAGING_INFO' ||\n messageseverity === 'MESSAGING_WARNING'\n ) {\n messageseverity = 'MESSAGING_ERROR';\n }\n color = 'table-danger';\n }\n if (++messagecount > 0) {\n $('#msgcnt').removeClass('badge-success');\n $('#msgcnt').removeClass('badge-warning');\n $('#msgcnt').removeClass('badge-danger');\n $('#msgcnt').addClass(pillcolors[messageseverity]);\n $('#msgcnt').text(messagecount);\n }\n\n $('#syslogTable').append(\n \"\" +\n '' +\n msgTime.toLocalShort() +\n '' +\n '' +\n msg.message.encodeHTML() +\n '' +\n ''\n );\n}\n\nfunction inRange(x, min, max) {\n return (x - min) * (x - max) <= 0;\n}\n\nfunction sleep(ms) {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nrequire(\"bootstrap\");\nrequire(\"./sass/main.scss\");\nrequire(\"./assets/images/favicon-32x32.png\");\nrequire(\"./js/custom.js\");\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\tid: moduleId,\n\t\tloaded: false,\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Flag the module as loaded\n\tmodule.loaded = true;\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","__webpack_require__.nmd = (module) => {\n\tmodule.paths = [];\n\tif (!module.children) module.children = [];\n\treturn module;\n};","// no baseURI\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t826: 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n__webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0);\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = (parentChunkLoadingFunction, data) => {\n\tvar [chunkIds, moreModules, runtime] = data;\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some((id) => (installedChunks[id] !== 0))) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\treturn __webpack_require__.O(result);\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunksqueezelite_esp32\"] = self[\"webpackChunksqueezelite_esp32\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));","// startup\n// Load entry module and return exports\n// This entry module depends on other loaded chunks and execution need to be delayed\nvar __webpack_exports__ = __webpack_require__.O(undefined, [987], () => (__webpack_require__(607)))\n__webpack_exports__ = __webpack_require__.O(__webpack_exports__);\n"],"names":["deferred","he","require","Promise","get_control_option_value","obj","ctrl","id","val","opt","$","concat","attr","checked","replace","includes","handleNVSVisible","nvs_previous_checked","isEnabled","Cookies","recovery","show","hide","undefined","match","window","bootstrap","String","prototype","format","Object","assign","args","arguments","this","number","encodeHTML","encode","Date","toLocalShort","toLocaleString","dateStyle","timeStyle","nvsTypes","btIcons","bt_playing","bt_disconnected","bt_neutral","bt_connecting","bt_connected","bt_disabled","play_arrow","pause","stop","batIcons","icon","label","ranges","f","t","btStateIcons","desc","sub","pillcolors","MESSAGING_INFO","MESSAGING_WARNING","MESSAGING_ERROR","connectReturnCode","OK","FAIL","DISC","LOST","RESTORE","ETH","taskStates","flashState","NONE","REBOOT_TO_RECOVERY","SET_FWURL","FLASHING","DONE","UPLOADING","ERROR","UPLOADCOMPLETE","_state","olderRecovery","statusText","flashURL","flashFileName","statusPercent","Completed","prevRecovery","updateModal","Modal","document","getElementById","reset","UpdateProgress","removeClass","prop","value","isStateError","html","parent","isStateUploadComplete","isStateNone","isStateRebootRecovery","isStateSetUrl","isStateFlashing","isStateDone","isStateUploading","init","SetStateError","addClass","SetStateNone","SetStateRebootRecovery","SetStatusText","ajax","url","context","dataType","method","cache","contentType","data","JSON","stringify","timestamp","now","error","xhr","_ajaxOptions","thrownError","_xhr$status","setOTAError","status","complete","response","SetStateSetUrl","post_config","fwurl","type","SetStateFlashing","SetStateDone","SetStateUploading","SetStateUploadComplete","isFlashExecuting","toString","_this","keys","find","x","setOTATargets","fileInput","files","length","message","SetStatusPercent","ShowDialog","pct","pctChanged","txt","changed","css","text","StartOTA","logEvent","name","TargetReadyStartOTA","UploadLocalFile","xhttp","XMLHttpRequest","boundHandleUploadProgressEvent","HandleUploadProgressEvent","bind","boundsetOTAError","upload","addEventListener","onreadystatechange","readyState","open","send","console","Math","round","loaded","total","EventTargetStatus","_data$ota_pct","_data$ota_dsc","ota_pct","ota_dsc","EventOTAMessageClass","otaData","parse","fun","log","hideSurrounding","presetsloaded","messageInterval","confPayload","config","handleExceptionResponse","parseSqueezeliteCommandLine","commandLine","output","options","otherValues","i","arg","startsWith","option","slice","join","trim","o","indexOf","substring","getOutput","n","getName","otherOptions","btname","toUpperCase","temp","isConnected","ConnectedTo","hasOwnProperty","ip","getIcon","icons","handleTemplateTypeRadio","outtype","children","display","commandDefaults","forEach","key","showLocalMessage","showCmdMessage","cmdname","msgtype","msgtext","append","color","escapedtext","hFlash","handleReboot","link","delayReboot","lmsBaseUrl","releaseURL","messagesHeld","commandBTSinkName","i2s","b","C","W","Z","spdif","bt","validOptions","codecs","messagecount","messageseverity","SystemConfig","LastCommandsState","hostName","versionName","prevmessage","project_name","board_model","platform_name","preset_name","btSinkNamesOptSel","ConnectingToSSID","prevLMSIP","ConnectingToActions","getConfigJson","slimMode","each","_index","entry","nvsType","parseInt","attributes","nvs_type","duration","empty","resolve","delay","then","rdata","HideCmdMessage","getCommands","getConfig","setPlatformFilter","filter","trigger","renderError","fieldname","errorFieldName","errorField","field","after","rssiToIcon","rssi","refreshAP","_ConnectedTo","urc","ajaxSetup","timeout","getJSON","_asyncToGenerator","_regeneratorRuntime","_callee","_context","prev","next","sleep","sort","a","y","refreshAPHTML2","formatAP","ssid","auth","rssi_icon","auth_icon","_ConnectedTo2","h","e","_ConnectedTo$rssi","wifiSelector","prepend","siblings","first","showTask","task","debug","nme","cpu","st","minstk","bprio","cprio","num","getBTSinkOpt","getMessages","_ref2","_callee2","_iterator","_step","_loop","msgTime","statsData","msgparts","attrs","j","curOpt","_context3","_createForOfIteratorHelper","msg","msgAge","_context2","current_time","sent_time","setTime","getTime","t0","abrupt","ntasks","tasks","showMessage","split","is","item","replaceWith","btEntry","remove","s","done","delegateYield","t1","finish","setTimeout","_x","apply","fail","ajaxOptions","handleWifiDialog","gw","netmask","Action","STS","ap_ssid","ap_pwd","setIcons","offline","textContent","handleNetworkStatus","hasConnectionChanged","checkStatus","_data$message","_data$recovery","handleRecoveryMode","tt","bt_status","bt_sub_status","iconindex","handlebtstate","depth","version","Voltage","bat_icon","voltage","_i3","_batIcons","_step2","iconEntry","_iterator2","entryRanges","err","batteryToIcon","is_i2c_locked","lms_ip","lms_port","baseUrl","success","Number","Jack","getLongOps","longopts","values","commands","command","cmdParts","isConfig","targetDiv","innerhtml","help","argtable","placeholder","datatype","ctrlname","curvalue","hasvalue","shortopts","checkbox","extraclass","mincount","glossary","choice","off","on","runCommand","ctrlselector","ctrlValue","_","jqxhr","textStatus","entries","autoexec","parsed","_parsed$options$u$spl","u","_parsed$options$u$spl2","_slicedToArray","resampleValue","resampleInterpolation","processSqueezeliteCommandLine","replaceAll","title","gpio","gpioEntry","fixed","group","severity","ms","reason","_resolve","reject","saveAutoexec1","_get_control_option_v","optStr","resample","_i","_Object$entries","_Object$entries$_i","concatenateOptions","autoexec1","responseText","result","Result","handleDisconnect","handleConnect","pwd","dhcpname","ready","index","not","_get_control_option_v2","invalid","map","test","event","relatedTarget","CONN","MAN","FileReader","file","fr","onload","target","ex","alert","readAsText","slideUp","slideDown","preventDefault","createElement","href","URL","createObjectURL","Blob","setAttribute","body","appendChild","click","removeChild","branches","release","branch","push","fwb","assets","asset","browser_download_url","namecomponents","ver","cfg","bits","substr","lastIndexOf","created_at","idf","trclass","setURL","button","dataset","reboot","cmdstring","fields","allfields","querySelectorAll","selJson","cmd","model_config","_i2","_Object$entries2","_Object$entries2$_i","storedval","handleHWPreset","_step3","_iterator3","_attr$hasvalue","qts","isSelect","hasValue","validVal","_attr$longopts","_attr$shortopts","_attr$hasvalue2","_attr$longopts2","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","exports","module","__webpack_modules__","call","m","O","chunkIds","fn","priority","notFulfilled","Infinity","fulfilled","every","splice","r","getter","__esModule","d","definition","defineProperty","enumerable","get","g","globalThis","Function","Symbol","toStringTag","nmd","paths","installedChunks","chunkId","webpackJsonpCallback","parentChunkLoadingFunction","moreModules","runtime","some","chunkLoadingGlobal","self","__webpack_exports__"],"sourceRoot":""} \ No newline at end of file +{"version":3,"file":"./js/index.1e1c60.bundle.js","mappings":"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","sources":["webpack://squeezelite-esp32/webpack/runtime/chunk loaded","webpack://squeezelite-esp32/./src/js/custom.js","webpack://squeezelite-esp32/./src/index.ts","webpack://squeezelite-esp32/webpack/bootstrap","webpack://squeezelite-esp32/webpack/runtime/compat get default export","webpack://squeezelite-esp32/webpack/runtime/define property getters","webpack://squeezelite-esp32/webpack/runtime/global","webpack://squeezelite-esp32/webpack/runtime/hasOwnProperty shorthand","webpack://squeezelite-esp32/webpack/runtime/make namespace object","webpack://squeezelite-esp32/webpack/runtime/node module decorator","webpack://squeezelite-esp32/webpack/runtime/jsonp chunk loading","webpack://squeezelite-esp32/webpack/startup"],"sourcesContent":["var deferred = [];\n__webpack_require__.O = (result, chunkIds, fn, priority) => {\n\tif(chunkIds) {\n\t\tpriority = priority || 0;\n\t\tfor(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];\n\t\tdeferred[i] = [chunkIds, fn, priority];\n\t\treturn;\n\t}\n\tvar notFulfilled = Infinity;\n\tfor (var i = 0; i < deferred.length; i++) {\n\t\tvar [chunkIds, fn, priority] = deferred[i];\n\t\tvar fulfilled = true;\n\t\tfor (var j = 0; j < chunkIds.length; j++) {\n\t\t\tif ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) {\n\t\t\t\tchunkIds.splice(j--, 1);\n\t\t\t} else {\n\t\t\t\tfulfilled = false;\n\t\t\t\tif(priority < notFulfilled) notFulfilled = priority;\n\t\t\t}\n\t\t}\n\t\tif(fulfilled) {\n\t\t\tdeferred.splice(i--, 1)\n\t\t\tvar r = fn();\n\t\t\tif (r !== undefined) result = r;\n\t\t}\n\t}\n\treturn result;\n};","var he = require('he');\nvar Promise = require('es6-promise').Promise;\nwindow.bootstrap = require('bootstrap');\nimport Cookies from 'js-cookie';\n\n\n\nif (!String.prototype.format) {\n Object.assign(String.prototype, {\n format() {\n const args = arguments;\n return this.replace(/{(\\d+)}/g, function (match, number) {\n return typeof args[number] !== 'undefined' ? args[number] : match;\n });\n },\n });\n}\nif (!String.prototype.encodeHTML) {\n Object.assign(String.prototype, {\n encodeHTML() {\n return he.encode(this).replace(/\\n/g, '
');\n },\n });\n}\nObject.assign(Date.prototype, {\n toLocalShort() {\n const opt = { dateStyle: 'short', timeStyle: 'short' };\n return this.toLocaleString(undefined, opt);\n },\n});\nfunction get_control_option_value(obj) {\n let ctrl,id,val,opt;\n let radio = false;\n let checked = false;\n if (typeof (obj) === 'string') {\n id = obj;\n ctrl = $(`#${id}`);\n } else {\n id = $(obj).attr('id');\n ctrl = $(obj);\n }\n if(ctrl.attr('type') === 'checkbox'){\n opt = $(obj).checked?id.replace('cmd_opt_', ''):'';\n val = true;\n }\n else {\n opt = id.replace('cmd_opt_', '');\n val = $(obj).val();\n val = `${val.includes(\" \") ? '\"' : ''}${val}${val.includes(\" \") ? '\"' : ''}`;\n }\n\n return { opt, val };\n}\nfunction handleNVSVisible() {\n let nvs_previous_checked = isEnabled(Cookies.get(\"show-nvs\"));\n $('input#show-nvs')[0].checked = nvs_previous_checked;\n if ($('input#show-nvs')[0].checked || recovery) {\n $('*[href*=\"-nvs\"]').show();\n } else {\n $('*[href*=\"-nvs\"]').hide();\n }\n}\nfunction concatenateOptions(options) {\n let commandLine = ' ';\n for (const [option, value] of Object.entries(options)) {\n if (option !== 'n' && option !== 'o') {\n commandLine += `-${option} `;\n if (value !== true) {\n commandLine += `${value} `;\n }\n }\n }\n return commandLine;\n}\n\nfunction isEnabled(val) {\n return val != undefined && typeof val === 'string' && val.match(\"[Yy1]\");\n}\n\nconst nvsTypes = {\n NVS_TYPE_U8: 0x01,\n /*! < Type uint8_t */\n NVS_TYPE_I8: 0x11,\n /*! < Type int8_t */\n NVS_TYPE_U16: 0x02,\n /*! < Type uint16_t */\n NVS_TYPE_I16: 0x12,\n /*! < Type int16_t */\n NVS_TYPE_U32: 0x04,\n /*! < Type uint32_t */\n NVS_TYPE_I32: 0x14,\n /*! < Type int32_t */\n NVS_TYPE_U64: 0x08,\n /*! < Type uint64_t */\n NVS_TYPE_I64: 0x18,\n /*! < Type int64_t */\n NVS_TYPE_STR: 0x21,\n /*! < Type string */\n NVS_TYPE_BLOB: 0x42,\n /*! < Type blob */\n NVS_TYPE_ANY: 0xff /*! < Must be last */,\n};\nconst btIcons = {\n bt_playing: { 'label': '', 'icon': 'media_bluetooth_on' },\n bt_disconnected: { 'label': '', 'icon': 'media_bluetooth_off' },\n bt_neutral: { 'label': '', 'icon': 'bluetooth' },\n bt_connecting: { 'label': '', 'icon': 'bluetooth_searching' },\n bt_connected: { 'label': '', 'icon': 'bluetooth_connected' },\n bt_disabled: { 'label': '', 'icon': 'bluetooth_disabled' },\n play_arrow: { 'label': '', 'icon': 'play_circle_filled' },\n pause: { 'label': '', 'icon': 'pause_circle' },\n stop: { 'label': '', 'icon': 'stop_circle' },\n '': { 'label': '', 'icon': '' }\n};\nconst batIcons = [\n { icon: \"battery_0_bar\", label: 'â–Ē', ranges: [{ f: 5.8, t: 6.8 }, { f: 8.8, t: 10.2 }] },\n { icon: \"battery_2_bar\", label: 'â–Ēâ–Ē', ranges: [{ f: 6.8, t: 7.4 }, { f: 10.2, t: 11.1 }] },\n { icon: \"battery_3_bar\", label: 'â–Ēâ–Ēâ–Ē', ranges: [{ f: 7.4, t: 7.5 }, { f: 11.1, t: 11.25 }] },\n { icon: \"battery_4_bar\", label: 'â–Ēâ–Ēâ–Ēâ–Ē', ranges: [{ f: 7.5, t: 7.8 }, { f: 11.25, t: 11.7 }] }\n];\nconst btStateIcons = [\n { desc: 'Idle', sub: ['bt_neutral'] },\n { desc: 'Discovering', sub: ['bt_connecting'] },\n { desc: 'Discovered', sub: ['bt_connecting'] },\n { desc: 'Unconnected', sub: ['bt_disconnected'] },\n { desc: 'Connecting', sub: ['bt_connecting'] },\n {\n desc: 'Connected',\n sub: ['bt_connected', 'play_arrow', 'bt_playing', 'pause', 'stop'],\n },\n { desc: 'Disconnecting', sub: ['bt_disconnected'] },\n];\n\nconst pillcolors = {\n MESSAGING_INFO: 'badge-success',\n MESSAGING_WARNING: 'badge-warning',\n MESSAGING_ERROR: 'badge-danger',\n};\nconst connectReturnCode = {\n OK: 0,\n FAIL: 1,\n DISC: 2,\n LOST: 3,\n RESTORE: 4,\n ETH: 5\n}\nconst taskStates = {\n 0: 'eRunning',\n /*! < A task is querying the state of itself, so must be running. */\n 1: 'eReady',\n /*! < The task being queried is in a read or pending ready list. */\n 2: 'eBlocked',\n /*! < The task being queried is in the Blocked state. */\n 3: 'eSuspended',\n /*! < The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */\n 4: 'eDeleted',\n};\nlet flashState = {\n NONE: 0,\n REBOOT_TO_RECOVERY: 2,\n SET_FWURL: 5,\n FLASHING: 6,\n DONE: 7,\n UPLOADING: 8,\n ERROR: 9,\n UPLOADCOMPLETE: 10,\n _state: -1,\n olderRecovery: false,\n statusText: '',\n flashURL: '',\n flashFileName: '',\n statusPercent: 0,\n Completed: false,\n recovery: false,\n prevRecovery: false,\n updateModal: new bootstrap.Modal(document.getElementById('otadiv'), {}),\n reset: function () {\n\n this.olderRecovery = false;\n this.statusText = '';\n this.statusPercent = -1;\n this.flashURL = '';\n this.flashFileName = undefined;\n this.UpdateProgress();\n $('#rTable tr.release').removeClass('table-success table-warning');\n $('.flact').prop('disabled', false);\n $('#flashfilename').value = null;\n $('#fw-url-input').value = null;\n if (!this.isStateError()) {\n $('span#flash-status').html('');\n $('#fwProgressLabel').parent().removeClass('bg-danger');\n }\n this._state = this.NONE\n return this;\n },\n isStateUploadComplete: function () {\n return this._state == this.UPLOADCOMPLETE;\n },\n isStateError: function () {\n return this._state == this.ERROR;\n },\n isStateNone: function () {\n return this._state == this.NONE;\n },\n isStateRebootRecovery: function () {\n return this._state == this.REBOOT_TO_RECOVERY;\n },\n isStateSetUrl: function () {\n return this._state == this.SET_FWURL;\n },\n isStateFlashing: function () {\n return this._state == this.FLASHING;\n },\n isStateDone: function () {\n return this._state == this.DONE;\n },\n isStateUploading: function () {\n return this._state == this.UPLOADING;\n },\n init: function () {\n this._state = this.NONE;\n return this;\n },\n\n SetStateError: function () {\n this._state = this.ERROR;\n $('#fwProgressLabel').parent().addClass('bg-danger');\n return this;\n },\n SetStateNone: function () {\n this._state = this.NONE;\n return this;\n },\n SetStateRebootRecovery: function () {\n this._state = this.REBOOT_TO_RECOVERY;\n // Reboot system to recovery mode\n this.SetStatusText('Starting recovery mode.')\n $.ajax({\n url: '/recovery.json',\n context: this,\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n timestamp: Date.now(),\n }),\n error: function (xhr, _ajaxOptions, thrownError) {\n this.setOTAError(`Unexpected error while trying to restart to recovery. (status=${xhr.status ?? ''}, error=${thrownError ?? ''} ) `);\n },\n complete: function (response) {\n this.SetStatusText('Waiting for system to boot.')\n },\n });\n return this;\n },\n SetStateSetUrl: function () {\n this._state = this.SET_FWURL;\n this.statusText = 'Sending firmware download location.';\n let confData = {\n fwurl: {\n value: this.flashURL,\n type: 33,\n }\n };\n post_config(confData);\n return this;\n },\n SetStateFlashing: function () {\n this._state = this.FLASHING;\n return this;\n },\n SetStateDone: function () {\n this._state = this.DONE;\n this.reset();\n return this;\n },\n SetStateUploading: function () {\n this._state = this.UPLOADING;\n return this.SetStatusText('Sending file to device.');\n },\n SetStateUploadComplete: function () {\n this._state = this.UPLOADCOMPLETE;\n return this;\n },\n\n isFlashExecuting: function () {\n return true === (this._state != this.UPLOADING && (this.statusText !== '' || this.statusPercent >= 0));\n },\n\n\n\n toString: function () {\n let keys = Object.keys(this);\n return keys.find(x => this[x] === this._state);\n },\n\n setOTATargets: function () {\n this.flashURL = '';\n this.flashFileName = '';\n this.flashURL = $('#fw-url-input').val();\n let fileInput = $('#flashfilename')[0].files;\n if (fileInput.length > 0) {\n this.flashFileName = fileInput[0];\n }\n if (this.flashFileName.length == 0 && this.flashURL.length == 0) {\n this.setOTAError('Invalid url or file. Cannot start OTA');\n }\n return this;\n },\n\n setOTAError: function (message) {\n this.SetStateError().SetStatusPercent(0).SetStatusText(message).reset();\n return this;\n },\n\n ShowDialog: function () {\n if (!this.isStateNone()) {\n this.updateModal.show();\n $('.flact').prop('disabled', true);\n }\n return this;\n },\n\n SetStatusPercent: function (pct) {\n var pctChanged = (this.statusPercent != pct);\n this.statusPercent = pct;\n if (pctChanged) {\n if (!this.isStateUploading() && !this.isStateFlashing()) {\n this.SetStateFlashing();\n }\n if (pct == 100) {\n if (this.isStateFlashing()) {\n this.SetStateDone();\n }\n else if (this.isStateUploading()) {\n this.statusPercent = 0;\n this.SetStateFlashing();\n }\n }\n this.UpdateProgress().ShowDialog();\n }\n return this;\n },\n SetStatusText: function (txt) {\n var changed = (this.statusText != txt);\n this.statusText = txt;\n if (changed) {\n $('span#flash-status').html(this.statusText);\n this.ShowDialog();\n }\n\n return this;\n },\n UpdateProgress: function () {\n $('.progress-bar')\n .css('width', this.statusPercent + '%')\n .attr('aria-valuenow', this.statusPercent)\n .text(this.statusPercent + '%')\n $('.progress-bar').html((this.isStateDone() ? 100 : this.statusPercent) + '%');\n return this;\n },\n StartOTA: function () {\n this.logEvent(this.StartOTA.name);\n $('#fwProgressLabel').parent().removeClass('bg-danger');\n this.setOTATargets();\n if (this.isStateError()) {\n return this;\n }\n if (!recovery) {\n this.SetStateRebootRecovery();\n }\n else {\n this.SetStateFlashing().TargetReadyStartOTA();\n }\n\n return this;\n },\n UploadLocalFile: function () {\n this.SetStateUploading();\n const xhttp = new XMLHttpRequest();\n xhttp.context = this;\n var boundHandleUploadProgressEvent = this.HandleUploadProgressEvent.bind(this);\n var boundsetOTAError = this.setOTAError.bind(this);\n xhttp.upload.addEventListener(\"progress\", boundHandleUploadProgressEvent, false);\n xhttp.onreadystatechange = function () {\n if (xhttp.readyState === 4) {\n if (xhttp.status === 0 || xhttp.status === 404) {\n boundsetOTAError(`Upload Failed. Recovery version might not support uploading. Please use web update instead.`);\n }\n }\n };\n xhttp.open('POST', '/flash.json', true);\n xhttp.send(this.flashFileName);\n },\n TargetReadyStartOTA: function () {\n if (recovery && this.prevRecovery && !this.isStateRebootRecovery() && !this.isStateFlashing()) {\n // this should only execute once, while being in a valid state\n return this;\n }\n\n this.logEvent(this.TargetReadyStartOTA.name);\n if (!recovery) {\n console.error('Event TargetReadyStartOTA fired in the wrong mode ');\n return this;\n }\n this.prevRecovery = true;\n\n if (this.flashFileName !== '') {\n this.UploadLocalFile();\n }\n else if (this.flashURL != '') {\n this.SetStateSetUrl();\n }\n else {\n this.setOTAError('Invalid URL or file name while trying to start the OTa process')\n }\n },\n HandleUploadProgressEvent: function (data) {\n this.logEvent(this.HandleUploadProgressEvent.name);\n this.SetStateUploading().SetStatusPercent(Math.round(data.loaded / data.total * 100)).SetStatusText('Uploading file to device');\n },\n EventTargetStatus: function (data) {\n if (!this.isStateNone()) {\n this.logEvent(this.EventTargetStatus.name);\n }\n if (data.ota_pct ?? -1 >= 0) {\n this.olderRecovery = true;\n this.SetStatusPercent(data.ota_pct);\n }\n if ((data.ota_dsc ?? '') != '') {\n this.olderRecovery = true;\n this.SetStatusText(data.ota_dsc);\n }\n\n if (data.recovery != undefined) {\n this.recovery = data.recovery === 1 ? true : false;\n }\n if (this.isStateRebootRecovery() && this.recovery) {\n this.TargetReadyStartOTA();\n }\n },\n EventOTAMessageClass: function (data) {\n this.logEvent(this.EventOTAMessageClass.name);\n var otaData = JSON.parse(data);\n this.SetStatusPercent(otaData.ota_pct).SetStatusText(otaData.ota_dsc);\n },\n logEvent: function (fun) {\n console.log(`${fun}, flash state ${this.toString()}, recovery: ${this.recovery}, ota pct: ${this.statusPercent}, ota desc: ${this.statusText}`);\n }\n\n};\nwindow.hideSurrounding = function (obj) {\n $(obj).parent().parent().hide();\n}\n\nlet presetsloaded = false;\nlet is_i2c_locked = false;\nlet statusInterval = 2000;\nlet messageInterval = 2500;\nfunction post_config(data) {\n let confPayload = {\n timestamp: Date.now(),\n config: data\n };\n $.ajax({\n url: '/config.json',\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify(confPayload),\n error: handleExceptionResponse,\n });\n}\n\n\nwindow.hFlash = function () {\n // reset file upload selection if any;\n $('#flashfilename').value = null\n flashState.StartOTA();\n}\nwindow.handleReboot = function (link) {\n if (link == 'reboot_ota') {\n $('#reboot_ota_nav').removeClass('active').prop(\"disabled\", true); delayReboot(500, '', 'reboot_ota');\n }\n else {\n $('#reboot_nav').removeClass('active'); delayReboot(500, '', link);\n }\n}\n\nfunction parseSqueezeliteCommandLine(commandLine) {\n const options = {};\n let output, name;\n let otherValues = '';\n\n const argRegex = /(\"[^\"]+\"|'[^']+'|\\S+)/g;\n const args = commandLine.match(argRegex);\n\n let i = 0;\n\n while (i < args.length) {\n const arg = args[i];\n\n if (arg.startsWith('-')) {\n const option = arg.slice(1);\n\n if (option === '') {\n otherValues += args.slice(i).join(' ');\n break;\n }\n\n let value = true;\n\n if (i + 1 < args.length && !args[i + 1].startsWith('-')) {\n value = args[i + 1].replace(/\"/g, '').replace(/'/g, '');\n i++;\n }\n\n options[option] = value;\n } else {\n otherValues += arg + ' ';\n }\n\n i++;\n }\n\n otherValues = otherValues.trim();\n output = getOutput(options);\n name = getName(options);\n let otherOptions={btname:null,n:null};\n // assign o and n options to otheroptions if present\n if (options.o && output.toUpperCase() === 'BT') {\n let temp = parseSqueezeliteCommandLine(options.o);\n if(temp.name) {\n otherOptions.btname = temp.name;\n }\n delete options.o;\n }\n if (options.n) {\n otherOptions['n'] = options.n;\n delete options.n;\n }\n return { name, output, options, otherValues,otherOptions }; \n}\n\nfunction getOutput(options) {\n let output;\n if (options.o){\n output = options.o.replace(/\"/g, '').replace(/'/g, '');\n /* set output as the first alphanumerical word in the command line */\n if (output.indexOf(' ') > 0) {\n output = output.substring(0, output.indexOf(' '));\n }\n }\n return output;\n}\n\nfunction getName(options) {\n let name;\n /* if n option present, assign to name variable */\n if (options.n){\n name = options.n.replace(/\"/g, '').replace(/'/g, '');\n }\n return name;\n}\n\n\nfunction isConnected() {\n return ConnectedTo.hasOwnProperty('ip') && ConnectedTo.ip != '0.0.0.0' && ConnectedTo.ip != '';\n}\nfunction getIcon(icons) {\n return isConnected() ? icons.icon : icons.label;\n}\nfunction handlebtstate(data) {\n let icon = '';\n let tt = '';\n if (data.bt_status !== undefined && data.bt_sub_status !== undefined) {\n const iconindex = btStateIcons[data.bt_status].sub[data.bt_sub_status];\n if (iconindex) {\n icon = btIcons[iconindex];\n tt = btStateIcons[data.bt_status].desc;\n } else {\n icon = btIcons.bt_connected;\n tt = 'Output status';\n }\n }\n\n $('#o_type').attr('title', tt);\n $('#o_bt').html(isConnected() ? icon.label : icon.text);\n}\nfunction handleTemplateTypeRadio(outtype) {\n $('#o_type').children('span').css({ display: 'none' });\n let changed = false;\n if (outtype === 'bt') {\n changed = output !== 'bt' && output !== '';\n output = 'bt';\n } else if (outtype === 'spdif') {\n changed = output !== 'spdif' && output !== '';\n output = 'spdif';\n } else {\n changed = output !== 'i2s' && output !== '';\n output = 'i2s';\n }\n $('#' + output).prop('checked', true);\n $('#o_' + output).css({ display: 'inline' });\n if (changed) {\n Object.keys(commandDefaults[output]).forEach(function (key) {\n $(`#cmd_opt_${key}`).val(commandDefaults[output][key]);\n });\n }\n}\n\nfunction handleExceptionResponse(xhr, _ajaxOptions, thrownError) {\n console.log(xhr.status);\n console.log(thrownError);\n if (thrownError !== '') {\n showLocalMessage(thrownError, 'MESSAGING_ERROR');\n }\n}\nfunction HideCmdMessage(cmdname) {\n $('#toast_' + cmdname)\n .removeClass('table-success')\n .removeClass('table-warning')\n .removeClass('table-danger')\n .addClass('table-success')\n .removeClass('show');\n $('#msg_' + cmdname).html('');\n}\nfunction showCmdMessage(cmdname, msgtype, msgtext, append = false) {\n let color = 'table-success';\n if (msgtype === 'MESSAGING_WARNING') {\n color = 'table-warning';\n } else if (msgtype === 'MESSAGING_ERROR') {\n color = 'table-danger';\n }\n $('#toast_' + cmdname)\n .removeClass('table-success')\n .removeClass('table-warning')\n .removeClass('table-danger')\n .addClass(color)\n .addClass('show');\n let escapedtext = msgtext\n .substring(0, msgtext.length - 1)\n .encodeHTML()\n .replace(/\\n/g, '
');\n escapedtext =\n ($('#msg_' + cmdname).html().length > 0 && append\n ? $('#msg_' + cmdname).html() + '
'\n : '') + escapedtext;\n $('#msg_' + cmdname).html(escapedtext);\n}\n\nlet releaseURL =\n 'https://api.github.com/repos/sle118/squeezelite-esp32/releases';\n\nlet recovery = false;\nlet messagesHeld = false;\nlet commandBTSinkName = '';\nconst commandHeader = 'squeezelite ';\nconst commandDefaults = {\n i2s: { b: \"500:2000\", C: \"30\", W: \"\", Z: \"96000\", o: \"I2S\" },\n spdif: { b: \"500:2000\", C: \"30\", W: \"\", Z: \"48000\", o: \"SPDIF\" },\n bt: { b: \"500:2000\", C: \"30\", W: \"\", Z: \"44100\", o: \"BT\" },\n};\nlet validOptions = {\n codecs: ['flac', 'pcm', 'mp3', 'ogg', 'aac', 'wma', 'alac', 'dsd', 'mad', 'mpg']\n};\n\n//let blockFlashButton = false;\nlet apList = null;\n//let selectedSSID = '';\n//let checkStatusInterval = null;\nlet messagecount = 0;\nlet messageseverity = 'MESSAGING_INFO';\nlet SystemConfig = {};\nlet LastCommandsState = null;\nvar output = '';\nlet hostName = '';\nlet versionName = 'Squeezelite-ESP32';\nlet prevmessage = '';\nlet project_name = versionName;\nlet depth = 16;\nlet board_model = '';\nlet platform_name = versionName;\nlet preset_name = '';\nlet btSinkNamesOptSel = '#cfg-audio-bt_source-sink_name';\nlet ConnectedTo = {};\nlet ConnectingToSSID = {};\nlet lmsBaseUrl;\nlet prevLMSIP = '';\nconst ConnectingToActions = {\n 'CONN': 0, 'MAN': 1, 'STS': 2,\n}\n\nPromise.prototype.delay = function (duration) {\n return this.then(\n function (value) {\n return new Promise(function (resolve) {\n setTimeout(function () {\n resolve(value);\n }, duration);\n });\n },\n function (reason) {\n return new Promise(function (_resolve, reject) {\n setTimeout(function () {\n reject(reason);\n }, duration);\n });\n }\n );\n};\n\nfunction getConfigJson(slimMode) {\n const config = {};\n $('input.nvs').each(function (_index, entry) {\n if (!slimMode) {\n const nvsType = parseInt(entry.attributes.nvs_type.value, 10);\n if (entry.id !== '') {\n config[entry.id] = {};\n if (\n nvsType === nvsTypes.NVS_TYPE_U8 ||\n nvsType === nvsTypes.NVS_TYPE_I8 ||\n nvsType === nvsTypes.NVS_TYPE_U16 ||\n nvsType === nvsTypes.NVS_TYPE_I16 ||\n nvsType === nvsTypes.NVS_TYPE_U32 ||\n nvsType === nvsTypes.NVS_TYPE_I32 ||\n nvsType === nvsTypes.NVS_TYPE_U64 ||\n nvsType === nvsTypes.NVS_TYPE_I64\n ) {\n config[entry.id].value = parseInt(entry.value);\n } else {\n config[entry.id].value = entry.value;\n }\n config[entry.id].type = nvsType;\n }\n } else {\n config[entry.id] = entry.value;\n }\n });\n const key = $('#nvs-new-key').val();\n const val = $('#nvs-new-value').val();\n if (key !== '') {\n if (!slimMode) {\n config[key] = {};\n config[key].value = val;\n config[key].type = 33;\n } else {\n config[key] = val;\n }\n }\n return config;\n}\n\nfunction handleHWPreset(allfields, reboot) {\n\n const selJson = JSON.parse(allfields[0].value);\n var cmd = allfields[0].attributes.cmdname.value;\n\n console.log(`selected model: ${selJson.name}`);\n let confPayload = {\n timestamp: Date.now(),\n config: { model_config: { value: selJson.name, type: 33 } }\n };\n for (const [name, value] of Object.entries(selJson.config)) {\n const storedval = (typeof value === 'string' || value instanceof String) ? value : JSON.stringify(value);\n confPayload.config[name] = {\n value: storedval,\n type: 33,\n }\n showCmdMessage(\n cmd,\n 'MESSAGING_INFO',\n `Setting ${name}=${storedval} `,\n true\n );\n }\n\n showCmdMessage(\n cmd,\n 'MESSAGING_INFO',\n `Committing `,\n true\n );\n $.ajax({\n url: '/config.json',\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify(confPayload),\n error: function (xhr, _ajaxOptions, thrownError) {\n handleExceptionResponse(xhr, _ajaxOptions, thrownError);\n showCmdMessage(\n cmd,\n 'MESSAGING_ERROR',\n `Unexpected error ${(thrownError !== '') ? thrownError : 'with return status = ' + xhr.status} `,\n true\n );\n },\n success: function (response) {\n showCmdMessage(\n cmd,\n 'MESSAGING_INFO',\n `Saving complete `,\n true\n );\n console.log(response);\n if (reboot) {\n delayReboot(2500, cmd);\n }\n },\n });\n}\n\n\n// pull json file from https://gist.githubusercontent.com/sle118/dae585e157b733a639c12dc70f0910c5/raw/b462691f69e2ad31ac95c547af6ec97afb0f53db/squeezelite-esp32-presets.json and\nfunction loadPresets() {\n if ($(\"#cfg-hw-preset-model_config\").length == 0) return;\n if (presetsloaded) return;\n presetsloaded = true;\n $('#cfg-hw-preset-model_config').html('');\n $.getJSON(\n 'https://gist.githubusercontent.com/sle118/dae585e157b733a639c12dc70f0910c5/raw/',\n { _: new Date().getTime() },\n function (data) {\n $.each(data, function (key, val) {\n $('#cfg-hw-preset-model_config').append(``);\n if (preset_name !== '' && preset_name == val.name) {\n $('#cfg-hw-preset-model_config').val(preset_name);\n }\n });\n if (preset_name !== '') {\n ('#prev_preset').show().val(preset_name);\n }\n }\n\n ).fail(function (jqxhr, textStatus, error) {\n const err = textStatus + ', ' + error;\n console.log('Request Failed: ' + err);\n }\n );\n}\n\nfunction delayReboot(duration, cmdname, ota = 'reboot') {\n const url = '/' + ota + '.json';\n $('tbody#tasks').empty();\n $('#tasks_sect').css('visibility', 'collapse');\n Promise.resolve({ cmdname: cmdname, url: url })\n .delay(duration)\n .then(function (data) {\n if (data.cmdname.length > 0) {\n showCmdMessage(\n data.cmdname,\n 'MESSAGING_WARNING',\n 'System is rebooting.\\n',\n true\n );\n } else {\n showLocalMessage('System is rebooting.\\n', 'MESSAGING_WARNING');\n }\n console.log('now triggering reboot');\n $(\"button[onclick*='handleReboot']\").addClass('rebooting');\n $.ajax({\n url: data.url,\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n timestamp: Date.now(),\n }),\n error: handleExceptionResponse,\n complete: function () {\n console.log('reboot call completed');\n Promise.resolve(data)\n .delay(6000)\n .then(function (rdata) {\n if (rdata.cmdname.length > 0) {\n HideCmdMessage(rdata.cmdname);\n }\n getCommands();\n getConfig();\n });\n },\n });\n });\n}\n// eslint-disable-next-line no-unused-vars\nwindow.saveAutoexec1 = function (apply) {\n showCmdMessage('cfg-audio-tmpl', 'MESSAGING_INFO', 'Saving.\\n', false);\n let commandLine = `${commandHeader} -o ${output} `;\n $('.sqcmd').each(function () {\n let { opt, val } = get_control_option_value($(this));\n if ((opt && opt.length>0 ) && typeof(val) == 'boolean' || val.length > 0) {\n const optStr=opt===':'?opt:(` -${opt} `);\n val = typeof(val) == 'boolean'?'':val;\n commandLine += `${optStr} ${val}`;\n }\n });\n const resample=$('#cmd_opt_R input[name=resample]:checked');\n if (resample.length>0 && resample.attr('suffix')!=='') {\n commandLine += resample.attr('suffix');\n // now check resample_i option and if checked, add suffix to command line\n if ($('#resample_i').is(\":checked\") && resample.attr('aint') =='true') {\n commandLine += $('#resample_i').attr('suffix');\n }\n}\n\n \n if (output === 'bt') {\n showCmdMessage(\n 'cfg-audio-tmpl',\n 'MESSAGING_INFO',\n 'Remember to configure the Bluetooth audio device name.\\n',\n true\n );\n }\n commandLine += concatenateOptions(options);\n const data = {\n timestamp: Date.now(),\n };\n data.config = {\n autoexec1: { value: commandLine, type: 33 },\n autoexec: {\n value: $('#disable-squeezelite').prop('checked') ? '0' : '1',\n type: 33,\n },\n };\n\n $.ajax({\n url: '/config.json',\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify(data),\n error: handleExceptionResponse,\n complete: function (response) {\n if (\n response.responseText &&\n JSON.parse(response.responseText).result === 'OK'\n ) {\n showCmdMessage('cfg-audio-tmpl', 'MESSAGING_INFO', 'Done.\\n', true);\n if (apply) {\n delayReboot(1500, 'cfg-audio-tmpl');\n }\n } else if (JSON.parse(response.responseText).result) {\n showCmdMessage(\n 'cfg-audio-tmpl',\n 'MESSAGING_WARNING',\n JSON.parse(response.responseText).Result + '\\n',\n true\n );\n } else {\n showCmdMessage(\n 'cfg-audio-tmpl',\n 'MESSAGING_ERROR',\n response.statusText + '\\n'\n );\n }\n console.log(response.responseText);\n },\n });\n console.log('sent data:', JSON.stringify(data));\n}\nwindow.handleDisconnect = function () {\n $.ajax({\n url: '/connect.json',\n dataType: 'text',\n method: 'DELETE',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n timestamp: Date.now(),\n }),\n });\n}\nfunction setPlatformFilter(val) {\n if ($('.upf').filter(function () { return $(this).text().toUpperCase() === val.toUpperCase() }).length > 0) {\n $('#splf').val(val).trigger('input');\n return true;\n }\n return false;\n}\nwindow.handleConnect = function () {\n ConnectingToSSID.ssid = $('#manual_ssid').val();\n ConnectingToSSID.pwd = $('#manual_pwd').val();\n ConnectingToSSID.dhcpname = $('#dhcp-name2').val();\n $(\"*[class*='connecting']\").hide();\n $('#ssid-wait').text(ConnectingToSSID.ssid);\n $('.connecting').show();\n $.ajax({\n url: '/connect.json',\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n timestamp: Date.now(),\n ssid: ConnectingToSSID.ssid,\n pwd: ConnectingToSSID.pwd\n }),\n error: handleExceptionResponse,\n });\n\n // now we can re-set the intervals regardless of result\n\n}\nfunction renderError(opt,error){\n const fieldname = `cmd_opt_${opt}`;\n let errorFieldName=`${fieldname}-error`;\n let errorField=$(`#${errorFieldName}`);\n let field=$(`#${fieldname}`);\n \n if (!errorField || errorField.length ==0) {\n field.after(`
`);\n errorField=$(`#${errorFieldName}`);\n }\n if(error.length ==0){\n errorField.hide();\n field.removeClass('is-invalid');\n field.addClass('is-valid');\n errorField.text('');\n }\n else { \n errorField.show();\n errorField.text(error);\n field.removeClass('is-valid');\n field.addClass('is-invalid');\n }\n return errorField;\n}\n$(document).ready(function () {\n $('.material-icons').each(function (_index, entry) {\n entry.attributes['icon'] = entry.textContent;\n });\n setIcons(true);\n handleNVSVisible();\n flashState.init();\n $('#fw-url-input').on('input', function () {\n if ($(this).val().length > 8 && ($(this).val().startsWith('http://') || $(this).val().startsWith('https://'))) {\n $('#start-flash').show();\n }\n else {\n $('#start-flash').hide();\n }\n });\n $('.upSrch').on('input', function () {\n const val = this.value;\n $(\"#rTable tr\").removeClass(this.id + '_hide');\n if (val.length > 0) {\n $(`#rTable td:nth-child(${$(this).parent().index() + 1})`).filter(function () {\n return !$(this).text().toUpperCase().includes(val.toUpperCase());\n }).parent().addClass(this.id + '_hide');\n }\n $('[class*=\"_hide\"]').hide();\n $('#rTable tr').not('[class*=\"_hide\"]').show()\n\n });\n setTimeout(refreshAP, 1500);\n /* add validation for cmd_opt_c, which accepts a comma separated list. \n getting known codecs from validOptions.codecs array\n use bootstrap classes to highlight the error with an overlay message */\n $('#options input').on('input', function () {\n const { opt, val } = get_control_option_value(this);\n if (opt === 'c' || opt === 'e') {\n const fieldname = `cmd_opt_${opt}_codec-error`;\n \n const values = val.split(',').map(function (item) {\n return item.trim();\n });\n /* get a list of invalid codecs */\n const invalid = values.filter(function (item) {\n return !validOptions.codecs.includes(item);\n });\n renderError(opt,invalid.length > 0 ? `Invalid codec(s) ${invalid.join(', ')}` : '');\n }\n /* add validation for cmd_opt_m, which accepts a mac_address */\n if (opt === 'm') {\n const mac_regex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;\n renderError(opt,mac_regex.test(val) ? '' : 'Invalid MAC address');\n }\n if (opt === 'r') {\n const rateRegex = /^(\\d+\\.?\\d*|\\.\\d+)-(\\d+\\.?\\d*|\\.\\d+)$|^(\\d+\\.?\\d*)$|^(\\d+\\.?\\d*,)+\\d+\\.?\\d*$/;\n renderError(opt,rateRegex.test(val)?'':`Invalid rate(s) ${val}. Acceptable format: |-|,,`);\n }\n\n\n\n }\n\n\n );\n\n\n\n\n\n $('#WifiConnectDialog')[0].addEventListener('shown.bs.modal', function (event) {\n $(\"*[class*='connecting']\").hide();\n\n if (event?.relatedTarget) {\n ConnectingToSSID.Action = ConnectingToActions.CONN;\n if ($(event.relatedTarget).children('td:eq(1)').text() == ConnectedTo.ssid) {\n ConnectingToSSID.Action = ConnectingToActions.STS;\n }\n else {\n if (!$(event.relatedTarget).is(':last-child')) {\n ConnectingToSSID.ssid = $(event.relatedTarget).children('td:eq(1)').text();\n $('#manual_ssid').val(ConnectingToSSID.ssid);\n }\n else {\n ConnectingToSSID.Action = ConnectingToActions.MAN;\n ConnectingToSSID.ssid = '';\n $('#manual_ssid').val(ConnectingToSSID.ssid);\n }\n }\n }\n\n\n if (ConnectingToSSID.Action !== ConnectingToActions.STS) {\n $('.connecting-init').show();\n $('#manual_ssid').trigger('focus');\n }\n else {\n handleWifiDialog();\n }\n });\n\n $('#WifiConnectDialog')[0].addEventListener('hidden.bs.modal', function () {\n $('#WifiConnectDialog input').val('');\n });\n\n $('#uCnfrm')[0].addEventListener('shown.bs.modal', function () {\n $('#selectedFWURL').text($('#fw-url-input').val());\n });\n\n $('input#show-commands')[0].checked = LastCommandsState === 1;\n $('a[href^=\"#tab-commands\"]').hide();\n $('#load-nvs').on('click', function () {\n $('#nvsfilename').trigger('click');\n });\n $('#nvsfilename').on('change', function () {\n if (typeof window.FileReader !== 'function') {\n throw \"The file API isn't supported on this browser.\";\n }\n if (!this.files) {\n throw 'This browser does not support the `files` property of the file input.';\n }\n if (!this.files[0]) {\n return undefined;\n }\n\n const file = this.files[0];\n let fr = new FileReader();\n fr.onload = function (e) {\n let data = {};\n try {\n data = JSON.parse(e.target.result);\n } catch (ex) {\n alert('Parsing failed!\\r\\n ' + ex);\n }\n $('input.nvs').each(function (_index, entry) {\n $(this).parent().removeClass('bg-warning').removeClass('bg-success');\n if (data[entry.id]) {\n if (data[entry.id] !== entry.value) {\n console.log(\n 'Changed ' + entry.id + ' ' + entry.value + '==>' + data[entry.id]\n );\n $(this).parent().addClass('bg-warning');\n $(this).val(data[entry.id]);\n }\n else {\n $(this).parent().addClass('bg-success');\n }\n }\n });\n var changed = $(\"input.nvs\").children('.bg-warning');\n if (changed) {\n alert('Highlighted values were changed. Press Commit to change on the device');\n }\n }\n fr.readAsText(file);\n this.value = null;\n\n }\n );\n $('#clear-syslog').on('click', function () {\n messagecount = 0;\n messageseverity = 'MESSAGING_INFO';\n $('#msgcnt').text('');\n $('#syslogTable').html('');\n });\n\n $('#ok-credits').on('click', function () {\n $('#credits').slideUp('fast', function () { });\n $('#app').slideDown('fast', function () { });\n });\n\n $('#acredits').on('click', function (event) {\n event.preventDefault();\n $('#app').slideUp('fast', function () { });\n $('#credits').slideDown('fast', function () { });\n });\n\n $('input#show-commands').on('click', function () {\n this.checked = this.checked ? 1 : 0;\n if (this.checked) {\n $('a[href^=\"#tab-commands\"]').show();\n LastCommandsState = 1;\n } else {\n LastCommandsState = 0;\n $('a[href^=\"#tab-commands\"]').hide();\n }\n });\n\n $('input#show-nvs').on('click', function () {\n this.checked = this.checked ? 1 : 0;\n Cookies.set(\"show-nvs\", this.checked ? 'Y' : 'N');\n handleNVSVisible();\n });\n $('#btn_reboot_recovery').on('click', function () {\n handleReboot('recovery');\n });\n $('#btn_reboot').on('click', function () {\n handleReboot('reboot');\n });\n $('#btn_flash').on('click', function () {\n hFlash();\n });\n $('#save-autoexec1').on('click', function () {\n saveAutoexec1(false);\n });\n $('#commit-autoexec1').on('click', function () {\n saveAutoexec1(true);\n });\n $('#btn_disconnect').on('click', function () {\n ConnectedTo = {};\n refreshAPHTML2();\n $.ajax({\n url: '/connect.json',\n dataType: 'text',\n method: 'DELETE',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n timestamp: Date.now(),\n }),\n });\n });\n $('#btnJoin').on('click', function () {\n handleConnect();\n });\n $('#reboot_nav').on('click', function () {\n handleReboot('reboot');\n });\n $('#reboot_ota_nav').on('click', function () {\n handleReboot('reboot_ota');\n });\n\n $('#save-as-nvs').on('click', function () {\n const config = getConfigJson(true);\n const a = document.createElement('a');\n a.href = URL.createObjectURL(\n new Blob([JSON.stringify(config, null, 2)], {\n type: 'text/plain',\n })\n );\n a.setAttribute(\n 'download',\n 'nvs_config_' + hostName + '_' + Date.now() + 'json'\n );\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n });\n\n $('#save-nvs').on('click', function () {\n post_config(getConfigJson(false));\n });\n\n $('#fwUpload').on('click', function () {\n const fileInput = document.getElementById('flashfilename').files;\n if (fileInput.length === 0) {\n alert('No file selected!');\n } else {\n $('#fw-url-input').value = null;\n flashState.StartOTA();\n }\n\n });\n $('[name=output-tmpl]').on('click', function () {\n handleTemplateTypeRadio(this.id);\n });\n\n $('#chkUpdates').on('click', function () {\n $('#rTable').html('');\n $.getJSON(releaseURL, function (data) {\n let i = 0;\n const branches = [];\n data.forEach(function (release) {\n const namecomponents = release.name.split('#');\n const branch = namecomponents[3];\n if (!branches.includes(branch)) {\n branches.push(branch);\n }\n });\n let fwb = '';\n branches.forEach(function (branch) {\n fwb += '';\n });\n $('#fwbranch').append(fwb);\n\n data.forEach(function (release) {\n let url = '';\n release.assets.forEach(function (asset) {\n if (asset.name.match(/\\.bin$/)) {\n url = asset.browser_download_url;\n }\n });\n const namecomponents = release.name.split('#');\n const ver = namecomponents[0];\n const cfg = namecomponents[2];\n const branch = namecomponents[3];\n var bits = ver.substr(ver.lastIndexOf('-') + 1);\n bits = (bits == '32' || bits == '16') ? bits : '';\n\n let body = release.body;\n body = body.replace(/'/gi, '\"');\n body = body.replace(\n /[\\s\\S]+(### Revision Log[\\s\\S]+)### ESP-IDF Version Used[\\s\\S]+/,\n '$1'\n );\n body = body.replace(/- \\(.+?\\) /g, '- ').encodeHTML();\n $('#rTable').append(`\n ${ver}${new Date(release.created_at).toLocalShort()}\n ${cfg}${branch}${bits}`\n );\n });\n if (i > 7) {\n $('#releaseTable').append(\n \"\" +\n \"\" +\n \"\" +\n '' +\n ''\n );\n $('#showallbutton').on('click', function () {\n $('tr.hide').removeClass('hide');\n $('tr#showall').addClass('hide');\n });\n }\n $('#searchfw').css('display', 'inline');\n if (!setPlatformFilter(platform_name)) {\n setPlatformFilter(project_name)\n }\n $('#rTable tr.release').on('click', function () {\n var url = this.attributes['fwurl'].value;\n if (lmsBaseUrl) {\n url = url.replace(/.*\\/download\\//, lmsBaseUrl + '/plugins/SqueezeESP32/firmware/');\n }\n $('#fw-url-input').val(url);\n $('#start-flash').show();\n $('#rTable tr.release').removeClass('table-success table-warning');\n $(this).addClass('table-success table-warning');\n });\n\n }).fail(function () {\n alert('failed to fetch release history!');\n });\n });\n $('#fwcheck').on('click', function () {\n $('#releaseTable').html('');\n $('#fwbranch').empty();\n $.getJSON(releaseURL, function (data) {\n let i = 0;\n const branches = [];\n data.forEach(function (release) {\n const namecomponents = release.name.split('#');\n const branch = namecomponents[3];\n if (!branches.includes(branch)) {\n branches.push(branch);\n }\n });\n let fwb;\n branches.forEach(function (branch) {\n fwb += '';\n });\n $('#fwbranch').append(fwb);\n\n data.forEach(function (release) {\n let url = '';\n release.assets.forEach(function (asset) {\n if (asset.name.match(/\\.bin$/)) {\n url = asset.browser_download_url;\n }\n });\n const namecomponents = release.name.split('#');\n const ver = namecomponents[0];\n const idf = namecomponents[1];\n const cfg = namecomponents[2];\n const branch = namecomponents[3];\n\n let body = release.body;\n body = body.replace(/'/gi, '\"');\n body = body.replace(\n /[\\s\\S]+(### Revision Log[\\s\\S]+)### ESP-IDF Version Used[\\s\\S]+/,\n '$1'\n );\n body = body.replace(/- \\(.+?\\) /g, '- ');\n const trclass = i++ > 6 ? ' hide' : '';\n $('#releaseTable').append(\n \"\" +\n \"\" +\n ver +\n '' +\n '' +\n new Date(release.created_at).toLocalShort() +\n '' +\n '' +\n cfg +\n '' +\n '' +\n idf +\n '' +\n '' +\n branch +\n '' +\n \"\" +\n ''\n );\n });\n if (i > 7) {\n $('#releaseTable').append(\n \"\" +\n \"\" +\n \"\" +\n '' +\n ''\n );\n $('#showallbutton').on('click', function () {\n $('tr.hide').removeClass('hide');\n $('tr#showall').addClass('hide');\n });\n }\n $('#searchfw').css('display', 'inline');\n }).fail(function () {\n alert('failed to fetch release history!');\n });\n });\n\n $('#updateAP').on('click', function () {\n refreshAP();\n console.log('refresh AP');\n });\n\n // first time the page loads: attempt to get the connection status and start the wifi scan\n getConfig();\n getCommands();\n getMessages();\n checkStatus();\n\n});\n\n// eslint-disable-next-line no-unused-vars\nwindow.setURL = function (button) {\n let url = button.dataset.url;\n\n $('[data-bs-url^=\"http\"]')\n .addClass('btn-success')\n .removeClass('btn-danger');\n $('[data-bs-url=\"' + url + '\"]')\n .addClass('btn-danger')\n .removeClass('btn-success');\n\n // if user can proxy download through LMS, modify the URL\n if (lmsBaseUrl) {\n url = url.replace(/.*\\/download\\//, lmsBaseUrl + '/plugins/SqueezeESP32/firmware/');\n }\n\n $('#fwurl').val(url);\n}\n\n\nfunction rssiToIcon(rssi) {\n if (rssi >= -55) {\n return { 'label': '****', 'icon': `signal_wifi_statusbar_4_bar` };\n } else if (rssi >= -60) {\n return { 'label': '***', 'icon': `network_wifi_3_bar` };\n } else if (rssi >= -65) {\n return { 'label': '**', 'icon': `network_wifi_2_bar` };\n } else if (rssi >= -70) {\n return { 'label': '*', 'icon': `network_wifi_1_bar` };\n } else {\n return { 'label': '.', 'icon': `signal_wifi_statusbar_null` };\n }\n}\n\nfunction refreshAP() {\n if (ConnectedTo?.urc === connectReturnCode.ETH) return;\n $.ajaxSetup({\n timeout: 3000 //Time in milliseconds\n });\n $.getJSON('/scan.json', async function () {\n await sleep(2000);\n $.getJSON('/ap.json', function (data) {\n if (data.length > 0) {\n // sort by signal strength\n data.sort(function (a, b) {\n const x = a.rssi;\n const y = b.rssi;\n // eslint-disable-next-line no-nested-ternary\n return x < y ? 1 : x > y ? -1 : 0;\n });\n apList = data;\n refreshAPHTML2(apList);\n\n }\n });\n });\n}\nfunction formatAP(ssid, rssi, auth) {\n const rssi_icon = rssiToIcon(rssi);\n const auth_icon = { label: auth == 0 ? '🔓' : '🔒', icon: auth == 0 ? 'no_encryption' : 'lock' };\n\n return `${ssid}\n ${getIcon(rssi_icon)}\n \t\n ${getIcon(auth_icon)}\n `;\n}\nfunction refreshAPHTML2(data) {\n let h = '';\n $('#wifiTable tr td:first-of-type').text('');\n $('#wifiTable tr').removeClass('table-success table-warning');\n if (data) {\n data.forEach(function (e) {\n h += formatAP(e.ssid, e.rssi, e.auth);\n });\n $('#wifiTable').html(h);\n }\n if ($('.manual_add').length == 0) {\n $('#wifiTable').append(formatAP('Manual add', 0, 0));\n $('#wifiTable tr:last').addClass('table-light text-dark').addClass('manual_add');\n }\n if (ConnectedTo.ssid && (ConnectedTo.urc === connectReturnCode.OK || ConnectedTo.urc === connectReturnCode.RESTORE)) {\n const wifiSelector = `#wifiTable td:contains(\"${ConnectedTo.ssid}\")`;\n if ($(wifiSelector).filter(function () { return $(this).text() === ConnectedTo.ssid; }).length == 0) {\n $('#wifiTable').prepend(`${formatAP(ConnectedTo.ssid, ConnectedTo.rssi ?? 0, 0)}`);\n }\n $(wifiSelector).filter(function () { return $(this).text() === ConnectedTo.ssid; }).siblings().first().html('✓').parent().addClass((ConnectedTo.urc === connectReturnCode.OK ? 'table-success' : 'table-warning'));\n $('span#foot-if').html(`SSID: ${ConnectedTo.ssid}, IP: ${ConnectedTo.ip}`);\n $('#wifiStsIcon').html(rssiToIcon(ConnectedTo.rssi));\n\n }\n else if (ConnectedTo?.urc !== connectReturnCode.ETH) {\n $('span#foot-if').html('');\n }\n\n}\nfunction refreshETH() {\n\n if (ConnectedTo.urc === connectReturnCode.ETH) {\n $('span#foot-if').html(`Network: Ethernet, IP: ${ConnectedTo.ip}`);\n }\n}\nfunction showTask(task) {\n console.debug(\n this.toLocaleString() +\n '\\t' +\n task.nme +\n '\\t' +\n task.cpu +\n '\\t' +\n taskStates[task.st] +\n '\\t' +\n task.minstk +\n '\\t' +\n task.bprio +\n '\\t' +\n task.cprio +\n '\\t' +\n task.num\n );\n $('tbody#tasks').append(\n '' +\n task.num +\n '' +\n task.nme +\n '' +\n task.cpu +\n '' +\n taskStates[task.st] +\n '' +\n task.minstk +\n '' +\n task.bprio +\n '' +\n task.cprio +\n ''\n );\n}\nfunction btExists(name) {\n return getBTSinkOpt(name).length > 0;\n}\nfunction getBTSinkOpt(name) {\n return $(`${btSinkNamesOptSel} option:contains('${name}')`);\n}\nfunction getMessages() {\n $.ajaxSetup({\n timeout: messageInterval //Time in milliseconds\n });\n $.getJSON('/messages.json', async function (data) {\n for (const msg of data) {\n const msgAge = msg.current_time - msg.sent_time;\n var msgTime = new Date();\n msgTime.setTime(msgTime.getTime() - msgAge);\n switch (msg.class) {\n case 'MESSAGING_CLASS_OTA':\n flashState.EventOTAMessageClass(msg.message);\n break;\n case 'MESSAGING_CLASS_STATS':\n // for task states, check structure : task_state_t\n var statsData = JSON.parse(msg.message);\n console.debug(\n msgTime.toLocalShort() +\n ' - Number of running tasks: ' +\n statsData.ntasks\n );\n console.debug(\n msgTime.toLocalShort() +\n '\\tname' +\n '\\tcpu' +\n '\\tstate' +\n '\\tminstk' +\n '\\tbprio' +\n '\\tcprio' +\n '\\tnum'\n );\n if (statsData.tasks) {\n if ($('#tasks_sect').css('visibility') === 'collapse') {\n $('#tasks_sect').css('visibility', 'visible');\n }\n $('tbody#tasks').html('');\n statsData.tasks\n .sort(function (a, b) {\n return b.cpu - a.cpu;\n })\n .forEach(showTask, msgTime);\n } else if ($('#tasks_sect').css('visibility') === 'visible') {\n $('tbody#tasks').empty();\n $('#tasks_sect').css('visibility', 'collapse');\n }\n break;\n case 'MESSAGING_CLASS_SYSTEM':\n showMessage(msg, msgTime);\n break;\n case 'MESSAGING_CLASS_CFGCMD':\n var msgparts = msg.message.split(/([^\\n]*)\\n(.*)/gs);\n showCmdMessage(msgparts[1], msg.type, msgparts[2], true);\n break;\n case 'MESSAGING_CLASS_BT':\n if ($(\"#cfg-audio-bt_source-sink_name\").is('input')) {\n var attr = $(\"#cfg-audio-bt_source-sink_name\")[0].attributes;\n var attrs = '';\n for (var j = 0; j < attr.length; j++) {\n if (attr.item(j).name != \"type\") {\n attrs += `${attr.item(j).name} = \"${attr.item(j).value}\" `;\n }\n }\n var curOpt = $(\"#cfg-audio-bt_source-sink_name\")[0].value;\n $(\"#cfg-audio-bt_source-sink_name\").replaceWith(` `);\n }\n JSON.parse(msg.message).forEach(function (btEntry) {\n //\n // \n if (!btExists(btEntry.name)) {\n $(\"#cfg-audio-bt_source-sink_name\").append(``);\n showMessage({ type: msg.type, message: `BT Audio device found: ${btEntry.name} RSSI: ${btEntry.rssi} ` }, msgTime);\n }\n getBTSinkOpt(btEntry.name).attr('data-bs-description', `${btEntry.name} (${btEntry.rssi}dB)`)\n .attr('rssi', btEntry.rssi)\n .attr('value', btEntry.name)\n .text(`${btEntry.name} [${btEntry.rssi}dB]`).trigger('change');\n\n });\n $(btSinkNamesOptSel).append($(`${btSinkNamesOptSel} option`).remove().sort(function (a, b) {\n console.log(`${parseInt($(a).attr('rssi'))} < ${parseInt($(b).attr('rssi'))} ? `);\n return parseInt($(a).attr('rssi')) < parseInt($(b).attr('rssi')) ? 1 : -1;\n }));\n break;\n default:\n break;\n }\n }\n setTimeout(getMessages, messageInterval);\n }).fail(function (xhr, ajaxOptions, thrownError) {\n\n if (xhr.status == 404) {\n $('.orec').hide(); // system commands won't be available either\n messagesHeld = true;\n }\n else {\n handleExceptionResponse(xhr, ajaxOptions, thrownError);\n }\n if (xhr.status == 0 && xhr.readyState == 0) {\n // probably a timeout. Target is rebooting? \n setTimeout(getMessages, messageInterval * 2); // increase duration if a failure happens\n }\n else if (!messagesHeld) {\n // 404 here means we rebooted to an old recovery\n setTimeout(getMessages, messageInterval); // increase duration if a failure happens\n }\n\n }\n );\n\n /*\n Minstk is minimum stack space left\nBprio is base priority\ncprio is current priority\nnme is name\nst is task state. I provided a \"typedef\" that you can use to convert to text\ncpu is cpu percent used\n*/\n}\nfunction handleRecoveryMode(data) {\n const locRecovery = data.recovery ?? 0;\n if (locRecovery === 1) {\n recovery = true;\n $('.recovery_element').show();\n $('.ota_element').hide();\n $('#boot-button').html('Reboot');\n $('#boot-form').attr('action', '/reboot_ota.json');\n } else {\n if (!recovery && messagesHeld) {\n messagesHeld = false;\n setTimeout(getMessages, messageInterval); // increase duration if a failure happens\n }\n recovery = false;\n\n $('.recovery_element').hide();\n $('.ota_element').show();\n $('#boot-button').html('Recovery');\n $('#boot-form').attr('action', '/recovery.json');\n }\n\n}\n\nfunction hasConnectionChanged(data) {\n // gw: \"192.168.10.1\"\n // ip: \"192.168.10.225\"\n // netmask: \"255.255.255.0\"\n // ssid: \"MyTestSSID\"\n\n return (data.urc !== ConnectedTo.urc ||\n data.ssid !== ConnectedTo.ssid ||\n data.gw !== ConnectedTo.gw ||\n data.netmask !== ConnectedTo.netmask ||\n data.ip !== ConnectedTo.ip || data.rssi !== ConnectedTo.rssi)\n}\nfunction handleWifiDialog(data) {\n if ($('#WifiConnectDialog').is(':visible')) {\n if (ConnectedTo.ip) {\n $('#ipAddress').text(ConnectedTo.ip);\n }\n if (ConnectedTo.ssid) {\n $('#connectedToSSID').text(ConnectedTo.ssid);\n }\n if (ConnectedTo.gw) {\n $('#gateway').text(ConnectedTo.gw);\n }\n if (ConnectedTo.netmask) {\n $('#netmask').text(ConnectedTo.netmask);\n }\n if (ConnectingToSSID.Action === undefined || (ConnectingToSSID.Action && ConnectingToSSID.Action == ConnectingToActions.STS)) {\n $(\"*[class*='connecting']\").hide();\n $('.connecting-status').show();\n }\n if (SystemConfig.ap_ssid) {\n $('#apName').text(SystemConfig.ap_ssid.value);\n }\n if (SystemConfig.ap_pwd) {\n $('#apPass').text(SystemConfig.ap_pwd.value);\n }\n if (!data) {\n return;\n }\n else {\n switch (data.urc) {\n case connectReturnCode.OK:\n if (data.ssid && data.ssid === ConnectingToSSID.ssid) {\n $(\"*[class*='connecting']\").hide();\n $('.connecting-success').show();\n ConnectingToSSID.Action = ConnectingToActions.STS;\n }\n break;\n case connectReturnCode.FAIL:\n // \n if (ConnectingToSSID.Action != ConnectingToActions.STS && ConnectingToSSID.ssid == data.ssid) {\n $(\"*[class*='connecting']\").hide();\n $('.connecting-fail').show();\n }\n break;\n case connectReturnCode.LOST:\n\n break;\n case connectReturnCode.RESTORE:\n if (ConnectingToSSID.Action != ConnectingToActions.STS && ConnectingToSSID.ssid != data.ssid) {\n $(\"*[class*='connecting']\").hide();\n $('.connecting-fail').show();\n }\n break;\n case connectReturnCode.DISC:\n // that's a manual disconnect\n // if ($('#wifi-status').is(':visible')) {\n // $('#wifi-status').slideUp('fast', function() {});\n // $('span#foot-wifi').html('');\n\n // } \n break;\n default:\n break;\n }\n }\n\n }\n}\nfunction setIcons(offline) {\n $('.material-icons').each(function (_index, entry) {\n entry.textContent = entry.attributes[offline ? 'aria-label' : 'icon'].value;\n });\n}\nfunction handleNetworkStatus(data) {\n setIcons(!isConnected());\n if (hasConnectionChanged(data) || !data.urc) {\n ConnectedTo = data;\n $(\".if_eth\").hide();\n $('.if_wifi').hide();\n if (!data.urc || ConnectedTo.urc != connectReturnCode.ETH) {\n $('.if_wifi').show();\n refreshAPHTML2();\n }\n else {\n $(\".if_eth\").show();\n refreshETH();\n }\n\n }\n handleWifiDialog(data);\n}\n\n\n\nfunction batteryToIcon(voltage) {\n /* Assuming Li-ion 18650s as a power source, 3.9V per cell, or above is treated\n as full charge (>75% of capacity). 3.4V is empty. The gauge is loosely\n following the graph here:\n https://learn.adafruit.com/li-ion-and-lipoly-batteries/voltages\n using the 0.2C discharge profile for the rest of the values.\n*/\n\n for (const iconEntry of batIcons) {\n for (const entryRanges of iconEntry.ranges) {\n if (inRange(voltage, entryRanges.f, entryRanges.t)) {\n return { label: iconEntry.label, icon: iconEntry.icon };\n }\n }\n }\n\n\n return { label: 'â–Ēâ–Ēâ–Ēâ–Ē', icon: \"battery_full\" };\n}\nfunction checkStatus() {\n $.ajaxSetup({\n timeout: statusInterval //Time in milliseconds\n });\n $.getJSON('/status.json', function (data) {\n handleRecoveryMode(data);\n handleNVSVisible();\n handleNetworkStatus(data);\n handlebtstate(data);\n flashState.EventTargetStatus(data);\n if(data.depth) {\n depth = data.depth;\n if(depth==16){\n $('#cmd_opt_R').show();\n }\n else{\n $('#cmd_opt_R').hide();\n }\n }\n\n\n if (data.project_name && data.project_name !== '') {\n project_name = data.project_name;\n }\n if (data.platform_name && data.platform_name !== '') {\n platform_name = data.platform_name;\n }\n if (board_model === '') board_model = project_name;\n if (board_model === '') board_model = 'Squeezelite-ESP32';\n if (data.version && data.version !== '') {\n versionName = data.version;\n $(\"#navtitle\").html(`${board_model}${recovery ? '
[recovery]' : ''}`);\n $('span#foot-fw').html(`fw: ${versionName}, mode: ${recovery ? \"Recovery\" : project_name}`);\n } else {\n $('span#flash-status').html('');\n }\n if (data.Voltage) {\n const bat_icon = batteryToIcon(data.Voltage);\n $('#battery').html(`${getIcon(bat_icon)}`);\n $('#battery').attr(\"aria-label\", bat_icon.label);\n $('#battery').attr(\"icon\", bat_icon.icon);\n $('#battery').show();\n } else {\n $('#battery').hide();\n }\n if ((data.message ?? '') != '' && prevmessage != data.message) {\n // supporting older recovery firmwares - messages will come from the status.json structure\n prevmessage = data.message;\n showLocalMessage(data.message, 'MESSAGING_INFO')\n }\n is_i2c_locked = data.is_i2c_locked;\n if (is_i2c_locked) {\n $('flds-cfg-hw-preset').hide();\n }\n else {\n $('flds-cfg-hw-preset').show();\n }\n $(\"button[onclick*='handleReboot']\").removeClass('rebooting');\n\n if (typeof lmsBaseUrl == \"undefined\" || data.lms_ip != prevLMSIP && data.lms_ip && data.lms_port) {\n const baseUrl = 'http://' + data.lms_ip + ':' + data.lms_port;\n prevLMSIP = data.lms_ip;\n $.ajax({\n url: baseUrl + '/plugins/SqueezeESP32/firmware/-check.bin',\n type: 'HEAD',\n dataType: 'text',\n cache: false,\n error: function () {\n // define the value, so we don't check it any more.\n lmsBaseUrl = '';\n },\n success: function () {\n lmsBaseUrl = baseUrl;\n }\n });\n }\n $('#o_jack').css({ display: Number(data.Jack) ? 'inline' : 'none' });\n setTimeout(checkStatus, statusInterval);\n }).fail(function (xhr, ajaxOptions, thrownError) {\n handleExceptionResponse(xhr, ajaxOptions, thrownError);\n if (xhr.status == 0 && xhr.readyState == 0) {\n // probably a timeout. Target is rebooting? \n setTimeout(checkStatus, messageInterval * 2); // increase duration if a failure happens\n }\n else {\n setTimeout(checkStatus, messageInterval); // increase duration if a failure happens\n }\n });\n}\n// eslint-disable-next-line no-unused-vars\nwindow.runCommand = function (button, reboot) {\n let cmdstring = button.attributes.cmdname.value;\n showCmdMessage(\n button.attributes.cmdname.value,\n 'MESSAGING_INFO',\n 'Executing.',\n false\n );\n const fields = document.getElementById('flds-' + cmdstring);\n const allfields = fields?.querySelectorAll('select,input');\n if (cmdstring === 'cfg-hw-preset') return handleHWPreset(allfields, reboot);\n cmdstring += ' ';\n if (fields) {\n\n for (const field of allfields) {\n let qts = '';\n let opt = '';\n let attr = field.attributes;\n let isSelect = $(field).is('select');\n const hasValue = attr?.hasvalue?.value === 'true';\n const validVal = (isSelect && field.value !== '--') || (!isSelect && field.value !== '');\n\n if (!hasValue || hasValue && validVal) {\n if (attr?.longopts?.value !== 'undefined') {\n opt += '--' + attr?.longopts?.value;\n } else if (attr?.shortopts?.value !== 'undefined') {\n opt = '-' + attr.shortopts.value;\n }\n\n if (attr?.hasvalue?.value === 'true') {\n if (attr?.value !== '') {\n qts = /\\s/.test(field.value) ? '\"' : '';\n cmdstring += opt + ' ' + qts + field.value + qts + ' ';\n }\n } else {\n // this is a checkbox\n if (field?.checked) {\n cmdstring += opt + ' ';\n }\n }\n }\n }\n }\n\n console.log(cmdstring);\n\n const data = {\n timestamp: Date.now(),\n };\n data.command = cmdstring;\n\n $.ajax({\n url: '/commands.json',\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify(data),\n error: function (xhr, _ajaxOptions, thrownError) {\n var cmd = JSON.parse(this.data).command;\n if (xhr.status == 404) {\n showCmdMessage(\n cmd.substr(0, cmd.indexOf(' ')),\n 'MESSAGING_ERROR',\n `${recovery ? 'Limited recovery mode active. Unsupported action ' : 'Unexpected error while processing command'}`,\n true\n );\n }\n else {\n handleExceptionResponse(xhr, _ajaxOptions, thrownError);\n showCmdMessage(\n cmd.substr(0, cmd.indexOf(' ') - 1),\n 'MESSAGING_ERROR',\n `Unexpected error ${(thrownError !== '') ? thrownError : 'with return status = ' + xhr.status}`,\n true\n );\n }\n },\n success: function (response) {\n $('.orec').show();\n console.log(response);\n if (\n JSON.parse(response).Result === 'Success' &&\n reboot\n ) {\n delayReboot(2500, button.attributes.cmdname.value);\n }\n },\n });\n}\nfunction getLongOps(data, name, longopts) {\n return data.values[name] !== undefined ? data.values[name][longopts] : \"\";\n}\nfunction getCommands() {\n $.ajaxSetup({\n timeout: 7000 //Time in milliseconds\n });\n $.getJSON('/commands.json', function (data) {\n console.log(data);\n $('.orec').show();\n data.commands.forEach(function (command) {\n if ($('#flds-' + command.name).length === 0) {\n const cmdParts = command.name.split('-');\n const isConfig = cmdParts[0] === 'cfg';\n const targetDiv = '#tab-' + cmdParts[0] + '-' + cmdParts[1];\n let innerhtml = '';\n innerhtml += `
${command.help.encodeHTML().replace(/\\n/g, '
')}
`;\n if (command.argtable) {\n command.argtable.forEach(function (arg) {\n let placeholder = arg.datatype || '';\n const ctrlname = command.name + '-' + arg.longopts;\n const curvalue = getLongOps(data, command.name, arg.longopts);\n\n let attributes = 'hasvalue=' + arg.hasvalue + ' ';\n attributes += 'longopts=\"' + arg.longopts + '\" ';\n attributes += 'shortopts=\"' + arg.shortopts + '\" ';\n attributes += 'checkbox=' + arg.checkbox + ' ';\n attributes += 'cmdname=\"' + command.name + '\" ';\n attributes +=\n 'id=\"' +\n ctrlname +\n '\" name=\"' +\n ctrlname +\n '\" hasvalue=\"' +\n arg.hasvalue +\n '\" ';\n let extraclass = arg.mincount > 0 ? 'bg-success' : '';\n if (arg.glossary === 'hidden') {\n attributes += ' style=\"visibility: hidden;\"';\n }\n if (arg.checkbox) {\n innerhtml += `
`;\n } else {\n innerhtml += `
`;\n if (placeholder.includes('|')) {\n extraclass = placeholder.startsWith('+') ? ' multiple ' : '';\n placeholder = placeholder\n .replace('<', '')\n .replace('=', '')\n .replace('>', '');\n innerhtml += `';\n } else {\n innerhtml += ``;\n }\n }\n\n innerhtml += `${arg.checkbox ? '
' : ''}Previous value: ${arg.checkbox ? (curvalue ? 'Checked' : 'Unchecked') : (curvalue || '')}${arg.checkbox ? '' : '
'}`;\n });\n }\n innerhtml += `
\n
\n
\n Result\n
\n
\n
`;\n if (isConfig) {\n innerhtml +=\n `\n`;\n } else {\n innerhtml += ``;\n }\n innerhtml += '
';\n if (isConfig) {\n $(targetDiv).append(innerhtml);\n } else {\n $('#commands-list').append(innerhtml);\n }\n }\n });\n $(\".sclk\").off('click').on('click', function () { runCommand(this, false); });\n $(\".cclk\").off('click').on('click', function () { runCommand(this, true); });\n data.commands.forEach(function (command) {\n $('[cmdname=' + command.name + ']:input').val('');\n $('[cmdname=' + command.name + ']:checkbox').prop('checked', false);\n if (command.argtable) {\n command.argtable.forEach(function (arg) {\n const ctrlselector = '#' + command.name + '-' + arg.longopts;\n const ctrlValue = getLongOps(data, command.name, arg.longopts);\n if (arg.checkbox) {\n $(ctrlselector)[0].checked = ctrlValue;\n } else {\n if (ctrlValue !== undefined) {\n $(ctrlselector)\n .val(ctrlValue)\n .trigger('change');\n }\n if (\n $(ctrlselector)[0].value.length === 0 &&\n (arg.datatype || '').includes('|')\n ) {\n $(ctrlselector)[0].value = '--';\n }\n }\n });\n }\n });\n loadPresets();\n }).fail(function (xhr, ajaxOptions, thrownError) {\n if (xhr.status == 404) {\n $('.orec').hide();\n }\n else {\n handleExceptionResponse(xhr, ajaxOptions, thrownError);\n }\n $('#commands-list').empty();\n\n });\n}\n\nfunction getConfig() {\n $.ajaxSetup({\n timeout: 7000 //Time in milliseconds\n });\n $.getJSON('/config.json', function (entries) {\n $('#nvsTable tr').remove();\n const data = (entries.config ? entries.config : entries);\n SystemConfig = data;\n commandBTSinkName = '';\n Object.keys(data)\n .sort()\n .forEach(function (key) {\n let val = data[key].value;\n if (key === 'autoexec') {\n if (data.autoexec.value === '0') {\n $('#disable-squeezelite')[0].checked = true;\n } else {\n $('#disable-squeezelite')[0].checked = false;\n }\n } else if (key === 'autoexec1') {\n /* call new function to parse the squeezelite options */\n processSqueezeliteCommandLine(val);\n } else if (key === 'host_name') {\n val = val.replaceAll('\"', '');\n $('input#dhcp-name1').val(val);\n $('input#dhcp-name2').val(val);\n if ($('#cmd_opt_n').length == 0) {\n $('#cmd_opt_n').val(val);\n }\n document.title = val;\n hostName = val;\n } else if (key === 'rel_api') {\n releaseURL = val;\n }\n else if (key === 'enable_airplay') {\n $(\"#s_airplay\").css({ display: isEnabled(val) ? 'inline' : 'none' })\n }\n else if (key === 'enable_cspot') {\n $(\"#s_cspot\").css({ display: isEnabled(val) ? 'inline' : 'none' })\n }\n else if (key == 'preset_name') {\n preset_name = val;\n }\n else if (key == 'board_model') {\n board_model = val;\n }\n\n $('tbody#nvsTable').append(\n '' +\n '' +\n key +\n '' +\n \"\" +\n \"' +\n '' +\n ''\n );\n $('input#' + key).val(data[key].value);\n });\n if(commandBTSinkName.length > 0) {\n // persist the sink name found in the autoexec1 command line\n $('#cfg-audio-bt_source-sink_name').val(commandBTSinkName);\n }\n $('tbody#nvsTable').append(\n \"\"\n );\n if (entries.gpio) {\n $('#pins').show();\n $('tbody#gpiotable tr').remove();\n entries.gpio.forEach(function (gpioEntry) {\n $('tbody#gpiotable').append(\n '' +\n gpioEntry.group +\n '' +\n gpioEntry.name +\n '' +\n gpioEntry.gpio +\n '' +\n (gpioEntry.fixed ? 'Fixed' : 'Configuration') +\n ''\n );\n });\n }\n else {\n $('#pins').hide();\n }\n }).fail(function (xhr, ajaxOptions, thrownError) {\n handleExceptionResponse(xhr, ajaxOptions, thrownError);\n });\n}\n\nfunction processSqueezeliteCommandLine(val) {\n const parsed = parseSqueezeliteCommandLine(val);\n if (parsed.output.toUpperCase().startsWith('I2S')) {\n handleTemplateTypeRadio('i2s');\n } else if (parsed.output.toUpperCase().startsWith('SPDIF')) {\n handleTemplateTypeRadio('spdif');\n } else if (parsed.output.toUpperCase().startsWith('BT')) {\n if(parsed.otherOptions.btname){ \n commandBTSinkName= parsed.otherOptions.btname;\n }\n handleTemplateTypeRadio('bt');\n }\n Object.keys(parsed.options).forEach(function (key) {\n const option = parsed.options[key];\n if (!$(`#cmd_opt_${key}`).hasOwnProperty('checked')) {\n $(`#cmd_opt_${key}`).val(option);\n } else {\n $(`#cmd_opt_${key}`)[0].checked = option;\n }\n });\n if (parsed.options.hasOwnProperty('u')) {\n // parse -u v[:i] and check the appropriate radio button with id #resample_v\n const [resampleValue, resampleInterpolation] = parsed.options.u.split(':');\n $(`#resample_${resampleValue}`).prop('checked', true);\n // if resampleinterpolation is set, check resample_i checkbox\n if (resampleInterpolation) {\n $('#resample_i').prop('checked', true);\n }\n }\n\n\n}\n\nfunction showLocalMessage(message, severity) {\n const msg = {\n message: message,\n type: severity,\n };\n showMessage(msg, new Date());\n}\n\nfunction showMessage(msg, msgTime) {\n let color = 'table-success';\n\n if (msg.type === 'MESSAGING_WARNING') {\n color = 'table-warning';\n if (messageseverity === 'MESSAGING_INFO') {\n messageseverity = 'MESSAGING_WARNING';\n }\n } else if (msg.type === 'MESSAGING_ERROR') {\n if (\n messageseverity === 'MESSAGING_INFO' ||\n messageseverity === 'MESSAGING_WARNING'\n ) {\n messageseverity = 'MESSAGING_ERROR';\n }\n color = 'table-danger';\n }\n if (++messagecount > 0) {\n $('#msgcnt').removeClass('badge-success');\n $('#msgcnt').removeClass('badge-warning');\n $('#msgcnt').removeClass('badge-danger');\n $('#msgcnt').addClass(pillcolors[messageseverity]);\n $('#msgcnt').text(messagecount);\n }\n\n $('#syslogTable').append(\n \"\" +\n '' +\n msgTime.toLocalShort() +\n '' +\n '' +\n msg.message.encodeHTML() +\n '' +\n ''\n );\n}\n\nfunction inRange(x, min, max) {\n return (x - min) * (x - max) <= 0;\n}\n\nfunction sleep(ms) {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nrequire(\"bootstrap\");\nrequire(\"./sass/main.scss\");\nrequire(\"./assets/images/favicon-32x32.png\");\nrequire(\"./js/custom.js\");\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\tid: moduleId,\n\t\tloaded: false,\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Flag the module as loaded\n\tmodule.loaded = true;\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","__webpack_require__.nmd = (module) => {\n\tmodule.paths = [];\n\tif (!module.children) module.children = [];\n\treturn module;\n};","// no baseURI\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t826: 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n__webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0);\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = (parentChunkLoadingFunction, data) => {\n\tvar [chunkIds, moreModules, runtime] = data;\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some((id) => (installedChunks[id] !== 0))) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\treturn __webpack_require__.O(result);\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunksqueezelite_esp32\"] = self[\"webpackChunksqueezelite_esp32\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));","// startup\n// Load entry module and return exports\n// This entry module depends on other loaded chunks and execution need to be delayed\nvar __webpack_exports__ = __webpack_require__.O(undefined, [987], () => (__webpack_require__(607)))\n__webpack_exports__ = __webpack_require__.O(__webpack_exports__);\n"],"names":["deferred","he","require","Promise","get_control_option_value","obj","ctrl","id","val","opt","$","concat","attr","checked","replace","includes","handleNVSVisible","nvs_previous_checked","isEnabled","Cookies","recovery","show","hide","undefined","match","window","bootstrap","String","prototype","format","Object","assign","args","arguments","this","number","encodeHTML","encode","Date","toLocalShort","toLocaleString","dateStyle","timeStyle","nvsTypes","btIcons","bt_playing","bt_disconnected","bt_neutral","bt_connecting","bt_connected","bt_disabled","play_arrow","pause","stop","batIcons","icon","label","ranges","f","t","btStateIcons","desc","sub","pillcolors","MESSAGING_INFO","MESSAGING_WARNING","MESSAGING_ERROR","connectReturnCode","OK","FAIL","DISC","LOST","RESTORE","ETH","taskStates","flashState","NONE","REBOOT_TO_RECOVERY","SET_FWURL","FLASHING","DONE","UPLOADING","ERROR","UPLOADCOMPLETE","_state","olderRecovery","statusText","flashURL","flashFileName","statusPercent","Completed","prevRecovery","updateModal","Modal","document","getElementById","reset","UpdateProgress","removeClass","prop","value","isStateError","html","parent","isStateUploadComplete","isStateNone","isStateRebootRecovery","isStateSetUrl","isStateFlashing","isStateDone","isStateUploading","init","SetStateError","addClass","SetStateNone","SetStateRebootRecovery","SetStatusText","ajax","url","context","dataType","method","cache","contentType","data","JSON","stringify","timestamp","now","error","xhr","_ajaxOptions","thrownError","_xhr$status","setOTAError","status","complete","response","SetStateSetUrl","post_config","fwurl","type","SetStateFlashing","SetStateDone","SetStateUploading","SetStateUploadComplete","isFlashExecuting","toString","_this","keys","find","x","setOTATargets","fileInput","files","length","message","SetStatusPercent","ShowDialog","pct","pctChanged","txt","changed","css","text","StartOTA","logEvent","name","TargetReadyStartOTA","UploadLocalFile","xhttp","XMLHttpRequest","boundHandleUploadProgressEvent","HandleUploadProgressEvent","bind","boundsetOTAError","upload","addEventListener","onreadystatechange","readyState","open","send","console","Math","round","loaded","total","EventTargetStatus","_data$ota_pct","_data$ota_dsc","ota_pct","ota_dsc","EventOTAMessageClass","otaData","parse","fun","log","hideSurrounding","presetsloaded","messageInterval","confPayload","config","handleExceptionResponse","parseSqueezeliteCommandLine","commandLine","output","options","otherValues","i","arg","startsWith","option","slice","join","trim","o","indexOf","substring","getOutput","n","getName","otherOptions","btname","toUpperCase","temp","isConnected","ConnectedTo","hasOwnProperty","ip","getIcon","icons","handleTemplateTypeRadio","outtype","children","display","commandDefaults","forEach","key","showLocalMessage","showCmdMessage","cmdname","msgtype","msgtext","append","color","escapedtext","hFlash","handleReboot","link","delayReboot","lmsBaseUrl","releaseURL","messagesHeld","commandBTSinkName","i2s","b","C","W","Z","spdif","bt","validOptions","codecs","messagecount","messageseverity","SystemConfig","LastCommandsState","hostName","versionName","prevmessage","project_name","board_model","platform_name","preset_name","btSinkNamesOptSel","ConnectingToSSID","prevLMSIP","ConnectingToActions","getConfigJson","slimMode","each","_index","entry","nvsType","parseInt","attributes","nvs_type","duration","empty","resolve","delay","then","rdata","HideCmdMessage","getCommands","getConfig","setPlatformFilter","filter","trigger","renderError","fieldname","errorFieldName","errorField","field","after","rssiToIcon","rssi","refreshAP","_ConnectedTo","urc","ajaxSetup","timeout","getJSON","_asyncToGenerator","_regeneratorRuntime","_callee","_context","prev","next","sleep","sort","a","y","refreshAPHTML2","formatAP","ssid","auth","rssi_icon","auth_icon","_ConnectedTo2","h","e","_ConnectedTo$rssi","wifiSelector","prepend","siblings","first","showTask","task","debug","nme","cpu","st","minstk","bprio","cprio","num","getBTSinkOpt","getMessages","_ref2","_callee2","_iterator","_step","_loop","msgTime","statsData","msgparts","attrs","j","curOpt","_context3","_createForOfIteratorHelper","msg","msgAge","_context2","current_time","sent_time","setTime","getTime","t0","abrupt","ntasks","tasks","showMessage","split","is","item","replaceWith","btEntry","remove","s","done","delegateYield","t1","finish","setTimeout","_x","apply","fail","ajaxOptions","handleWifiDialog","gw","netmask","Action","STS","ap_ssid","ap_pwd","setIcons","offline","textContent","handleNetworkStatus","hasConnectionChanged","checkStatus","_data$message","_data$recovery","handleRecoveryMode","tt","bt_status","bt_sub_status","iconindex","handlebtstate","depth","version","Voltage","bat_icon","voltage","_i3","_batIcons","_step2","iconEntry","_iterator2","entryRanges","err","batteryToIcon","is_i2c_locked","lms_ip","lms_port","baseUrl","success","Number","Jack","getLongOps","longopts","values","commands","command","cmdParts","isConfig","targetDiv","innerhtml","help","argtable","placeholder","datatype","ctrlname","curvalue","hasvalue","shortopts","checkbox","extraclass","mincount","glossary","choice","off","on","runCommand","ctrlselector","ctrlValue","_","jqxhr","textStatus","entries","autoexec","parsed","_parsed$options$u$spl","u","_parsed$options$u$spl2","_slicedToArray","resampleValue","resampleInterpolation","processSqueezeliteCommandLine","replaceAll","title","gpio","gpioEntry","fixed","group","severity","ms","reason","_resolve","reject","saveAutoexec1","_get_control_option_v","optStr","resample","_i","_Object$entries","_Object$entries$_i","concatenateOptions","autoexec1","responseText","result","Result","handleDisconnect","handleConnect","pwd","dhcpname","ready","index","not","_get_control_option_v2","invalid","map","test","event","relatedTarget","CONN","MAN","FileReader","file","fr","onload","target","ex","alert","readAsText","slideUp","slideDown","preventDefault","createElement","href","URL","createObjectURL","Blob","setAttribute","body","appendChild","click","removeChild","branches","release","branch","push","fwb","assets","asset","browser_download_url","namecomponents","ver","cfg","bits","substr","lastIndexOf","created_at","idf","trclass","setURL","button","dataset","reboot","cmdstring","fields","allfields","querySelectorAll","selJson","cmd","model_config","_i2","_Object$entries2","_Object$entries2$_i","storedval","handleHWPreset","_step3","_iterator3","_attr$hasvalue","qts","isSelect","hasValue","validVal","_attr$longopts","_attr$shortopts","_attr$hasvalue2","_attr$longopts2","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","exports","module","__webpack_modules__","call","m","O","chunkIds","fn","priority","notFulfilled","Infinity","fulfilled","every","splice","r","getter","__esModule","d","definition","defineProperty","enumerable","get","g","globalThis","Function","Symbol","toStringTag","nmd","paths","installedChunks","chunkId","webpackJsonpCallback","parentChunkLoadingFunction","moreModules","runtime","some","chunkLoadingGlobal","self","__webpack_exports__"],"sourceRoot":""} \ No newline at end of file diff --git a/components/wifi-manager/webapp/dist/js/node_vendors.997af2.bundle.js b/components/wifi-manager/webapp/dist/js/node_vendors.1e1c60.bundle.js similarity index 57% rename from components/wifi-manager/webapp/dist/js/node_vendors.997af2.bundle.js rename to components/wifi-manager/webapp/dist/js/node_vendors.1e1c60.bundle.js index bb83ec85..e322a846 100644 --- a/components/wifi-manager/webapp/dist/js/node_vendors.997af2.bundle.js +++ b/components/wifi-manager/webapp/dist/js/node_vendors.1e1c60.bundle.js @@ -1,2 +1,2 @@ -(self.webpackChunksqueezelite_esp32=self.webpackChunksqueezelite_esp32||[]).push([[987],{138:(e,t,r)=>{"use strict";r.r(t),r.d(t,{Alert:()=>qt,Button:()=>Lt,Carousel:()=>cr,Collapse:()=>Ar,Dropdown:()=>Yr,Modal:()=>Ln,Offcanvas:()=>Yn,Popover:()=>vi,ScrollSpy:()=>qi,Tab:()=>Yi,Toast:()=>co,Tooltip:()=>hi});var n={};r.r(n),r.d(n,{afterMain:()=>A,afterRead:()=>w,afterWrite:()=>T,applyStyles:()=>O,arrow:()=>Z,auto:()=>l,basePlacements:()=>c,beforeMain:()=>_,beforeRead:()=>b,beforeWrite:()=>E,bottom:()=>o,clippingParents:()=>f,computeStyles:()=>ne,createPopper:()=>Oe,createPopperBase:()=>Ne,createPopperLite:()=>je,detectOverflow:()=>ye,end:()=>p,eventListeners:()=>oe,flip:()=>we,hide:()=>Ae,left:()=>a,main:()=>x,modifierPhases:()=>C,offset:()=>Ee,placements:()=>v,popper:()=>h,popperGenerator:()=>Se,popperOffsets:()=>De,preventOverflow:()=>Te,read:()=>y,reference:()=>g,right:()=>s,start:()=>u,top:()=>i,variationPlacements:()=>m,viewport:()=>d,write:()=>D});var i="top",o="bottom",s="right",a="left",l="auto",c=[i,o,s,a],u="start",p="end",f="clippingParents",d="viewport",h="popper",g="reference",m=c.reduce((function(e,t){return e.concat([t+"-"+u,t+"-"+p])}),[]),v=[].concat(c,[l]).reduce((function(e,t){return e.concat([t,t+"-"+u,t+"-"+p])}),[]),b="beforeRead",y="read",w="afterRead",_="beforeMain",x="main",A="afterMain",E="beforeWrite",D="write",T="afterWrite",C=[b,y,w,_,x,A,E,D,T];function q(e){return e?(e.nodeName||"").toLowerCase():null}function k(e){if(null==e)return window;if("[object Window]"!==e.toString()){var t=e.ownerDocument;return t&&t.defaultView||window}return e}function L(e){return e instanceof k(e).Element||e instanceof Element}function S(e){return e instanceof k(e).HTMLElement||e instanceof HTMLElement}function N(e){return"undefined"!=typeof ShadowRoot&&(e instanceof k(e).ShadowRoot||e instanceof ShadowRoot)}const O={name:"applyStyles",enabled:!0,phase:"write",fn:function(e){var t=e.state;Object.keys(t.elements).forEach((function(e){var r=t.styles[e]||{},n=t.attributes[e]||{},i=t.elements[e];S(i)&&q(i)&&(Object.assign(i.style,r),Object.keys(n).forEach((function(e){var t=n[e];!1===t?i.removeAttribute(e):i.setAttribute(e,!0===t?"":t)})))}))},effect:function(e){var t=e.state,r={popper:{position:t.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(t.elements.popper.style,r.popper),t.styles=r,t.elements.arrow&&Object.assign(t.elements.arrow.style,r.arrow),function(){Object.keys(t.elements).forEach((function(e){var n=t.elements[e],i=t.attributes[e]||{},o=Object.keys(t.styles.hasOwnProperty(e)?t.styles[e]:r[e]).reduce((function(e,t){return e[t]="",e}),{});S(n)&&q(n)&&(Object.assign(n.style,o),Object.keys(i).forEach((function(e){n.removeAttribute(e)})))}))}},requires:["computeStyles"]};function j(e){return e.split("-")[0]}var R=Math.max,B=Math.min,I=Math.round;function P(){var e=navigator.userAgentData;return null!=e&&e.brands&&Array.isArray(e.brands)?e.brands.map((function(e){return e.brand+"/"+e.version})).join(" "):navigator.userAgent}function F(){return!/^((?!chrome|android).)*safari/i.test(P())}function H(e,t,r){void 0===t&&(t=!1),void 0===r&&(r=!1);var n=e.getBoundingClientRect(),i=1,o=1;t&&S(e)&&(i=e.offsetWidth>0&&I(n.width)/e.offsetWidth||1,o=e.offsetHeight>0&&I(n.height)/e.offsetHeight||1);var s=(L(e)?k(e):window).visualViewport,a=!F()&&r,l=(n.left+(a&&s?s.offsetLeft:0))/i,c=(n.top+(a&&s?s.offsetTop:0))/o,u=n.width/i,p=n.height/o;return{width:u,height:p,top:c,right:l+u,bottom:c+p,left:l,x:l,y:c}}function M(e){var t=H(e),r=e.offsetWidth,n=e.offsetHeight;return Math.abs(t.width-r)<=1&&(r=t.width),Math.abs(t.height-n)<=1&&(n=t.height),{x:e.offsetLeft,y:e.offsetTop,width:r,height:n}}function U(e,t){var r=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(r&&N(r)){var n=t;do{if(n&&e.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function V(e){return k(e).getComputedStyle(e)}function $(e){return["table","td","th"].indexOf(q(e))>=0}function z(e){return((L(e)?e.ownerDocument:e.document)||window.document).documentElement}function G(e){return"html"===q(e)?e:e.assignedSlot||e.parentNode||(N(e)?e.host:null)||z(e)}function W(e){return S(e)&&"fixed"!==V(e).position?e.offsetParent:null}function Y(e){for(var t=k(e),r=W(e);r&&$(r)&&"static"===V(r).position;)r=W(r);return r&&("html"===q(r)||"body"===q(r)&&"static"===V(r).position)?t:r||function(e){var t=/firefox/i.test(P());if(/Trident/i.test(P())&&S(e)&&"fixed"===V(e).position)return null;var r=G(e);for(N(r)&&(r=r.host);S(r)&&["html","body"].indexOf(q(r))<0;){var n=V(r);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||t&&"filter"===n.willChange||t&&n.filter&&"none"!==n.filter)return r;r=r.parentNode}return null}(e)||t}function X(e){return["top","bottom"].indexOf(e)>=0?"x":"y"}function K(e,t,r){return R(e,B(t,r))}function J(e){return Object.assign({},{top:0,right:0,bottom:0,left:0},e)}function Q(e,t){return t.reduce((function(t,r){return t[r]=e,t}),{})}const Z={name:"arrow",enabled:!0,phase:"main",fn:function(e){var t,r=e.state,n=e.name,l=e.options,u=r.elements.arrow,p=r.modifiersData.popperOffsets,f=j(r.placement),d=X(f),h=[a,s].indexOf(f)>=0?"height":"width";if(u&&p){var g=function(e,t){return J("number"!=typeof(e="function"==typeof e?e(Object.assign({},t.rects,{placement:t.placement})):e)?e:Q(e,c))}(l.padding,r),m=M(u),v="y"===d?i:a,b="y"===d?o:s,y=r.rects.reference[h]+r.rects.reference[d]-p[d]-r.rects.popper[h],w=p[d]-r.rects.reference[d],_=Y(u),x=_?"y"===d?_.clientHeight||0:_.clientWidth||0:0,A=y/2-w/2,E=g[v],D=x-m[h]-g[b],T=x/2-m[h]/2+A,C=K(E,T,D),q=d;r.modifiersData[n]=((t={})[q]=C,t.centerOffset=C-T,t)}},effect:function(e){var t=e.state,r=e.options.element,n=void 0===r?"[data-popper-arrow]":r;null!=n&&("string"!=typeof n||(n=t.elements.popper.querySelector(n)))&&U(t.elements.popper,n)&&(t.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function ee(e){return e.split("-")[1]}var te={top:"auto",right:"auto",bottom:"auto",left:"auto"};function re(e){var t,r=e.popper,n=e.popperRect,l=e.placement,c=e.variation,u=e.offsets,f=e.position,d=e.gpuAcceleration,h=e.adaptive,g=e.roundOffsets,m=e.isFixed,v=u.x,b=void 0===v?0:v,y=u.y,w=void 0===y?0:y,_="function"==typeof g?g({x:b,y:w}):{x:b,y:w};b=_.x,w=_.y;var x=u.hasOwnProperty("x"),A=u.hasOwnProperty("y"),E=a,D=i,T=window;if(h){var C=Y(r),q="clientHeight",L="clientWidth";if(C===k(r)&&"static"!==V(C=z(r)).position&&"absolute"===f&&(q="scrollHeight",L="scrollWidth"),l===i||(l===a||l===s)&&c===p)D=o,w-=(m&&C===T&&T.visualViewport?T.visualViewport.height:C[q])-n.height,w*=d?1:-1;if(l===a||(l===i||l===o)&&c===p)E=s,b-=(m&&C===T&&T.visualViewport?T.visualViewport.width:C[L])-n.width,b*=d?1:-1}var S,N=Object.assign({position:f},h&&te),O=!0===g?function(e,t){var r=e.x,n=e.y,i=t.devicePixelRatio||1;return{x:I(r*i)/i||0,y:I(n*i)/i||0}}({x:b,y:w},k(r)):{x:b,y:w};return b=O.x,w=O.y,d?Object.assign({},N,((S={})[D]=A?"0":"",S[E]=x?"0":"",S.transform=(T.devicePixelRatio||1)<=1?"translate("+b+"px, "+w+"px)":"translate3d("+b+"px, "+w+"px, 0)",S)):Object.assign({},N,((t={})[D]=A?w+"px":"",t[E]=x?b+"px":"",t.transform="",t))}const ne={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(e){var t=e.state,r=e.options,n=r.gpuAcceleration,i=void 0===n||n,o=r.adaptive,s=void 0===o||o,a=r.roundOffsets,l=void 0===a||a,c={placement:j(t.placement),variation:ee(t.placement),popper:t.elements.popper,popperRect:t.rects.popper,gpuAcceleration:i,isFixed:"fixed"===t.options.strategy};null!=t.modifiersData.popperOffsets&&(t.styles.popper=Object.assign({},t.styles.popper,re(Object.assign({},c,{offsets:t.modifiersData.popperOffsets,position:t.options.strategy,adaptive:s,roundOffsets:l})))),null!=t.modifiersData.arrow&&(t.styles.arrow=Object.assign({},t.styles.arrow,re(Object.assign({},c,{offsets:t.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-placement":t.placement})},data:{}};var ie={passive:!0};const oe={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(e){var t=e.state,r=e.instance,n=e.options,i=n.scroll,o=void 0===i||i,s=n.resize,a=void 0===s||s,l=k(t.elements.popper),c=[].concat(t.scrollParents.reference,t.scrollParents.popper);return o&&c.forEach((function(e){e.addEventListener("scroll",r.update,ie)})),a&&l.addEventListener("resize",r.update,ie),function(){o&&c.forEach((function(e){e.removeEventListener("scroll",r.update,ie)})),a&&l.removeEventListener("resize",r.update,ie)}},data:{}};var se={left:"right",right:"left",bottom:"top",top:"bottom"};function ae(e){return e.replace(/left|right|bottom|top/g,(function(e){return se[e]}))}var le={start:"end",end:"start"};function ce(e){return e.replace(/start|end/g,(function(e){return le[e]}))}function ue(e){var t=k(e);return{scrollLeft:t.pageXOffset,scrollTop:t.pageYOffset}}function pe(e){return H(z(e)).left+ue(e).scrollLeft}function fe(e){var t=V(e),r=t.overflow,n=t.overflowX,i=t.overflowY;return/auto|scroll|overlay|hidden/.test(r+i+n)}function de(e){return["html","body","#document"].indexOf(q(e))>=0?e.ownerDocument.body:S(e)&&fe(e)?e:de(G(e))}function he(e,t){var r;void 0===t&&(t=[]);var n=de(e),i=n===(null==(r=e.ownerDocument)?void 0:r.body),o=k(n),s=i?[o].concat(o.visualViewport||[],fe(n)?n:[]):n,a=t.concat(s);return i?a:a.concat(he(G(s)))}function ge(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function me(e,t,r){return t===d?ge(function(e,t){var r=k(e),n=z(e),i=r.visualViewport,o=n.clientWidth,s=n.clientHeight,a=0,l=0;if(i){o=i.width,s=i.height;var c=F();(c||!c&&"fixed"===t)&&(a=i.offsetLeft,l=i.offsetTop)}return{width:o,height:s,x:a+pe(e),y:l}}(e,r)):L(t)?function(e,t){var r=H(e,!1,"fixed"===t);return r.top=r.top+e.clientTop,r.left=r.left+e.clientLeft,r.bottom=r.top+e.clientHeight,r.right=r.left+e.clientWidth,r.width=e.clientWidth,r.height=e.clientHeight,r.x=r.left,r.y=r.top,r}(t,r):ge(function(e){var t,r=z(e),n=ue(e),i=null==(t=e.ownerDocument)?void 0:t.body,o=R(r.scrollWidth,r.clientWidth,i?i.scrollWidth:0,i?i.clientWidth:0),s=R(r.scrollHeight,r.clientHeight,i?i.scrollHeight:0,i?i.clientHeight:0),a=-n.scrollLeft+pe(e),l=-n.scrollTop;return"rtl"===V(i||r).direction&&(a+=R(r.clientWidth,i?i.clientWidth:0)-o),{width:o,height:s,x:a,y:l}}(z(e)))}function ve(e,t,r,n){var i="clippingParents"===t?function(e){var t=he(G(e)),r=["absolute","fixed"].indexOf(V(e).position)>=0&&S(e)?Y(e):e;return L(r)?t.filter((function(e){return L(e)&&U(e,r)&&"body"!==q(e)})):[]}(e):[].concat(t),o=[].concat(i,[r]),s=o[0],a=o.reduce((function(t,r){var i=me(e,r,n);return t.top=R(i.top,t.top),t.right=B(i.right,t.right),t.bottom=B(i.bottom,t.bottom),t.left=R(i.left,t.left),t}),me(e,s,n));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}function be(e){var t,r=e.reference,n=e.element,l=e.placement,c=l?j(l):null,f=l?ee(l):null,d=r.x+r.width/2-n.width/2,h=r.y+r.height/2-n.height/2;switch(c){case i:t={x:d,y:r.y-n.height};break;case o:t={x:d,y:r.y+r.height};break;case s:t={x:r.x+r.width,y:h};break;case a:t={x:r.x-n.width,y:h};break;default:t={x:r.x,y:r.y}}var g=c?X(c):null;if(null!=g){var m="y"===g?"height":"width";switch(f){case u:t[g]=t[g]-(r[m]/2-n[m]/2);break;case p:t[g]=t[g]+(r[m]/2-n[m]/2)}}return t}function ye(e,t){void 0===t&&(t={});var r=t,n=r.placement,a=void 0===n?e.placement:n,l=r.strategy,u=void 0===l?e.strategy:l,p=r.boundary,m=void 0===p?f:p,v=r.rootBoundary,b=void 0===v?d:v,y=r.elementContext,w=void 0===y?h:y,_=r.altBoundary,x=void 0!==_&&_,A=r.padding,E=void 0===A?0:A,D=J("number"!=typeof E?E:Q(E,c)),T=w===h?g:h,C=e.rects.popper,q=e.elements[x?T:w],k=ve(L(q)?q:q.contextElement||z(e.elements.popper),m,b,u),S=H(e.elements.reference),N=be({reference:S,element:C,strategy:"absolute",placement:a}),O=ge(Object.assign({},C,N)),j=w===h?O:S,R={top:k.top-j.top+D.top,bottom:j.bottom-k.bottom+D.bottom,left:k.left-j.left+D.left,right:j.right-k.right+D.right},B=e.modifiersData.offset;if(w===h&&B){var I=B[a];Object.keys(R).forEach((function(e){var t=[s,o].indexOf(e)>=0?1:-1,r=[i,o].indexOf(e)>=0?"y":"x";R[e]+=I[r]*t}))}return R}const we={name:"flip",enabled:!0,phase:"main",fn:function(e){var t=e.state,r=e.options,n=e.name;if(!t.modifiersData[n]._skip){for(var p=r.mainAxis,f=void 0===p||p,d=r.altAxis,h=void 0===d||d,g=r.fallbackPlacements,b=r.padding,y=r.boundary,w=r.rootBoundary,_=r.altBoundary,x=r.flipVariations,A=void 0===x||x,E=r.allowedAutoPlacements,D=t.options.placement,T=j(D),C=g||(T===D||!A?[ae(D)]:function(e){if(j(e)===l)return[];var t=ae(e);return[ce(e),t,ce(t)]}(D)),q=[D].concat(C).reduce((function(e,r){return e.concat(j(r)===l?function(e,t){void 0===t&&(t={});var r=t,n=r.placement,i=r.boundary,o=r.rootBoundary,s=r.padding,a=r.flipVariations,l=r.allowedAutoPlacements,u=void 0===l?v:l,p=ee(n),f=p?a?m:m.filter((function(e){return ee(e)===p})):c,d=f.filter((function(e){return u.indexOf(e)>=0}));0===d.length&&(d=f);var h=d.reduce((function(t,r){return t[r]=ye(e,{placement:r,boundary:i,rootBoundary:o,padding:s})[j(r)],t}),{});return Object.keys(h).sort((function(e,t){return h[e]-h[t]}))}(t,{placement:r,boundary:y,rootBoundary:w,padding:b,flipVariations:A,allowedAutoPlacements:E}):r)}),[]),k=t.rects.reference,L=t.rects.popper,S=new Map,N=!0,O=q[0],R=0;R=0,H=F?"width":"height",M=ye(t,{placement:B,boundary:y,rootBoundary:w,altBoundary:_,padding:b}),U=F?P?s:a:P?o:i;k[H]>L[H]&&(U=ae(U));var V=ae(U),$=[];if(f&&$.push(M[I]<=0),h&&$.push(M[U]<=0,M[V]<=0),$.every((function(e){return e}))){O=B,N=!1;break}S.set(B,$)}if(N)for(var z=function(e){var t=q.find((function(t){var r=S.get(t);if(r)return r.slice(0,e).every((function(e){return e}))}));if(t)return O=t,"break"},G=A?3:1;G>0;G--){if("break"===z(G))break}t.placement!==O&&(t.modifiersData[n]._skip=!0,t.placement=O,t.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function _e(e,t,r){return void 0===r&&(r={x:0,y:0}),{top:e.top-t.height-r.y,right:e.right-t.width+r.x,bottom:e.bottom-t.height+r.y,left:e.left-t.width-r.x}}function xe(e){return[i,s,o,a].some((function(t){return e[t]>=0}))}const Ae={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(e){var t=e.state,r=e.name,n=t.rects.reference,i=t.rects.popper,o=t.modifiersData.preventOverflow,s=ye(t,{elementContext:"reference"}),a=ye(t,{altBoundary:!0}),l=_e(s,n),c=_e(a,i,o),u=xe(l),p=xe(c);t.modifiersData[r]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:u,hasPopperEscaped:p},t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-reference-hidden":u,"data-popper-escaped":p})}};const Ee={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(e){var t=e.state,r=e.options,n=e.name,o=r.offset,l=void 0===o?[0,0]:o,c=v.reduce((function(e,r){return e[r]=function(e,t,r){var n=j(e),o=[a,i].indexOf(n)>=0?-1:1,l="function"==typeof r?r(Object.assign({},t,{placement:e})):r,c=l[0],u=l[1];return c=c||0,u=(u||0)*o,[a,s].indexOf(n)>=0?{x:u,y:c}:{x:c,y:u}}(r,t.rects,l),e}),{}),u=c[t.placement],p=u.x,f=u.y;null!=t.modifiersData.popperOffsets&&(t.modifiersData.popperOffsets.x+=p,t.modifiersData.popperOffsets.y+=f),t.modifiersData[n]=c}};const De={name:"popperOffsets",enabled:!0,phase:"read",fn:function(e){var t=e.state,r=e.name;t.modifiersData[r]=be({reference:t.rects.reference,element:t.rects.popper,strategy:"absolute",placement:t.placement})},data:{}};const Te={name:"preventOverflow",enabled:!0,phase:"main",fn:function(e){var t=e.state,r=e.options,n=e.name,l=r.mainAxis,c=void 0===l||l,p=r.altAxis,f=void 0!==p&&p,d=r.boundary,h=r.rootBoundary,g=r.altBoundary,m=r.padding,v=r.tether,b=void 0===v||v,y=r.tetherOffset,w=void 0===y?0:y,_=ye(t,{boundary:d,rootBoundary:h,padding:m,altBoundary:g}),x=j(t.placement),A=ee(t.placement),E=!A,D=X(x),T="x"===D?"y":"x",C=t.modifiersData.popperOffsets,q=t.rects.reference,k=t.rects.popper,L="function"==typeof w?w(Object.assign({},t.rects,{placement:t.placement})):w,S="number"==typeof L?{mainAxis:L,altAxis:L}:Object.assign({mainAxis:0,altAxis:0},L),N=t.modifiersData.offset?t.modifiersData.offset[t.placement]:null,O={x:0,y:0};if(C){if(c){var I,P="y"===D?i:a,F="y"===D?o:s,H="y"===D?"height":"width",U=C[D],V=U+_[P],$=U-_[F],z=b?-k[H]/2:0,G=A===u?q[H]:k[H],W=A===u?-k[H]:-q[H],J=t.elements.arrow,Q=b&&J?M(J):{width:0,height:0},Z=t.modifiersData["arrow#persistent"]?t.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},te=Z[P],re=Z[F],ne=K(0,q[H],Q[H]),ie=E?q[H]/2-z-ne-te-S.mainAxis:G-ne-te-S.mainAxis,oe=E?-q[H]/2+z+ne+re+S.mainAxis:W+ne+re+S.mainAxis,se=t.elements.arrow&&Y(t.elements.arrow),ae=se?"y"===D?se.clientTop||0:se.clientLeft||0:0,le=null!=(I=null==N?void 0:N[D])?I:0,ce=U+oe-le,ue=K(b?B(V,U+ie-le-ae):V,U,b?R($,ce):$);C[D]=ue,O[D]=ue-U}if(f){var pe,fe="x"===D?i:a,de="x"===D?o:s,he=C[T],ge="y"===T?"height":"width",me=he+_[fe],ve=he-_[de],be=-1!==[i,a].indexOf(x),we=null!=(pe=null==N?void 0:N[T])?pe:0,_e=be?me:he-q[ge]-k[ge]-we+S.altAxis,xe=be?he+q[ge]+k[ge]-we-S.altAxis:ve,Ae=b&&be?function(e,t,r){var n=K(e,t,r);return n>r?r:n}(_e,he,xe):K(b?_e:me,he,b?xe:ve);C[T]=Ae,O[T]=Ae-he}t.modifiersData[n]=O}},requiresIfExists:["offset"]};function Ce(e,t,r){void 0===r&&(r=!1);var n,i,o=S(t),s=S(t)&&function(e){var t=e.getBoundingClientRect(),r=I(t.width)/e.offsetWidth||1,n=I(t.height)/e.offsetHeight||1;return 1!==r||1!==n}(t),a=z(t),l=H(e,s,r),c={scrollLeft:0,scrollTop:0},u={x:0,y:0};return(o||!o&&!r)&&(("body"!==q(t)||fe(a))&&(c=(n=t)!==k(n)&&S(n)?{scrollLeft:(i=n).scrollLeft,scrollTop:i.scrollTop}:ue(n)),S(t)?((u=H(t,!0)).x+=t.clientLeft,u.y+=t.clientTop):a&&(u.x=pe(a))),{x:l.left+c.scrollLeft-u.x,y:l.top+c.scrollTop-u.y,width:l.width,height:l.height}}function qe(e){var t=new Map,r=new Set,n=[];function i(e){r.add(e.name),[].concat(e.requires||[],e.requiresIfExists||[]).forEach((function(e){if(!r.has(e)){var n=t.get(e);n&&i(n)}})),n.push(e)}return e.forEach((function(e){t.set(e.name,e)})),e.forEach((function(e){r.has(e.name)||i(e)})),n}var ke={placement:"bottom",modifiers:[],strategy:"absolute"};function Le(){for(var e=arguments.length,t=new Array(e),r=0;r{let t=e.getAttribute("data-bs-target");if(!t||"#"===t){let r=e.getAttribute("href");if(!r||!r.includes("#")&&!r.startsWith("."))return null;r.includes("#")&&!r.startsWith("#")&&(r=`#${r.split("#")[1]}`),t=r&&"#"!==r?r.trim():null}return t},Ie=e=>{const t=Be(e);return t&&document.querySelector(t)?t:null},Pe=e=>{const t=Be(e);return t?document.querySelector(t):null},Fe=e=>{e.dispatchEvent(new Event(Re))},He=e=>!(!e||"object"!=typeof e)&&(void 0!==e.jquery&&(e=e[0]),void 0!==e.nodeType),Me=e=>He(e)?e.jquery?e[0]:e:"string"==typeof e&&e.length>0?document.querySelector(e):null,Ue=e=>{if(!He(e)||0===e.getClientRects().length)return!1;const t="visible"===getComputedStyle(e).getPropertyValue("visibility"),r=e.closest("details:not([open])");if(!r)return t;if(r!==e){const t=e.closest("summary");if(t&&t.parentNode!==r)return!1;if(null===t)return!1}return t},Ve=e=>!e||e.nodeType!==Node.ELEMENT_NODE||(!!e.classList.contains("disabled")||(void 0!==e.disabled?e.disabled:e.hasAttribute("disabled")&&"false"!==e.getAttribute("disabled"))),$e=e=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof e.getRootNode){const t=e.getRootNode();return t instanceof ShadowRoot?t:null}return e instanceof ShadowRoot?e:e.parentNode?$e(e.parentNode):null},ze=()=>{},Ge=e=>{e.offsetHeight},We=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,Ye=[],Xe=()=>"rtl"===document.documentElement.dir,Ke=e=>{var t;t=()=>{const t=We();if(t){const r=e.NAME,n=t.fn[r];t.fn[r]=e.jQueryInterface,t.fn[r].Constructor=e,t.fn[r].noConflict=()=>(t.fn[r]=n,e.jQueryInterface)}},"loading"===document.readyState?(Ye.length||document.addEventListener("DOMContentLoaded",(()=>{for(const e of Ye)e()})),Ye.push(t)):t()},Je=e=>{"function"==typeof e&&e()},Qe=(e,t,r=!0)=>{if(!r)return void Je(e);const n=(e=>{if(!e)return 0;let{transitionDuration:t,transitionDelay:r}=window.getComputedStyle(e);const n=Number.parseFloat(t),i=Number.parseFloat(r);return n||i?(t=t.split(",")[0],r=r.split(",")[0],1e3*(Number.parseFloat(t)+Number.parseFloat(r))):0})(t)+5;let i=!1;const o=({target:r})=>{r===t&&(i=!0,t.removeEventListener(Re,o),Je(e))};t.addEventListener(Re,o),setTimeout((()=>{i||Fe(t)}),n)},Ze=(e,t,r,n)=>{const i=e.length;let o=e.indexOf(t);return-1===o?!r&&n?e[i-1]:e[0]:(o+=r?1:-1,n&&(o=(o+i)%i),e[Math.max(0,Math.min(o,i-1))])},et=/[^.]*(?=\..*)\.|.*/,tt=/\..*/,rt=/::\d+$/,nt={};let it=1;const ot={mouseenter:"mouseover",mouseleave:"mouseout"},st=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function at(e,t){return t&&`${t}::${it++}`||e.uidEvent||it++}function lt(e){const t=at(e);return e.uidEvent=t,nt[t]=nt[t]||{},nt[t]}function ct(e,t,r=null){return Object.values(e).find((e=>e.callable===t&&e.delegationSelector===r))}function ut(e,t,r){const n="string"==typeof t,i=n?r:t||r;let o=ht(e);return st.has(o)||(o=e),[n,i,o]}function pt(e,t,r,n,i){if("string"!=typeof t||!e)return;let[o,s,a]=ut(t,r,n);if(t in ot){const e=e=>function(t){if(!t.relatedTarget||t.relatedTarget!==t.delegateTarget&&!t.delegateTarget.contains(t.relatedTarget))return e.call(this,t)};s=e(s)}const l=lt(e),c=l[a]||(l[a]={}),u=ct(c,s,o?r:null);if(u)return void(u.oneOff=u.oneOff&&i);const p=at(s,t.replace(et,"")),f=o?function(e,t,r){return function n(i){const o=e.querySelectorAll(t);for(let{target:s}=i;s&&s!==this;s=s.parentNode)for(const a of o)if(a===s)return mt(i,{delegateTarget:s}),n.oneOff&>.off(e,i.type,t,r),r.apply(s,[i])}}(e,r,s):function(e,t){return function r(n){return mt(n,{delegateTarget:e}),r.oneOff&>.off(e,n.type,t),t.apply(e,[n])}}(e,s);f.delegationSelector=o?r:null,f.callable=s,f.oneOff=i,f.uidEvent=p,c[p]=f,e.addEventListener(a,f,o)}function ft(e,t,r,n,i){const o=ct(t[r],n,i);o&&(e.removeEventListener(r,o,Boolean(i)),delete t[r][o.uidEvent])}function dt(e,t,r,n){const i=t[r]||{};for(const o of Object.keys(i))if(o.includes(n)){const n=i[o];ft(e,t,r,n.callable,n.delegationSelector)}}function ht(e){return e=e.replace(tt,""),ot[e]||e}const gt={on(e,t,r,n){pt(e,t,r,n,!1)},one(e,t,r,n){pt(e,t,r,n,!0)},off(e,t,r,n){if("string"!=typeof t||!e)return;const[i,o,s]=ut(t,r,n),a=s!==t,l=lt(e),c=l[s]||{},u=t.startsWith(".");if(void 0===o){if(u)for(const r of Object.keys(l))dt(e,l,r,t.slice(1));for(const r of Object.keys(c)){const n=r.replace(rt,"");if(!a||t.includes(n)){const t=c[r];ft(e,l,s,t.callable,t.delegationSelector)}}}else{if(!Object.keys(c).length)return;ft(e,l,s,o,i?r:null)}},trigger(e,t,r){if("string"!=typeof t||!e)return null;const n=We();let i=null,o=!0,s=!0,a=!1;t!==ht(t)&&n&&(i=n.Event(t,r),n(e).trigger(i),o=!i.isPropagationStopped(),s=!i.isImmediatePropagationStopped(),a=i.isDefaultPrevented());let l=new Event(t,{bubbles:o,cancelable:!0});return l=mt(l,r),a&&l.preventDefault(),s&&e.dispatchEvent(l),l.defaultPrevented&&i&&i.preventDefault(),l}};function mt(e,t){for(const[r,n]of Object.entries(t||{}))try{e[r]=n}catch(t){Object.defineProperty(e,r,{configurable:!0,get:()=>n})}return e}const vt=new Map,bt={set(e,t,r){vt.has(e)||vt.set(e,new Map);const n=vt.get(e);n.has(t)||0===n.size?n.set(t,r):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(e,t)=>vt.has(e)&&vt.get(e).get(t)||null,remove(e,t){if(!vt.has(e))return;const r=vt.get(e);r.delete(t),0===r.size&&vt.delete(e)}};function yt(e){if("true"===e)return!0;if("false"===e)return!1;if(e===Number(e).toString())return Number(e);if(""===e||"null"===e)return null;if("string"!=typeof e)return e;try{return JSON.parse(decodeURIComponent(e))}catch(t){return e}}function wt(e){return e.replace(/[A-Z]/g,(e=>`-${e.toLowerCase()}`))}const _t={setDataAttribute(e,t,r){e.setAttribute(`data-bs-${wt(t)}`,r)},removeDataAttribute(e,t){e.removeAttribute(`data-bs-${wt(t)}`)},getDataAttributes(e){if(!e)return{};const t={},r=Object.keys(e.dataset).filter((e=>e.startsWith("bs")&&!e.startsWith("bsConfig")));for(const n of r){let r=n.replace(/^bs/,"");r=r.charAt(0).toLowerCase()+r.slice(1,r.length),t[r]=yt(e.dataset[n])}return t},getDataAttribute:(e,t)=>yt(e.getAttribute(`data-bs-${wt(t)}`))};class xt{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(e){return e=this._mergeConfigObj(e),e=this._configAfterMerge(e),this._typeCheckConfig(e),e}_configAfterMerge(e){return e}_mergeConfigObj(e,t){const r=He(t)?_t.getDataAttribute(t,"config"):{};return{...this.constructor.Default,..."object"==typeof r?r:{},...He(t)?_t.getDataAttributes(t):{},..."object"==typeof e?e:{}}}_typeCheckConfig(e,t=this.constructor.DefaultType){for(const n of Object.keys(t)){const i=t[n],o=e[n],s=He(o)?"element":null==(r=o)?`${r}`:Object.prototype.toString.call(r).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(i).test(s))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${n}" provided type "${s}" but expected type "${i}".`)}var r}}class At extends xt{constructor(e,t){super(),(e=Me(e))&&(this._element=e,this._config=this._getConfig(t),bt.set(this._element,this.constructor.DATA_KEY,this))}dispose(){bt.remove(this._element,this.constructor.DATA_KEY),gt.off(this._element,this.constructor.EVENT_KEY);for(const e of Object.getOwnPropertyNames(this))this[e]=null}_queueCallback(e,t,r=!0){Qe(e,t,r)}_getConfig(e){return e=this._mergeConfigObj(e,this._element),e=this._configAfterMerge(e),this._typeCheckConfig(e),e}static getInstance(e){return bt.get(Me(e),this.DATA_KEY)}static getOrCreateInstance(e,t={}){return this.getInstance(e)||new this(e,"object"==typeof t?t:null)}static get VERSION(){return"5.2.3"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(e){return`${e}${this.EVENT_KEY}`}}const Et=(e,t="hide")=>{const r=`click.dismiss${e.EVENT_KEY}`,n=e.NAME;gt.on(document,r,`[data-bs-dismiss="${n}"]`,(function(r){if(["A","AREA"].includes(this.tagName)&&r.preventDefault(),Ve(this))return;const i=Pe(this)||this.closest(`.${n}`);e.getOrCreateInstance(i)[t]()}))},Dt=".bs.alert",Tt=`close${Dt}`,Ct=`closed${Dt}`;class qt extends At{static get NAME(){return"alert"}close(){if(gt.trigger(this._element,Tt).defaultPrevented)return;this._element.classList.remove("show");const e=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,e)}_destroyElement(){this._element.remove(),gt.trigger(this._element,Ct),this.dispose()}static jQueryInterface(e){return this.each((function(){const t=qt.getOrCreateInstance(this);if("string"==typeof e){if(void 0===t[e]||e.startsWith("_")||"constructor"===e)throw new TypeError(`No method named "${e}"`);t[e](this)}}))}}Et(qt,"close"),Ke(qt);const kt='[data-bs-toggle="button"]';class Lt extends At{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(e){return this.each((function(){const t=Lt.getOrCreateInstance(this);"toggle"===e&&t[e]()}))}}gt.on(document,"click.bs.button.data-api",kt,(e=>{e.preventDefault();const t=e.target.closest(kt);Lt.getOrCreateInstance(t).toggle()})),Ke(Lt);const St={find:(e,t=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(t,e)),findOne:(e,t=document.documentElement)=>Element.prototype.querySelector.call(t,e),children:(e,t)=>[].concat(...e.children).filter((e=>e.matches(t))),parents(e,t){const r=[];let n=e.parentNode.closest(t);for(;n;)r.push(n),n=n.parentNode.closest(t);return r},prev(e,t){let r=e.previousElementSibling;for(;r;){if(r.matches(t))return[r];r=r.previousElementSibling}return[]},next(e,t){let r=e.nextElementSibling;for(;r;){if(r.matches(t))return[r];r=r.nextElementSibling}return[]},focusableChildren(e){const t=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((e=>`${e}:not([tabindex^="-"])`)).join(",");return this.find(t,e).filter((e=>!Ve(e)&&Ue(e)))}},Nt=".bs.swipe",Ot=`touchstart${Nt}`,jt=`touchmove${Nt}`,Rt=`touchend${Nt}`,Bt=`pointerdown${Nt}`,It=`pointerup${Nt}`,Pt={endCallback:null,leftCallback:null,rightCallback:null},Ft={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class Ht extends xt{constructor(e,t){super(),this._element=e,e&&Ht.isSupported()&&(this._config=this._getConfig(t),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return Pt}static get DefaultType(){return Ft}static get NAME(){return"swipe"}dispose(){gt.off(this._element,Nt)}_start(e){this._supportPointerEvents?this._eventIsPointerPenTouch(e)&&(this._deltaX=e.clientX):this._deltaX=e.touches[0].clientX}_end(e){this._eventIsPointerPenTouch(e)&&(this._deltaX=e.clientX-this._deltaX),this._handleSwipe(),Je(this._config.endCallback)}_move(e){this._deltaX=e.touches&&e.touches.length>1?0:e.touches[0].clientX-this._deltaX}_handleSwipe(){const e=Math.abs(this._deltaX);if(e<=40)return;const t=e/this._deltaX;this._deltaX=0,t&&Je(t>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(gt.on(this._element,Bt,(e=>this._start(e))),gt.on(this._element,It,(e=>this._end(e))),this._element.classList.add("pointer-event")):(gt.on(this._element,Ot,(e=>this._start(e))),gt.on(this._element,jt,(e=>this._move(e))),gt.on(this._element,Rt,(e=>this._end(e))))}_eventIsPointerPenTouch(e){return this._supportPointerEvents&&("pen"===e.pointerType||"touch"===e.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const Mt=".bs.carousel",Ut=".data-api",Vt="next",$t="prev",zt="left",Gt="right",Wt=`slide${Mt}`,Yt=`slid${Mt}`,Xt=`keydown${Mt}`,Kt=`mouseenter${Mt}`,Jt=`mouseleave${Mt}`,Qt=`dragstart${Mt}`,Zt=`load${Mt}${Ut}`,er=`click${Mt}${Ut}`,tr="carousel",rr="active",nr=".active",ir=".carousel-item",or=nr+ir,sr={ArrowLeft:Gt,ArrowRight:zt},ar={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},lr={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class cr extends At{constructor(e,t){super(e,t),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=St.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===tr&&this.cycle()}static get Default(){return ar}static get DefaultType(){return lr}static get NAME(){return"carousel"}next(){this._slide(Vt)}nextWhenVisible(){!document.hidden&&Ue(this._element)&&this.next()}prev(){this._slide($t)}pause(){this._isSliding&&Fe(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval((()=>this.nextWhenVisible()),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?gt.one(this._element,Yt,(()=>this.cycle())):this.cycle())}to(e){const t=this._getItems();if(e>t.length-1||e<0)return;if(this._isSliding)return void gt.one(this._element,Yt,(()=>this.to(e)));const r=this._getItemIndex(this._getActive());if(r===e)return;const n=e>r?Vt:$t;this._slide(n,t[e])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(e){return e.defaultInterval=e.interval,e}_addEventListeners(){this._config.keyboard&>.on(this._element,Xt,(e=>this._keydown(e))),"hover"===this._config.pause&&(gt.on(this._element,Kt,(()=>this.pause())),gt.on(this._element,Jt,(()=>this._maybeEnableCycle()))),this._config.touch&&Ht.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const e of St.find(".carousel-item img",this._element))gt.on(e,Qt,(e=>e.preventDefault()));const e={leftCallback:()=>this._slide(this._directionToOrder(zt)),rightCallback:()=>this._slide(this._directionToOrder(Gt)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((()=>this._maybeEnableCycle()),500+this._config.interval))}};this._swipeHelper=new Ht(this._element,e)}_keydown(e){if(/input|textarea/i.test(e.target.tagName))return;const t=sr[e.key];t&&(e.preventDefault(),this._slide(this._directionToOrder(t)))}_getItemIndex(e){return this._getItems().indexOf(e)}_setActiveIndicatorElement(e){if(!this._indicatorsElement)return;const t=St.findOne(nr,this._indicatorsElement);t.classList.remove(rr),t.removeAttribute("aria-current");const r=St.findOne(`[data-bs-slide-to="${e}"]`,this._indicatorsElement);r&&(r.classList.add(rr),r.setAttribute("aria-current","true"))}_updateInterval(){const e=this._activeElement||this._getActive();if(!e)return;const t=Number.parseInt(e.getAttribute("data-bs-interval"),10);this._config.interval=t||this._config.defaultInterval}_slide(e,t=null){if(this._isSliding)return;const r=this._getActive(),n=e===Vt,i=t||Ze(this._getItems(),r,n,this._config.wrap);if(i===r)return;const o=this._getItemIndex(i),s=t=>gt.trigger(this._element,t,{relatedTarget:i,direction:this._orderToDirection(e),from:this._getItemIndex(r),to:o});if(s(Wt).defaultPrevented)return;if(!r||!i)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=i;const l=n?"carousel-item-start":"carousel-item-end",c=n?"carousel-item-next":"carousel-item-prev";i.classList.add(c),Ge(i),r.classList.add(l),i.classList.add(l);this._queueCallback((()=>{i.classList.remove(l,c),i.classList.add(rr),r.classList.remove(rr,c,l),this._isSliding=!1,s(Yt)}),r,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return St.findOne(or,this._element)}_getItems(){return St.find(ir,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(e){return Xe()?e===zt?$t:Vt:e===zt?Vt:$t}_orderToDirection(e){return Xe()?e===$t?zt:Gt:e===$t?Gt:zt}static jQueryInterface(e){return this.each((function(){const t=cr.getOrCreateInstance(this,e);if("number"!=typeof e){if("string"==typeof e){if(void 0===t[e]||e.startsWith("_")||"constructor"===e)throw new TypeError(`No method named "${e}"`);t[e]()}}else t.to(e)}))}}gt.on(document,er,"[data-bs-slide], [data-bs-slide-to]",(function(e){const t=Pe(this);if(!t||!t.classList.contains(tr))return;e.preventDefault();const r=cr.getOrCreateInstance(t),n=this.getAttribute("data-bs-slide-to");return n?(r.to(n),void r._maybeEnableCycle()):"next"===_t.getDataAttribute(this,"slide")?(r.next(),void r._maybeEnableCycle()):(r.prev(),void r._maybeEnableCycle())})),gt.on(window,Zt,(()=>{const e=St.find('[data-bs-ride="carousel"]');for(const t of e)cr.getOrCreateInstance(t)})),Ke(cr);const ur=".bs.collapse",pr=`show${ur}`,fr=`shown${ur}`,dr=`hide${ur}`,hr=`hidden${ur}`,gr=`click${ur}.data-api`,mr="show",vr="collapse",br="collapsing",yr=`:scope .${vr} .${vr}`,wr='[data-bs-toggle="collapse"]',_r={parent:null,toggle:!0},xr={parent:"(null|element)",toggle:"boolean"};class Ar extends At{constructor(e,t){super(e,t),this._isTransitioning=!1,this._triggerArray=[];const r=St.find(wr);for(const e of r){const t=Ie(e),r=St.find(t).filter((e=>e===this._element));null!==t&&r.length&&this._triggerArray.push(e)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return _r}static get DefaultType(){return xr}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let e=[];if(this._config.parent&&(e=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter((e=>e!==this._element)).map((e=>Ar.getOrCreateInstance(e,{toggle:!1})))),e.length&&e[0]._isTransitioning)return;if(gt.trigger(this._element,pr).defaultPrevented)return;for(const t of e)t.hide();const t=this._getDimension();this._element.classList.remove(vr),this._element.classList.add(br),this._element.style[t]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const r=`scroll${t[0].toUpperCase()+t.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(br),this._element.classList.add(vr,mr),this._element.style[t]="",gt.trigger(this._element,fr)}),this._element,!0),this._element.style[t]=`${this._element[r]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(gt.trigger(this._element,dr).defaultPrevented)return;const e=this._getDimension();this._element.style[e]=`${this._element.getBoundingClientRect()[e]}px`,Ge(this._element),this._element.classList.add(br),this._element.classList.remove(vr,mr);for(const e of this._triggerArray){const t=Pe(e);t&&!this._isShown(t)&&this._addAriaAndCollapsedClass([e],!1)}this._isTransitioning=!0;this._element.style[e]="",this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(br),this._element.classList.add(vr),gt.trigger(this._element,hr)}),this._element,!0)}_isShown(e=this._element){return e.classList.contains(mr)}_configAfterMerge(e){return e.toggle=Boolean(e.toggle),e.parent=Me(e.parent),e}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const e=this._getFirstLevelChildren(wr);for(const t of e){const e=Pe(t);e&&this._addAriaAndCollapsedClass([t],this._isShown(e))}}_getFirstLevelChildren(e){const t=St.find(yr,this._config.parent);return St.find(e,this._config.parent).filter((e=>!t.includes(e)))}_addAriaAndCollapsedClass(e,t){if(e.length)for(const r of e)r.classList.toggle("collapsed",!t),r.setAttribute("aria-expanded",t)}static jQueryInterface(e){const t={};return"string"==typeof e&&/show|hide/.test(e)&&(t.toggle=!1),this.each((function(){const r=Ar.getOrCreateInstance(this,t);if("string"==typeof e){if(void 0===r[e])throw new TypeError(`No method named "${e}"`);r[e]()}}))}}gt.on(document,gr,wr,(function(e){("A"===e.target.tagName||e.delegateTarget&&"A"===e.delegateTarget.tagName)&&e.preventDefault();const t=Ie(this),r=St.find(t);for(const e of r)Ar.getOrCreateInstance(e,{toggle:!1}).toggle()})),Ke(Ar);const Er="dropdown",Dr=".bs.dropdown",Tr=".data-api",Cr="ArrowUp",qr="ArrowDown",kr=`hide${Dr}`,Lr=`hidden${Dr}`,Sr=`show${Dr}`,Nr=`shown${Dr}`,Or=`click${Dr}${Tr}`,jr=`keydown${Dr}${Tr}`,Rr=`keyup${Dr}${Tr}`,Br="show",Ir='[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)',Pr=`${Ir}.${Br}`,Fr=".dropdown-menu",Hr=Xe()?"top-end":"top-start",Mr=Xe()?"top-start":"top-end",Ur=Xe()?"bottom-end":"bottom-start",Vr=Xe()?"bottom-start":"bottom-end",$r=Xe()?"left-start":"right-start",zr=Xe()?"right-start":"left-start",Gr={autoClose:!0,boundary:"clippingParents",display:"dynamic",offset:[0,2],popperConfig:null,reference:"toggle"},Wr={autoClose:"(boolean|string)",boundary:"(string|element)",display:"string",offset:"(array|string|function)",popperConfig:"(null|object|function)",reference:"(string|element|object)"};class Yr extends At{constructor(e,t){super(e,t),this._popper=null,this._parent=this._element.parentNode,this._menu=St.next(this._element,Fr)[0]||St.prev(this._element,Fr)[0]||St.findOne(Fr,this._parent),this._inNavbar=this._detectNavbar()}static get Default(){return Gr}static get DefaultType(){return Wr}static get NAME(){return Er}toggle(){return this._isShown()?this.hide():this.show()}show(){if(Ve(this._element)||this._isShown())return;const e={relatedTarget:this._element};if(!gt.trigger(this._element,Sr,e).defaultPrevented){if(this._createPopper(),"ontouchstart"in document.documentElement&&!this._parent.closest(".navbar-nav"))for(const e of[].concat(...document.body.children))gt.on(e,"mouseover",ze);this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(Br),this._element.classList.add(Br),gt.trigger(this._element,Nr,e)}}hide(){if(Ve(this._element)||!this._isShown())return;const e={relatedTarget:this._element};this._completeHide(e)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(e){if(!gt.trigger(this._element,kr,e).defaultPrevented){if("ontouchstart"in document.documentElement)for(const e of[].concat(...document.body.children))gt.off(e,"mouseover",ze);this._popper&&this._popper.destroy(),this._menu.classList.remove(Br),this._element.classList.remove(Br),this._element.setAttribute("aria-expanded","false"),_t.removeDataAttribute(this._menu,"popper"),gt.trigger(this._element,Lr,e)}}_getConfig(e){if("object"==typeof(e=super._getConfig(e)).reference&&!He(e.reference)&&"function"!=typeof e.reference.getBoundingClientRect)throw new TypeError(`${Er.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return e}_createPopper(){if(void 0===n)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let e=this._element;"parent"===this._config.reference?e=this._parent:He(this._config.reference)?e=Me(this._config.reference):"object"==typeof this._config.reference&&(e=this._config.reference);const t=this._getPopperConfig();this._popper=Oe(e,this._menu,t)}_isShown(){return this._menu.classList.contains(Br)}_getPlacement(){const e=this._parent;if(e.classList.contains("dropend"))return $r;if(e.classList.contains("dropstart"))return zr;if(e.classList.contains("dropup-center"))return"top";if(e.classList.contains("dropdown-center"))return"bottom";const t="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return e.classList.contains("dropup")?t?Mr:Hr:t?Vr:Ur}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:e}=this._config;return"string"==typeof e?e.split(",").map((e=>Number.parseInt(e,10))):"function"==typeof e?t=>e(t,this._element):e}_getPopperConfig(){const e={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(_t.setDataAttribute(this._menu,"popper","static"),e.modifiers=[{name:"applyStyles",enabled:!1}]),{...e,..."function"==typeof this._config.popperConfig?this._config.popperConfig(e):this._config.popperConfig}}_selectMenuItem({key:e,target:t}){const r=St.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter((e=>Ue(e)));r.length&&Ze(r,t,e===qr,!r.includes(t)).focus()}static jQueryInterface(e){return this.each((function(){const t=Yr.getOrCreateInstance(this,e);if("string"==typeof e){if(void 0===t[e])throw new TypeError(`No method named "${e}"`);t[e]()}}))}static clearMenus(e){if(2===e.button||"keyup"===e.type&&"Tab"!==e.key)return;const t=St.find(Pr);for(const r of t){const t=Yr.getInstance(r);if(!t||!1===t._config.autoClose)continue;const n=e.composedPath(),i=n.includes(t._menu);if(n.includes(t._element)||"inside"===t._config.autoClose&&!i||"outside"===t._config.autoClose&&i)continue;if(t._menu.contains(e.target)&&("keyup"===e.type&&"Tab"===e.key||/input|select|option|textarea|form/i.test(e.target.tagName)))continue;const o={relatedTarget:t._element};"click"===e.type&&(o.clickEvent=e),t._completeHide(o)}}static dataApiKeydownHandler(e){const t=/input|textarea/i.test(e.target.tagName),r="Escape"===e.key,n=[Cr,qr].includes(e.key);if(!n&&!r)return;if(t&&!r)return;e.preventDefault();const i=this.matches(Ir)?this:St.prev(this,Ir)[0]||St.next(this,Ir)[0]||St.findOne(Ir,e.delegateTarget.parentNode),o=Yr.getOrCreateInstance(i);if(n)return e.stopPropagation(),o.show(),void o._selectMenuItem(e);o._isShown()&&(e.stopPropagation(),o.hide(),i.focus())}}gt.on(document,jr,Ir,Yr.dataApiKeydownHandler),gt.on(document,jr,Fr,Yr.dataApiKeydownHandler),gt.on(document,Or,Yr.clearMenus),gt.on(document,Rr,Yr.clearMenus),gt.on(document,Or,Ir,(function(e){e.preventDefault(),Yr.getOrCreateInstance(this).toggle()})),Ke(Yr);const Xr=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",Kr=".sticky-top",Jr="padding-right",Qr="margin-right";class Zr{constructor(){this._element=document.body}getWidth(){const e=document.documentElement.clientWidth;return Math.abs(window.innerWidth-e)}hide(){const e=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,Jr,(t=>t+e)),this._setElementAttributes(Xr,Jr,(t=>t+e)),this._setElementAttributes(Kr,Qr,(t=>t-e))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,Jr),this._resetElementAttributes(Xr,Jr),this._resetElementAttributes(Kr,Qr)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(e,t,r){const n=this.getWidth();this._applyManipulationCallback(e,(e=>{if(e!==this._element&&window.innerWidth>e.clientWidth+n)return;this._saveInitialAttribute(e,t);const i=window.getComputedStyle(e).getPropertyValue(t);e.style.setProperty(t,`${r(Number.parseFloat(i))}px`)}))}_saveInitialAttribute(e,t){const r=e.style.getPropertyValue(t);r&&_t.setDataAttribute(e,t,r)}_resetElementAttributes(e,t){this._applyManipulationCallback(e,(e=>{const r=_t.getDataAttribute(e,t);null!==r?(_t.removeDataAttribute(e,t),e.style.setProperty(t,r)):e.style.removeProperty(t)}))}_applyManipulationCallback(e,t){if(He(e))t(e);else for(const r of St.find(e,this._element))t(r)}}const en="backdrop",tn="show",rn=`mousedown.bs.${en}`,nn={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},on={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class sn extends xt{constructor(e){super(),this._config=this._getConfig(e),this._isAppended=!1,this._element=null}static get Default(){return nn}static get DefaultType(){return on}static get NAME(){return en}show(e){if(!this._config.isVisible)return void Je(e);this._append();const t=this._getElement();this._config.isAnimated&&Ge(t),t.classList.add(tn),this._emulateAnimation((()=>{Je(e)}))}hide(e){this._config.isVisible?(this._getElement().classList.remove(tn),this._emulateAnimation((()=>{this.dispose(),Je(e)}))):Je(e)}dispose(){this._isAppended&&(gt.off(this._element,rn),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const e=document.createElement("div");e.className=this._config.className,this._config.isAnimated&&e.classList.add("fade"),this._element=e}return this._element}_configAfterMerge(e){return e.rootElement=Me(e.rootElement),e}_append(){if(this._isAppended)return;const e=this._getElement();this._config.rootElement.append(e),gt.on(e,rn,(()=>{Je(this._config.clickCallback)})),this._isAppended=!0}_emulateAnimation(e){Qe(e,this._getElement(),this._config.isAnimated)}}const an=".bs.focustrap",ln=`focusin${an}`,cn=`keydown.tab${an}`,un="backward",pn={autofocus:!0,trapElement:null},fn={autofocus:"boolean",trapElement:"element"};class dn extends xt{constructor(e){super(),this._config=this._getConfig(e),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return pn}static get DefaultType(){return fn}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),gt.off(document,an),gt.on(document,ln,(e=>this._handleFocusin(e))),gt.on(document,cn,(e=>this._handleKeydown(e))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,gt.off(document,an))}_handleFocusin(e){const{trapElement:t}=this._config;if(e.target===document||e.target===t||t.contains(e.target))return;const r=St.focusableChildren(t);0===r.length?t.focus():this._lastTabNavDirection===un?r[r.length-1].focus():r[0].focus()}_handleKeydown(e){"Tab"===e.key&&(this._lastTabNavDirection=e.shiftKey?un:"forward")}}const hn=".bs.modal",gn=`hide${hn}`,mn=`hidePrevented${hn}`,vn=`hidden${hn}`,bn=`show${hn}`,yn=`shown${hn}`,wn=`resize${hn}`,_n=`click.dismiss${hn}`,xn=`mousedown.dismiss${hn}`,An=`keydown.dismiss${hn}`,En=`click${hn}.data-api`,Dn="modal-open",Tn="show",Cn="modal-static",qn={backdrop:!0,focus:!0,keyboard:!0},kn={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class Ln extends At{constructor(e,t){super(e,t),this._dialog=St.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new Zr,this._addEventListeners()}static get Default(){return qn}static get DefaultType(){return kn}static get NAME(){return"modal"}toggle(e){return this._isShown?this.hide():this.show(e)}show(e){if(this._isShown||this._isTransitioning)return;gt.trigger(this._element,bn,{relatedTarget:e}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(Dn),this._adjustDialog(),this._backdrop.show((()=>this._showElement(e))))}hide(){if(!this._isShown||this._isTransitioning)return;gt.trigger(this._element,gn).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(Tn),this._queueCallback((()=>this._hideModal()),this._element,this._isAnimated()))}dispose(){for(const e of[window,this._dialog])gt.off(e,hn);this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new sn({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new dn({trapElement:this._element})}_showElement(e){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const t=St.findOne(".modal-body",this._dialog);t&&(t.scrollTop=0),Ge(this._element),this._element.classList.add(Tn);this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,gt.trigger(this._element,yn,{relatedTarget:e})}),this._dialog,this._isAnimated())}_addEventListeners(){gt.on(this._element,An,(e=>{if("Escape"===e.key)return this._config.keyboard?(e.preventDefault(),void this.hide()):void this._triggerBackdropTransition()})),gt.on(window,wn,(()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()})),gt.on(this._element,xn,(e=>{gt.one(this._element,_n,(t=>{this._element===e.target&&this._element===t.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())}))}))}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(Dn),this._resetAdjustments(),this._scrollBar.reset(),gt.trigger(this._element,vn)}))}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(gt.trigger(this._element,mn).defaultPrevented)return;const e=this._element.scrollHeight>document.documentElement.clientHeight,t=this._element.style.overflowY;"hidden"===t||this._element.classList.contains(Cn)||(e||(this._element.style.overflowY="hidden"),this._element.classList.add(Cn),this._queueCallback((()=>{this._element.classList.remove(Cn),this._queueCallback((()=>{this._element.style.overflowY=t}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const e=this._element.scrollHeight>document.documentElement.clientHeight,t=this._scrollBar.getWidth(),r=t>0;if(r&&!e){const e=Xe()?"paddingLeft":"paddingRight";this._element.style[e]=`${t}px`}if(!r&&e){const e=Xe()?"paddingRight":"paddingLeft";this._element.style[e]=`${t}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(e,t){return this.each((function(){const r=Ln.getOrCreateInstance(this,e);if("string"==typeof e){if(void 0===r[e])throw new TypeError(`No method named "${e}"`);r[e](t)}}))}}gt.on(document,En,'[data-bs-toggle="modal"]',(function(e){const t=Pe(this);["A","AREA"].includes(this.tagName)&&e.preventDefault(),gt.one(t,bn,(e=>{e.defaultPrevented||gt.one(t,vn,(()=>{Ue(this)&&this.focus()}))}));const r=St.findOne(".modal.show");r&&Ln.getInstance(r).hide();Ln.getOrCreateInstance(t).toggle(this)})),Et(Ln),Ke(Ln);const Sn=".bs.offcanvas",Nn=".data-api",On=`load${Sn}${Nn}`,jn="show",Rn="showing",Bn="hiding",In=".offcanvas.show",Pn=`show${Sn}`,Fn=`shown${Sn}`,Hn=`hide${Sn}`,Mn=`hidePrevented${Sn}`,Un=`hidden${Sn}`,Vn=`resize${Sn}`,$n=`click${Sn}${Nn}`,zn=`keydown.dismiss${Sn}`,Gn={backdrop:!0,keyboard:!0,scroll:!1},Wn={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class Yn extends At{constructor(e,t){super(e,t),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return Gn}static get DefaultType(){return Wn}static get NAME(){return"offcanvas"}toggle(e){return this._isShown?this.hide():this.show(e)}show(e){if(this._isShown)return;if(gt.trigger(this._element,Pn,{relatedTarget:e}).defaultPrevented)return;this._isShown=!0,this._backdrop.show(),this._config.scroll||(new Zr).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(Rn);this._queueCallback((()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(jn),this._element.classList.remove(Rn),gt.trigger(this._element,Fn,{relatedTarget:e})}),this._element,!0)}hide(){if(!this._isShown)return;if(gt.trigger(this._element,Hn).defaultPrevented)return;this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add(Bn),this._backdrop.hide();this._queueCallback((()=>{this._element.classList.remove(jn,Bn),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new Zr).reset(),gt.trigger(this._element,Un)}),this._element,!0)}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const e=Boolean(this._config.backdrop);return new sn({className:"offcanvas-backdrop",isVisible:e,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:e?()=>{"static"!==this._config.backdrop?this.hide():gt.trigger(this._element,Mn)}:null})}_initializeFocusTrap(){return new dn({trapElement:this._element})}_addEventListeners(){gt.on(this._element,zn,(e=>{"Escape"===e.key&&(this._config.keyboard?this.hide():gt.trigger(this._element,Mn))}))}static jQueryInterface(e){return this.each((function(){const t=Yn.getOrCreateInstance(this,e);if("string"==typeof e){if(void 0===t[e]||e.startsWith("_")||"constructor"===e)throw new TypeError(`No method named "${e}"`);t[e](this)}}))}}gt.on(document,$n,'[data-bs-toggle="offcanvas"]',(function(e){const t=Pe(this);if(["A","AREA"].includes(this.tagName)&&e.preventDefault(),Ve(this))return;gt.one(t,Un,(()=>{Ue(this)&&this.focus()}));const r=St.findOne(In);r&&r!==t&&Yn.getInstance(r).hide();Yn.getOrCreateInstance(t).toggle(this)})),gt.on(window,On,(()=>{for(const e of St.find(In))Yn.getOrCreateInstance(e).show()})),gt.on(window,Vn,(()=>{for(const e of St.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(e).position&&Yn.getOrCreateInstance(e).hide()})),Et(Yn),Ke(Yn);const Xn=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Kn=/^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i,Jn=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,Qn=(e,t)=>{const r=e.nodeName.toLowerCase();return t.includes(r)?!Xn.has(r)||Boolean(Kn.test(e.nodeValue)||Jn.test(e.nodeValue)):t.filter((e=>e instanceof RegExp)).some((e=>e.test(r)))},Zn={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]};const ei={allowList:Zn,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"
"},ti={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},ri={entry:"(string|element|function|null)",selector:"(string|element)"};class ni extends xt{constructor(e){super(),this._config=this._getConfig(e)}static get Default(){return ei}static get DefaultType(){return ti}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map((e=>this._resolvePossibleFunction(e))).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(e){return this._checkContent(e),this._config.content={...this._config.content,...e},this}toHtml(){const e=document.createElement("div");e.innerHTML=this._maybeSanitize(this._config.template);for(const[t,r]of Object.entries(this._config.content))this._setContent(e,r,t);const t=e.children[0],r=this._resolvePossibleFunction(this._config.extraClass);return r&&t.classList.add(...r.split(" ")),t}_typeCheckConfig(e){super._typeCheckConfig(e),this._checkContent(e.content)}_checkContent(e){for(const[t,r]of Object.entries(e))super._typeCheckConfig({selector:t,entry:r},ri)}_setContent(e,t,r){const n=St.findOne(r,e);n&&((t=this._resolvePossibleFunction(t))?He(t)?this._putElementInTemplate(Me(t),n):this._config.html?n.innerHTML=this._maybeSanitize(t):n.textContent=t:n.remove())}_maybeSanitize(e){return this._config.sanitize?function(e,t,r){if(!e.length)return e;if(r&&"function"==typeof r)return r(e);const n=(new window.DOMParser).parseFromString(e,"text/html"),i=[].concat(...n.body.querySelectorAll("*"));for(const e of i){const r=e.nodeName.toLowerCase();if(!Object.keys(t).includes(r)){e.remove();continue}const n=[].concat(...e.attributes),i=[].concat(t["*"]||[],t[r]||[]);for(const t of n)Qn(t,i)||e.removeAttribute(t.nodeName)}return n.body.innerHTML}(e,this._config.allowList,this._config.sanitizeFn):e}_resolvePossibleFunction(e){return"function"==typeof e?e(this):e}_putElementInTemplate(e,t){if(this._config.html)return t.innerHTML="",void t.append(e);t.textContent=e.textContent}}const ii=new Set(["sanitize","allowList","sanitizeFn"]),oi="fade",si="show",ai=".modal",li="hide.bs.modal",ci="hover",ui="focus",pi={AUTO:"auto",TOP:"top",RIGHT:Xe()?"left":"right",BOTTOM:"bottom",LEFT:Xe()?"right":"left"},fi={allowList:Zn,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,0],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'',title:"",trigger:"hover focus"},di={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class hi extends At{constructor(e,t){if(void 0===n)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(e,t),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return fi}static get DefaultType(){return di}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._activeTrigger.click=!this._activeTrigger.click,this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),gt.off(this._element.closest(ai),li,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const e=gt.trigger(this._element,this.constructor.eventName("show")),t=($e(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(e.defaultPrevented||!t)return;this._disposePopper();const r=this._getTipElement();this._element.setAttribute("aria-describedby",r.getAttribute("id"));const{container:n}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(n.append(r),gt.trigger(this._element,this.constructor.eventName("inserted"))),this._popper=this._createPopper(r),r.classList.add(si),"ontouchstart"in document.documentElement)for(const e of[].concat(...document.body.children))gt.on(e,"mouseover",ze);this._queueCallback((()=>{gt.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1}),this.tip,this._isAnimated())}hide(){if(!this._isShown())return;if(gt.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented)return;if(this._getTipElement().classList.remove(si),"ontouchstart"in document.documentElement)for(const e of[].concat(...document.body.children))gt.off(e,"mouseover",ze);this._activeTrigger.click=!1,this._activeTrigger[ui]=!1,this._activeTrigger[ci]=!1,this._isHovered=null;this._queueCallback((()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),gt.trigger(this._element,this.constructor.eventName("hidden")))}),this.tip,this._isAnimated())}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(e){const t=this._getTemplateFactory(e).toHtml();if(!t)return null;t.classList.remove(oi,si),t.classList.add(`bs-${this.constructor.NAME}-auto`);const r=(e=>{do{e+=Math.floor(1e6*Math.random())}while(document.getElementById(e));return e})(this.constructor.NAME).toString();return t.setAttribute("id",r),this._isAnimated()&&t.classList.add(oi),t}setContent(e){this._newContent=e,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(e){return this._templateFactory?this._templateFactory.changeContent(e):this._templateFactory=new ni({...this._config,content:e,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{".tooltip-inner":this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(e){return this.constructor.getOrCreateInstance(e.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(oi)}_isShown(){return this.tip&&this.tip.classList.contains(si)}_createPopper(e){const t="function"==typeof this._config.placement?this._config.placement.call(this,e,this._element):this._config.placement,r=pi[t.toUpperCase()];return Oe(this._element,e,this._getPopperConfig(r))}_getOffset(){const{offset:e}=this._config;return"string"==typeof e?e.split(",").map((e=>Number.parseInt(e,10))):"function"==typeof e?t=>e(t,this._element):e}_resolvePossibleFunction(e){return"function"==typeof e?e.call(this._element):e}_getPopperConfig(e){const t={placement:e,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:e=>{this._getTipElement().setAttribute("data-popper-placement",e.state.placement)}}]};return{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_setListeners(){const e=this._config.trigger.split(" ");for(const t of e)if("click"===t)gt.on(this._element,this.constructor.eventName("click"),this._config.selector,(e=>{this._initializeOnDelegatedTarget(e).toggle()}));else if("manual"!==t){const e=t===ci?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),r=t===ci?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");gt.on(this._element,e,this._config.selector,(e=>{const t=this._initializeOnDelegatedTarget(e);t._activeTrigger["focusin"===e.type?ui:ci]=!0,t._enter()})),gt.on(this._element,r,this._config.selector,(e=>{const t=this._initializeOnDelegatedTarget(e);t._activeTrigger["focusout"===e.type?ui:ci]=t._element.contains(e.relatedTarget),t._leave()}))}this._hideModalHandler=()=>{this._element&&this.hide()},gt.on(this._element.closest(ai),li,this._hideModalHandler)}_fixTitle(){const e=this._element.getAttribute("title");e&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",e),this._element.setAttribute("data-bs-original-title",e),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout((()=>{this._isHovered&&this.show()}),this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout((()=>{this._isHovered||this.hide()}),this._config.delay.hide))}_setTimeout(e,t){clearTimeout(this._timeout),this._timeout=setTimeout(e,t)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(e){const t=_t.getDataAttributes(this._element);for(const e of Object.keys(t))ii.has(e)&&delete t[e];return e={...t,..."object"==typeof e&&e?e:{}},e=this._mergeConfigObj(e),e=this._configAfterMerge(e),this._typeCheckConfig(e),e}_configAfterMerge(e){return e.container=!1===e.container?document.body:Me(e.container),"number"==typeof e.delay&&(e.delay={show:e.delay,hide:e.delay}),"number"==typeof e.title&&(e.title=e.title.toString()),"number"==typeof e.content&&(e.content=e.content.toString()),e}_getDelegateConfig(){const e={};for(const t in this._config)this.constructor.Default[t]!==this._config[t]&&(e[t]=this._config[t]);return e.selector=!1,e.trigger="manual",e}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(e){return this.each((function(){const t=hi.getOrCreateInstance(this,e);if("string"==typeof e){if(void 0===t[e])throw new TypeError(`No method named "${e}"`);t[e]()}}))}}Ke(hi);const gi={...hi.Default,content:"",offset:[0,8],placement:"right",template:'',trigger:"click"},mi={...hi.DefaultType,content:"(null|string|element|function)"};class vi extends hi{static get Default(){return gi}static get DefaultType(){return mi}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{".popover-header":this._getTitle(),".popover-body":this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(e){return this.each((function(){const t=vi.getOrCreateInstance(this,e);if("string"==typeof e){if(void 0===t[e])throw new TypeError(`No method named "${e}"`);t[e]()}}))}}Ke(vi);const bi=".bs.scrollspy",yi=`activate${bi}`,wi=`click${bi}`,_i=`load${bi}.data-api`,xi="active",Ai="[href]",Ei=".nav-link",Di=`${Ei}, .nav-item > ${Ei}, .list-group-item`,Ti={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},Ci={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class qi extends At{constructor(e,t){super(e,t),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return Ti}static get DefaultType(){return Ci}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const e of this._observableSections.values())this._observer.observe(e)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(e){return e.target=Me(e.target)||document.body,e.rootMargin=e.offset?`${e.offset}px 0px -30%`:e.rootMargin,"string"==typeof e.threshold&&(e.threshold=e.threshold.split(",").map((e=>Number.parseFloat(e)))),e}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(gt.off(this._config.target,wi),gt.on(this._config.target,wi,Ai,(e=>{const t=this._observableSections.get(e.target.hash);if(t){e.preventDefault();const r=this._rootElement||window,n=t.offsetTop-this._element.offsetTop;if(r.scrollTo)return void r.scrollTo({top:n,behavior:"smooth"});r.scrollTop=n}})))}_getNewObserver(){const e={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver((e=>this._observerCallback(e)),e)}_observerCallback(e){const t=e=>this._targetLinks.get(`#${e.target.id}`),r=e=>{this._previousScrollData.visibleEntryTop=e.target.offsetTop,this._process(t(e))},n=(this._rootElement||document.documentElement).scrollTop,i=n>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=n;for(const o of e){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(t(o));continue}const e=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(i&&e){if(r(o),!n)return}else i||e||r(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const e=St.find(Ai,this._config.target);for(const t of e){if(!t.hash||Ve(t))continue;const e=St.findOne(t.hash,this._element);Ue(e)&&(this._targetLinks.set(t.hash,t),this._observableSections.set(t.hash,e))}}_process(e){this._activeTarget!==e&&(this._clearActiveClass(this._config.target),this._activeTarget=e,e.classList.add(xi),this._activateParents(e),gt.trigger(this._element,yi,{relatedTarget:e}))}_activateParents(e){if(e.classList.contains("dropdown-item"))St.findOne(".dropdown-toggle",e.closest(".dropdown")).classList.add(xi);else for(const t of St.parents(e,".nav, .list-group"))for(const e of St.prev(t,Di))e.classList.add(xi)}_clearActiveClass(e){e.classList.remove(xi);const t=St.find(`${Ai}.${xi}`,e);for(const e of t)e.classList.remove(xi)}static jQueryInterface(e){return this.each((function(){const t=qi.getOrCreateInstance(this,e);if("string"==typeof e){if(void 0===t[e]||e.startsWith("_")||"constructor"===e)throw new TypeError(`No method named "${e}"`);t[e]()}}))}}gt.on(window,_i,(()=>{for(const e of St.find('[data-bs-spy="scroll"]'))qi.getOrCreateInstance(e)})),Ke(qi);const ki=".bs.tab",Li=`hide${ki}`,Si=`hidden${ki}`,Ni=`show${ki}`,Oi=`shown${ki}`,ji=`click${ki}`,Ri=`keydown${ki}`,Bi=`load${ki}`,Ii="ArrowLeft",Pi="ArrowRight",Fi="ArrowUp",Hi="ArrowDown",Mi="active",Ui="fade",Vi="show",$i=":not(.dropdown-toggle)",zi='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',Gi=`${`.nav-link${$i}, .list-group-item${$i}, [role="tab"]${$i}`}, ${zi}`,Wi=`.${Mi}[data-bs-toggle="tab"], .${Mi}[data-bs-toggle="pill"], .${Mi}[data-bs-toggle="list"]`;class Yi extends At{constructor(e){super(e),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),gt.on(this._element,Ri,(e=>this._keydown(e))))}static get NAME(){return"tab"}show(){const e=this._element;if(this._elemIsActive(e))return;const t=this._getActiveElem(),r=t?gt.trigger(t,Li,{relatedTarget:e}):null;gt.trigger(e,Ni,{relatedTarget:t}).defaultPrevented||r&&r.defaultPrevented||(this._deactivate(t,e),this._activate(e,t))}_activate(e,t){if(!e)return;e.classList.add(Mi),this._activate(Pe(e));this._queueCallback((()=>{"tab"===e.getAttribute("role")?(e.removeAttribute("tabindex"),e.setAttribute("aria-selected",!0),this._toggleDropDown(e,!0),gt.trigger(e,Oi,{relatedTarget:t})):e.classList.add(Vi)}),e,e.classList.contains(Ui))}_deactivate(e,t){if(!e)return;e.classList.remove(Mi),e.blur(),this._deactivate(Pe(e));this._queueCallback((()=>{"tab"===e.getAttribute("role")?(e.setAttribute("aria-selected",!1),e.setAttribute("tabindex","-1"),this._toggleDropDown(e,!1),gt.trigger(e,Si,{relatedTarget:t})):e.classList.remove(Vi)}),e,e.classList.contains(Ui))}_keydown(e){if(![Ii,Pi,Fi,Hi].includes(e.key))return;e.stopPropagation(),e.preventDefault();const t=[Pi,Hi].includes(e.key),r=Ze(this._getChildren().filter((e=>!Ve(e))),e.target,t,!0);r&&(r.focus({preventScroll:!0}),Yi.getOrCreateInstance(r).show())}_getChildren(){return St.find(Gi,this._parent)}_getActiveElem(){return this._getChildren().find((e=>this._elemIsActive(e)))||null}_setInitialAttributes(e,t){this._setAttributeIfNotExists(e,"role","tablist");for(const e of t)this._setInitialAttributesOnChild(e)}_setInitialAttributesOnChild(e){e=this._getInnerElement(e);const t=this._elemIsActive(e),r=this._getOuterElement(e);e.setAttribute("aria-selected",t),r!==e&&this._setAttributeIfNotExists(r,"role","presentation"),t||e.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(e,"role","tab"),this._setInitialAttributesOnTargetPanel(e)}_setInitialAttributesOnTargetPanel(e){const t=Pe(e);t&&(this._setAttributeIfNotExists(t,"role","tabpanel"),e.id&&this._setAttributeIfNotExists(t,"aria-labelledby",`#${e.id}`))}_toggleDropDown(e,t){const r=this._getOuterElement(e);if(!r.classList.contains("dropdown"))return;const n=(e,n)=>{const i=St.findOne(e,r);i&&i.classList.toggle(n,t)};n(".dropdown-toggle",Mi),n(".dropdown-menu",Vi),r.setAttribute("aria-expanded",t)}_setAttributeIfNotExists(e,t,r){e.hasAttribute(t)||e.setAttribute(t,r)}_elemIsActive(e){return e.classList.contains(Mi)}_getInnerElement(e){return e.matches(Gi)?e:St.findOne(Gi,e)}_getOuterElement(e){return e.closest(".nav-item, .list-group-item")||e}static jQueryInterface(e){return this.each((function(){const t=Yi.getOrCreateInstance(this);if("string"==typeof e){if(void 0===t[e]||e.startsWith("_")||"constructor"===e)throw new TypeError(`No method named "${e}"`);t[e]()}}))}}gt.on(document,ji,zi,(function(e){["A","AREA"].includes(this.tagName)&&e.preventDefault(),Ve(this)||Yi.getOrCreateInstance(this).show()})),gt.on(window,Bi,(()=>{for(const e of St.find(Wi))Yi.getOrCreateInstance(e)})),Ke(Yi);const Xi=".bs.toast",Ki=`mouseover${Xi}`,Ji=`mouseout${Xi}`,Qi=`focusin${Xi}`,Zi=`focusout${Xi}`,eo=`hide${Xi}`,to=`hidden${Xi}`,ro=`show${Xi}`,no=`shown${Xi}`,io="hide",oo="show",so="showing",ao={animation:"boolean",autohide:"boolean",delay:"number"},lo={animation:!0,autohide:!0,delay:5e3};class co extends At{constructor(e,t){super(e,t),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return lo}static get DefaultType(){return ao}static get NAME(){return"toast"}show(){if(gt.trigger(this._element,ro).defaultPrevented)return;this._clearTimeout(),this._config.animation&&this._element.classList.add("fade");this._element.classList.remove(io),Ge(this._element),this._element.classList.add(oo,so),this._queueCallback((()=>{this._element.classList.remove(so),gt.trigger(this._element,no),this._maybeScheduleHide()}),this._element,this._config.animation)}hide(){if(!this.isShown())return;if(gt.trigger(this._element,eo).defaultPrevented)return;this._element.classList.add(so),this._queueCallback((()=>{this._element.classList.add(io),this._element.classList.remove(so,oo),gt.trigger(this._element,to)}),this._element,this._config.animation)}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(oo),super.dispose()}isShown(){return this._element.classList.contains(oo)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(e,t){switch(e.type){case"mouseover":case"mouseout":this._hasMouseInteraction=t;break;case"focusin":case"focusout":this._hasKeyboardInteraction=t}if(t)return void this._clearTimeout();const r=e.relatedTarget;this._element===r||this._element.contains(r)||this._maybeScheduleHide()}_setListeners(){gt.on(this._element,Ki,(e=>this._onInteraction(e,!0))),gt.on(this._element,Ji,(e=>this._onInteraction(e,!1))),gt.on(this._element,Qi,(e=>this._onInteraction(e,!0))),gt.on(this._element,Zi,(e=>this._onInteraction(e,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(e){return this.each((function(){const t=co.getOrCreateInstance(this,e);if("string"==typeof e){if(void 0===t[e])throw new TypeError(`No method named "${e}"`);t[e](this)}}))}}Et(co),Ke(co)},702:function(e,t,r){e.exports=function(){"use strict";function e(e){var t=typeof e;return null!==e&&("object"===t||"function"===t)}function t(e){return"function"==typeof e}var n=Array.isArray?Array.isArray:function(e){return"[object Array]"===Object.prototype.toString.call(e)},i=0,o=void 0,s=void 0,a=function(e,t){w[i]=e,w[i+1]=t,2===(i+=2)&&(s?s(_):A())};function l(e){s=e}function c(e){a=e}var u="undefined"!=typeof window?window:void 0,p=u||{},f=p.MutationObserver||p.WebKitMutationObserver,d="undefined"==typeof self&&"undefined"!=typeof process&&"[object process]"==={}.toString.call(process),h="undefined"!=typeof Uint8ClampedArray&&"undefined"!=typeof importScripts&&"undefined"!=typeof MessageChannel;function g(){return function(){return process.nextTick(_)}}function m(){return void 0!==o?function(){o(_)}:y()}function v(){var e=0,t=new f(_),r=document.createTextNode("");return t.observe(r,{characterData:!0}),function(){r.data=e=++e%2}}function b(){var e=new MessageChannel;return e.port1.onmessage=_,function(){return e.port2.postMessage(0)}}function y(){var e=setTimeout;return function(){return e(_,1)}}var w=new Array(1e3);function _(){for(var e=0;e\u20D2|\u205F\u200A|\u219D\u0338|\u2202\u0338|\u2220\u20D2|\u2229\uFE00|\u222A\uFE00|\u223C\u20D2|\u223D\u0331|\u223E\u0333|\u2242\u0338|\u224B\u0338|\u224D\u20D2|\u224E\u0338|\u224F\u0338|\u2250\u0338|\u2261\u20E5|\u2264\u20D2|\u2265\u20D2|\u2266\u0338|\u2267\u0338|\u2268\uFE00|\u2269\uFE00|\u226A\u0338|\u226A\u20D2|\u226B\u0338|\u226B\u20D2|\u227F\u0338|\u2282\u20D2|\u2283\u20D2|\u228A\uFE00|\u228B\uFE00|\u228F\u0338|\u2290\u0338|\u2293\uFE00|\u2294\uFE00|\u22B4\u20D2|\u22B5\u20D2|\u22D8\u0338|\u22D9\u0338|\u22DA\uFE00|\u22DB\uFE00|\u22F5\u0338|\u22F9\u0338|\u2933\u0338|\u29CF\u0338|\u29D0\u0338|\u2A6D\u0338|\u2A70\u0338|\u2A7D\u0338|\u2A7E\u0338|\u2AA1\u0338|\u2AA2\u0338|\u2AAC\uFE00|\u2AAD\uFE00|\u2AAF\u0338|\u2AB0\u0338|\u2AC5\u0338|\u2AC6\u0338|\u2ACB\uFE00|\u2ACC\uFE00|\u2AFD\u20E5|[\xA0-\u0113\u0116-\u0122\u0124-\u012B\u012E-\u014D\u0150-\u017E\u0192\u01B5\u01F5\u0237\u02C6\u02C7\u02D8-\u02DD\u0311\u0391-\u03A1\u03A3-\u03A9\u03B1-\u03C9\u03D1\u03D2\u03D5\u03D6\u03DC\u03DD\u03F0\u03F1\u03F5\u03F6\u0401-\u040C\u040E-\u044F\u0451-\u045C\u045E\u045F\u2002-\u2005\u2007-\u2010\u2013-\u2016\u2018-\u201A\u201C-\u201E\u2020-\u2022\u2025\u2026\u2030-\u2035\u2039\u203A\u203E\u2041\u2043\u2044\u204F\u2057\u205F-\u2063\u20AC\u20DB\u20DC\u2102\u2105\u210A-\u2113\u2115-\u211E\u2122\u2124\u2127-\u2129\u212C\u212D\u212F-\u2131\u2133-\u2138\u2145-\u2148\u2153-\u215E\u2190-\u219B\u219D-\u21A7\u21A9-\u21AE\u21B0-\u21B3\u21B5-\u21B7\u21BA-\u21DB\u21DD\u21E4\u21E5\u21F5\u21FD-\u2205\u2207-\u2209\u220B\u220C\u220F-\u2214\u2216-\u2218\u221A\u221D-\u2238\u223A-\u2257\u2259\u225A\u225C\u225F-\u2262\u2264-\u228B\u228D-\u229B\u229D-\u22A5\u22A7-\u22B0\u22B2-\u22BB\u22BD-\u22DB\u22DE-\u22E3\u22E6-\u22F7\u22F9-\u22FE\u2305\u2306\u2308-\u2310\u2312\u2313\u2315\u2316\u231C-\u231F\u2322\u2323\u232D\u232E\u2336\u233D\u233F\u237C\u23B0\u23B1\u23B4-\u23B6\u23DC-\u23DF\u23E2\u23E7\u2423\u24C8\u2500\u2502\u250C\u2510\u2514\u2518\u251C\u2524\u252C\u2534\u253C\u2550-\u256C\u2580\u2584\u2588\u2591-\u2593\u25A1\u25AA\u25AB\u25AD\u25AE\u25B1\u25B3-\u25B5\u25B8\u25B9\u25BD-\u25BF\u25C2\u25C3\u25CA\u25CB\u25EC\u25EF\u25F8-\u25FC\u2605\u2606\u260E\u2640\u2642\u2660\u2663\u2665\u2666\u266A\u266D-\u266F\u2713\u2717\u2720\u2736\u2758\u2772\u2773\u27C8\u27C9\u27E6-\u27ED\u27F5-\u27FA\u27FC\u27FF\u2902-\u2905\u290C-\u2913\u2916\u2919-\u2920\u2923-\u292A\u2933\u2935-\u2939\u293C\u293D\u2945\u2948-\u294B\u294E-\u2976\u2978\u2979\u297B-\u297F\u2985\u2986\u298B-\u2996\u299A\u299C\u299D\u29A4-\u29B7\u29B9\u29BB\u29BC\u29BE-\u29C5\u29C9\u29CD-\u29D0\u29DC-\u29DE\u29E3-\u29E5\u29EB\u29F4\u29F6\u2A00-\u2A02\u2A04\u2A06\u2A0C\u2A0D\u2A10-\u2A17\u2A22-\u2A27\u2A29\u2A2A\u2A2D-\u2A31\u2A33-\u2A3C\u2A3F\u2A40\u2A42-\u2A4D\u2A50\u2A53-\u2A58\u2A5A-\u2A5D\u2A5F\u2A66\u2A6A\u2A6D-\u2A75\u2A77-\u2A9A\u2A9D-\u2AA2\u2AA4-\u2AB0\u2AB3-\u2AC8\u2ACB\u2ACC\u2ACF-\u2ADB\u2AE4\u2AE6-\u2AE9\u2AEB-\u2AF3\u2AFD\uFB00-\uFB04]|\uD835[\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDCCF\uDD04\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDD6B]/g,p={"­":"shy","‌":"zwnj","‍":"zwj","‎":"lrm","âŖ":"ic","âĸ":"it","⁥":"af","‏":"rlm","​":"ZeroWidthSpace","⁠":"NoBreak","Ė‘":"DownBreve","⃛":"tdot","⃜":"DotDot","\t":"Tab","\n":"NewLine"," ":"puncsp"," ":"MediumSpace"," ":"thinsp"," ":"hairsp"," ":"emsp13"," ":"ensp"," ":"emsp14"," ":"emsp"," ":"numsp"," ":"nbsp","  ":"ThickSpace","‾":"oline",_:"lowbar","‐":"dash","–":"ndash","—":"mdash","―":"horbar",",":"comma",";":"semi","⁏":"bsemi",":":"colon","⊴":"Colone","!":"excl","ÂĄ":"iexcl","?":"quest","Âŋ":"iquest",".":"period","â€Ĩ":"nldr","â€Ļ":"mldr","¡":"middot","'":"apos","‘":"lsquo","’":"rsquo","‚":"sbquo","‹":"lsaquo","â€ē":"rsaquo",'"':"quot","“":"ldquo","”":"rdquo","„":"bdquo","ÂĢ":"laquo","Âģ":"raquo","(":"lpar",")":"rpar","[":"lsqb","]":"rsqb","{":"lcub","}":"rcub","⌈":"lceil","⌉":"rceil","⌊":"lfloor","⌋":"rfloor","âĻ…":"lopar","âφ":"ropar","âĻ‹":"lbrke","âό":"rbrke","âĻ":"lbrkslu","âĻŽ":"rbrksld","âĻ":"lbrksld","âϐ":"rbrkslu","âĻ‘":"langd","âĻ’":"rangd","âĻ“":"lparlt","âĻ”":"rpargt","âĻ•":"gtlPar","âĻ–":"ltrPar","âŸĻ":"lobrk","⟧":"robrk","⟨":"lang","⟩":"rang","âŸĒ":"Lang","âŸĢ":"Rang","âŸŦ":"loang","⟭":"roang","❲":"lbbrk","âŗ":"rbbrk","‖":"Vert","§":"sect","Âļ":"para","@":"commat","*":"ast","/":"sol",undefined:null,"&":"amp","#":"num","%":"percnt","‰":"permil","‱":"pertenk","†":"dagger","‡":"Dagger","â€ĸ":"bull","⁃":"hybull","′":"prime","â€ŗ":"Prime","‴":"tprime","⁗":"qprime","â€ĩ":"bprime","⁁":"caret","`":"grave","´":"acute","˜":"tilde","^":"Hat","¯":"macr","˘":"breve","˙":"dot","¨":"die","˚":"ring","˝":"dblac","¸":"cedil","˛":"ogon",ˆ:"circ",ˇ:"caron","°":"deg","Š":"copy","ÂŽ":"reg","℗":"copysr",℘:"wp","℞":"rx","℧":"mho","℩":"iiota","←":"larr","↚":"nlarr","→":"rarr","↛":"nrarr","↑":"uarr","↓":"darr","↔":"harr","↮":"nharr","↕":"varr","↖":"nwarr","↗":"nearr","↘":"searr","↙":"swarr","↝":"rarrw","â†Ė¸":"nrarrw","↞":"Larr","↟":"Uarr","↠":"Rarr","↡":"Darr","â†ĸ":"larrtl","â†Ŗ":"rarrtl","↤":"mapstoleft","â†Ĩ":"mapstoup","â†Ļ":"map","↧":"mapstodown","↩":"larrhk","â†Ē":"rarrhk","â†Ģ":"larrlp","â†Ŧ":"rarrlp","↭":"harrw","↰":"lsh","↱":"rsh","↲":"ldsh","â†ŗ":"rdsh","â†ĩ":"crarr","â†ļ":"cularr","↷":"curarr","â†ē":"olarr","â†ģ":"orarr","â†ŧ":"lharu","â†Ŋ":"lhard","↾":"uharr","â†ŋ":"uharl","⇀":"rharu","⇁":"rhard","⇂":"dharr","⇃":"dharl","⇄":"rlarr","⇅":"udarr","⇆":"lrarr","⇇":"llarr","⇈":"uuarr","⇉":"rrarr","⇊":"ddarr","⇋":"lrhar","⇌":"rlhar","⇐":"lArr","⇍":"nlArr","⇑":"uArr","⇒":"rArr","⇏":"nrArr","⇓":"dArr","⇔":"iff","⇎":"nhArr","⇕":"vArr","⇖":"nwArr","⇗":"neArr","⇘":"seArr","⇙":"swArr","⇚":"lAarr","⇛":"rAarr","⇝":"zigrarr","⇤":"larrb","â‡Ĩ":"rarrb","â‡ĩ":"duarr","â‡Ŋ":"loarr","⇾":"roarr","â‡ŋ":"hoarr","∀":"forall","∁":"comp","∂":"part","âˆ‚Ė¸":"npart","∃":"exist","∄":"nexist","∅":"empty","∇":"Del","∈":"in","∉":"notin","∋":"ni","∌":"notni","Īļ":"bepsi","∏":"prod","∐":"coprod","∑":"sum","+":"plus","Âą":"pm","Ãˇ":"div","×":"times","<":"lt","≮":"nlt","<⃒":"nvlt","=":"equals","≠":"ne","=âƒĨ":"bne","âŠĩ":"Equal",">":"gt","≯":"ngt",">⃒":"nvgt","ÂŦ":"not","|":"vert","ÂĻ":"brvbar","−":"minus","∓":"mp","∔":"plusdo","⁄":"frasl","∖":"setmn","∗":"lowast","∘":"compfn","√":"Sqrt","∝":"prop","∞":"infin","∟":"angrt","∠":"ang","∠⃒":"nang","∥":"angmsd","âˆĸ":"angsph","âˆŖ":"mid","∤":"nmid","âˆĨ":"par","âˆĻ":"npar","∧":"and","∨":"or","∊":"cap","âˆŠī¸€":"caps","âˆĒ":"cup","âˆĒ":"cups","âˆĢ":"int","âˆŦ":"Int","∭":"tint","⨌":"qint","∎":"oint","∯":"Conint","∰":"Cconint","∹":"cwint","∲":"cwconint","âˆŗ":"awconint","∴":"there4","âˆĩ":"becaus","âˆļ":"ratio","∡":"Colon","∸":"minusd","âˆē":"mDDot","âˆģ":"homtht","âˆŧ":"sim","≁":"nsim","âˆŧ⃒":"nvsim","âˆŊ":"bsim","âˆŊĖą":"race","∞":"ac","âˆžĖŗ":"acE","âˆŋ":"acd","≀":"wr","≂":"esim","â‰‚Ė¸":"nesim","≃":"sime","≄":"nsime","≅":"cong","≇":"ncong","≆":"simne","≈":"ap","≉":"nap","≊":"ape","≋":"apid","â‰‹Ė¸":"napid","≌":"bcong","≍":"CupCap","≭":"NotCupCap","≍⃒":"nvap","≎":"bump","â‰ŽĖ¸":"nbump","≏":"bumpe","â‰Ė¸":"nbumpe","≐":"doteq","â‰Ė¸":"nedot","≑":"eDot","≒":"efDot","≓":"erDot","≔":"colone","≕":"ecolon","≖":"ecir","≗":"cire","≙":"wedgeq","≚":"veeeq","≜":"trie","≟":"equest","≡":"equiv","â‰ĸ":"nequiv","≡âƒĨ":"bnequiv","≤":"le","≰":"nle","≤⃒":"nvle","â‰Ĩ":"ge","≱":"nge","â‰Ĩ⃒":"nvge","â‰Ļ":"lE","â‰Ļˏ":"nlE","≧":"gE","â‰§Ė¸":"ngE","â‰¨ī¸€":"lvnE","≨":"lnE","≩":"gnE","â‰Šī¸€":"gvnE","â‰Ē":"ll","â‰Ēˏ":"nLtv","â‰Ē⃒":"nLt","â‰Ģ":"gg","â‰Ģˏ":"nGtv","â‰Ģ⃒":"nGt","â‰Ŧ":"twixt","≲":"lsim","≴":"nlsim","â‰ŗ":"gsim","â‰ĩ":"ngsim","â‰ļ":"lg","≸":"ntlg","≷":"gl","≹":"ntgl","â‰ē":"pr","⊀":"npr","â‰ģ":"sc","⊁":"nsc","â‰ŧ":"prcue","⋠":"nprcue","â‰Ŋ":"sccue","⋡":"nsccue","≾":"prsim","â‰ŋ":"scsim","â‰ŋˏ":"NotSucceedsTilde","⊂":"sub","⊄":"nsub","⊂⃒":"vnsub","⊃":"sup","⊅":"nsup","⊃⃒":"vnsup","⊆":"sube","⊈":"nsube","⊇":"supe","⊉":"nsupe","âŠŠī¸€":"vsubne","⊊":"subne","âŠ‹ī¸€":"vsupne","⊋":"supne","⊍":"cupdot","⊎":"uplus","⊏":"sqsub","âŠĖ¸":"NotSquareSubset","⊐":"sqsup","âŠĖ¸":"NotSquareSuperset","⊑":"sqsube","â‹ĸ":"nsqsube","⊒":"sqsupe","â‹Ŗ":"nsqsupe","⊓":"sqcap","âŠ“ī¸€":"sqcaps","⊔":"sqcup","âŠ”ī¸€":"sqcups","⊕":"oplus","⊖":"ominus","⊗":"otimes","⊘":"osol","⊙":"odot","⊚":"ocir","⊛":"oast","⊝":"odash","⊞":"plusb","⊟":"minusb","⊠":"timesb","⊡":"sdotb","âŠĸ":"vdash","âŠŦ":"nvdash","âŠŖ":"dashv","⊤":"top","âŠĨ":"bot","⊧":"models","⊨":"vDash","⊭":"nvDash","⊩":"Vdash","⊮":"nVdash","âŠĒ":"Vvdash","âŠĢ":"VDash","⊯":"nVDash","⊰":"prurel","⊲":"vltri","â‹Ē":"nltri","âŠŗ":"vrtri","â‹Ģ":"nrtri","⊴":"ltrie","â‹Ŧ":"nltrie","⊴⃒":"nvltrie","âŠĩ":"rtrie","⋭":"nrtrie","âŠĩ⃒":"nvrtrie","âŠļ":"origof","⊷":"imof","⊸":"mumap","⊹":"hercon","âŠē":"intcal","âŠģ":"veebar","âŠŊ":"barvee","⊾":"angrtvb","âŠŋ":"lrtri","⋀":"Wedge","⋁":"Vee","⋂":"xcap","⋃":"xcup","⋄":"diam","⋅":"sdot","⋆":"Star","⋇":"divonx","⋈":"bowtie","⋉":"ltimes","⋊":"rtimes","⋋":"lthree","⋌":"rthree","⋍":"bsime","⋎":"cuvee","⋏":"cuwed","⋐":"Sub","⋑":"Sup","⋒":"Cap","⋓":"Cup","⋔":"fork","⋕":"epar","⋖":"ltdot","⋗":"gtdot","⋘":"Ll","â‹˜Ė¸":"nLl","⋙":"Gg","â‹™Ė¸":"nGg","â‹šī¸€":"lesg","⋚":"leg","⋛":"gel","â‹›ī¸€":"gesl","⋞":"cuepr","⋟":"cuesc","â‹Ļ":"lnsim","⋧":"gnsim","⋨":"prnsim","⋩":"scnsim","⋮":"vellip","⋯":"ctdot","⋰":"utdot","⋱":"dtdot","⋲":"disin","â‹ŗ":"isinsv","⋴":"isins","â‹ĩ":"isindot","â‹ĩˏ":"notindot","â‹ļ":"notinvc","⋷":"notinvb","⋹":"isinE","â‹šĖ¸":"notinE","â‹ē":"nisd","â‹ģ":"xnis","â‹ŧ":"nis","â‹Ŋ":"notnivc","⋾":"notnivb","⌅":"barwed","⌆":"Barwed","⌌":"drcrop","⌍":"dlcrop","⌎":"urcrop","⌏":"ulcrop","⌐":"bnot","⌒":"profline","⌓":"profsurf","⌕":"telrec","⌖":"target","⌜":"ulcorn","⌝":"urcorn","⌞":"dlcorn","⌟":"drcorn","âŒĸ":"frown","âŒŖ":"smile","⌭":"cylcty","⌮":"profalar","âŒļ":"topbot","âŒŊ":"ovbar","âŒŋ":"solbar","âŧ":"angzarr","⎰":"lmoust","⎱":"rmoust","⎴":"tbrk","âŽĩ":"bbrk","âŽļ":"bbrktbrk","⏜":"OverParenthesis","⏝":"UnderParenthesis","⏞":"OverBrace","⏟":"UnderBrace","âĸ":"trpezium","⏧":"elinters","âŖ":"blank","─":"boxh","│":"boxv","┌":"boxdr","┐":"boxdl","└":"boxur","┘":"boxul","├":"boxvr","┤":"boxvl","â”Ŧ":"boxhd","┴":"boxhu","â”ŧ":"boxvh","═":"boxH","║":"boxV","╒":"boxdR","╓":"boxDr","╔":"boxDR","╕":"boxdL","╖":"boxDl","╗":"boxDL","╘":"boxuR","╙":"boxUr","╚":"boxUR","╛":"boxuL","╜":"boxUl","╝":"boxUL","╞":"boxvR","╟":"boxVr","╠":"boxVR","╡":"boxvL","â•ĸ":"boxVl","â•Ŗ":"boxVL","╤":"boxHd","â•Ĩ":"boxhD","â•Ļ":"boxHD","╧":"boxHu","╨":"boxhU","╩":"boxHU","â•Ē":"boxvH","â•Ģ":"boxVh","â•Ŧ":"boxVH","▀":"uhblk","▄":"lhblk","█":"block","░":"blk14","▒":"blk12","▓":"blk34","□":"squ","â–Ē":"squf","â–Ģ":"EmptyVerySmallSquare","▭":"rect","▮":"marker","▱":"fltns","â–ŗ":"xutri","▴":"utrif","â–ĩ":"utri","▸":"rtrif","▹":"rtri","â–Ŋ":"xdtri","▾":"dtrif","â–ŋ":"dtri","◂":"ltrif","◃":"ltri","◊":"loz","○":"cir","â—Ŧ":"tridot","◯":"xcirc","◸":"ultri","◹":"urtri","â—ē":"lltri","â—ģ":"EmptySmallSquare","â—ŧ":"FilledSmallSquare","★":"starf","☆":"star","☎":"phone","♀":"female","♂":"male","♠":"spades","â™Ŗ":"clubs","â™Ĩ":"hearts","â™Ļ":"diams","â™Ē":"sung","✓":"check","✗":"cross","✠":"malt","âœļ":"sext","❘":"VerticalSeparator","⟈":"bsolhsub","⟉":"suphsol","âŸĩ":"xlarr","âŸļ":"xrarr","⟷":"xharr","⟸":"xlArr","⟹":"xrArr","âŸē":"xhArr","âŸŧ":"xmap","âŸŋ":"dzigrarr","⤂":"nvlArr","⤃":"nvrArr","⤄":"nvHarr","⤅":"Map","⤌":"lbarr","⤍":"rbarr","⤎":"lBarr","⤏":"rBarr","⤐":"RBarr","⤑":"DDotrahd","⤒":"UpArrowBar","⤓":"DownArrowBar","⤖":"Rarrtl","⤙":"latail","⤚":"ratail","⤛":"lAtail","⤜":"rAtail","⤝":"larrfs","⤞":"rarrfs","⤟":"larrbfs","⤠":"rarrbfs","â¤Ŗ":"nwarhk","⤤":"nearhk","â¤Ĩ":"searhk","â¤Ļ":"swarhk","⤧":"nwnear","⤨":"toea","⤊":"tosa","â¤Ē":"swnwar","â¤ŗ":"rarrc","â¤ŗĖ¸":"nrarrc","â¤ĩ":"cudarrr","â¤ļ":"ldca","⤡":"rdca","⤸":"cudarrl","⤚":"larrpl","â¤ŧ":"curarrm","â¤Ŋ":"cularrp","âĨ…":"rarrpl","âĨˆ":"harrcir","âĨ‰":"Uarrocir","âĨŠ":"lurdshar","âĨ‹":"ldrushar","âĨŽ":"LeftRightVector","âĨ":"RightUpDownVector","âĨ":"DownLeftRightVector","âĨ‘":"LeftUpDownVector","âĨ’":"LeftVectorBar","âĨ“":"RightVectorBar","âĨ”":"RightUpVectorBar","âĨ•":"RightDownVectorBar","âĨ–":"DownLeftVectorBar","âĨ—":"DownRightVectorBar","âĨ˜":"LeftUpVectorBar","âĨ™":"LeftDownVectorBar","âĨš":"LeftTeeVector","âĨ›":"RightTeeVector","âĨœ":"RightUpTeeVector","âĨ":"RightDownTeeVector","âĨž":"DownLeftTeeVector","âĨŸ":"DownRightTeeVector","âĨ ":"LeftUpTeeVector","âĨĄ":"LeftDownTeeVector","âĨĸ":"lHar","âĨŖ":"uHar","âĨ¤":"rHar","âĨĨ":"dHar","âĨĻ":"luruhar","âĨ§":"ldrdhar","âĨ¨":"ruluhar","âĨŠ":"rdldhar","âĨĒ":"lharul","âĨĢ":"llhard","âĨŦ":"rharul","âĨ­":"lrhard","âĨŽ":"udhar","âĨ¯":"duhar","âĨ°":"RoundImplies","âĨą":"erarr","âĨ˛":"simrarr","âĨŗ":"larrsim","âĨ´":"rarrsim","âĨĩ":"rarrap","âĨļ":"ltlarr","âĨ¸":"gtrarr","âĨš":"subrarr","âĨģ":"suplarr","âĨŧ":"lfisht","âĨŊ":"rfisht","âĨž":"ufisht","âĨŋ":"dfisht","âϚ":"vzigzag","âϜ":"vangrt","âĻ":"angrtvbd","âϤ":"ange","âĻĨ":"range","âĻĻ":"dwangle","âϧ":"uwangle","âύ":"angmsdaa","âĻŠ":"angmsdab","âĻĒ":"angmsdac","âĻĢ":"angmsdad","âĻŦ":"angmsdae","âĻ­":"angmsdaf","âĻŽ":"angmsdag","âϝ":"angmsdah","âϰ":"bemptyv","âĻą":"demptyv","âϞ":"cemptyv","âĻŗ":"raemptyv","âĻ´":"laemptyv","âĻĩ":"ohbar","âĻļ":"omid","âώ":"opar","âĻš":"operp","âĻģ":"olcross","âĻŧ":"odsold","âĻž":"olcir","âĻŋ":"ofcir","⧀":"olt","⧁":"ogt","⧂":"cirscir","⧃":"cirE","⧄":"solb","⧅":"bsolb","⧉":"boxbox","⧍":"trisb","⧎":"rtriltri","⧏":"LeftTriangleBar","â§Ė¸":"NotLeftTriangleBar","⧐":"RightTriangleBar","â§Ė¸":"NotRightTriangleBar","⧜":"iinfin","⧝":"infintie","⧞":"nvinfin","â§Ŗ":"eparsl","⧤":"smeparsl","â§Ĩ":"eqvparsl","â§Ģ":"lozf","â§´":"RuleDelayed","â§ļ":"dsol","⨀":"xodot","⨁":"xoplus","⨂":"xotime","⨄":"xuplus","⨆":"xsqcup","⨍":"fpartint","⨐":"cirfnint","⨑":"awint","⨒":"rppolint","⨓":"scpolint","⨔":"npolint","⨕":"pointint","⨖":"quatint","⨗":"intlarhk","â¨ĸ":"pluscir","â¨Ŗ":"plusacir","⨤":"simplus","â¨Ĩ":"plusdu","â¨Ļ":"plussim","⨧":"plustwo","⨊":"mcomma","â¨Ē":"minusdu","⨭":"loplus","⨎":"roplus","⨯":"Cross","⨰":"timesd","⨹":"timesbar","â¨ŗ":"smashp","⨴":"lotimes","â¨ĩ":"rotimes","â¨ļ":"otimesas","⨡":"Otimes","⨸":"odiv","⨚":"triplus","â¨ē":"triminus","â¨ģ":"tritime","â¨ŧ":"iprod","â¨ŋ":"amalg","⩀":"capdot","⩂":"ncup","⊃":"ncap","⩄":"capand","⩅":"cupor","⩆":"cupcap","⩇":"capcup","⊈":"cupbrcap","⩉":"capbrcup","⩊":"cupcup","⩋":"capcap","⩌":"ccups","⊍":"ccaps","⊐":"ccupssm","⩓":"And","⩔":"Or","⩕":"andand","⩖":"oror","⩗":"orslope","⊘":"andslope","⩚":"andv","⩛":"orv","⩜":"andd","⊝":"ord","⩟":"wedbar","âŠĻ":"sdote","âŠĒ":"simdot","⊭":"congdot","âŠ­Ė¸":"ncongdot","⊎":"easter","⊯":"apacir","⊰":"apE","âŠ°Ė¸":"napE","⊹":"eplus","⊲":"pluse","âŠŗ":"Esim","⊡":"eDDot","⊸":"equivDD","⊚":"ltcir","âŠē":"gtcir","âŠģ":"ltquest","âŠŧ":"gtquest","âŠŊ":"les","âŠŊˏ":"nles","⊞":"ges","âŠžĖ¸":"nges","âŠŋ":"lesdot","âĒ€":"gesdot","âǁ":"lesdoto","âĒ‚":"gesdoto","âǃ":"lesdotor","âĒ„":"gesdotol","âĒ…":"lap","âdž":"gap","âLJ":"lne","âLj":"gne","âlj":"lnap","âNJ":"gnap","âĒ‹":"lEg","ânj":"gEl","âĒ":"lsime","âĒŽ":"gsime","âĒ":"lsimg","âǐ":"gsiml","âĒ‘":"lgE","âĒ’":"glE","âĒ“":"lesges","âĒ”":"gesles","âĒ•":"els","âĒ–":"egs","âĒ—":"elsdot","âǘ":"egsdot","âĒ™":"el","âǚ":"eg","âĒ":"siml","âĒž":"simg","âǟ":"simlE","âĒ ":"simgE","âĒĄ":"LessLess","âĒĄĖ¸":"NotNestedLessLess","âĒĸ":"GreaterGreater","âĒĸˏ":"NotNestedGreaterGreater","âǤ":"glj","âĒĨ":"gla","âĒĻ":"ltcc","âǧ":"gtcc","âǍ":"lescc","âĒŠ":"gescc","âĒĒ":"smt","âĒĢ":"lat","âĒŦ":"smte","âĒŦ":"smtes","âĒ­":"late","âǭ":"lates","âĒŽ":"bumpE","âǝ":"pre","âǝˏ":"npre","âǰ":"sce","âǰˏ":"nsce","âĒŗ":"prE","âĒ´":"scE","âĒĩ":"prnE","âĒļ":"scnE","âǎ":"prap","âǏ":"scap","âĒš":"prnap","âĒē":"scnap","âĒģ":"Pr","âĒŧ":"Sc","âĒŊ":"subdot","âĒž":"supdot","âĒŋ":"subplus","âĢ€":"supplus","ấ":"submult","âĢ‚":"supmult","ẫ":"subedot","âĢ„":"supedot","âĢ…":"subE","â̅ˏ":"nsubE","â̆":"supE","â̆ˏ":"nsupE","â̇":"subsim","â̈":"supsim","â̋":"vsubnE","âĢ‹":"subnE","âĢŒī¸€":"vsupnE","â̌":"supnE","âĢ":"csub","â̐":"csup","âĢ‘":"csube","âĢ’":"csupe","âĢ“":"subsup","âĢ”":"supsub","âĢ•":"subsub","âĢ–":"supsup","âĢ—":"suphsub","â̘":"supdsub","âĢ™":"forkv","â̚":"topfork","âĢ›":"mlcp","â̤":"Dashv","âĢĻ":"Vdashl","â̧":"Barv","â̍":"vBar","âĢŠ":"vBarv","âĢĢ":"Vbar","âĢŦ":"Not","âĢ­":"bNot","âĢŽ":"rnmid","â̝":"cirmid","â̰":"midcir","âĢą":"topcir","â̞":"nhpar","âĢŗ":"parsim","âĢŊ":"parsl","âĢŊâƒĨ":"nparsl","♭":"flat","♮":"natur","♯":"sharp","¤":"curren","Âĸ":"cent",$:"dollar","ÂŖ":"pound","ÂĨ":"yen","â‚Ŧ":"euro","š":"sup1","ÂŊ":"half","⅓":"frac13","Âŧ":"frac14","⅕":"frac15","⅙":"frac16","⅛":"frac18","²":"sup2","⅔":"frac23","⅖":"frac25","Âŗ":"sup3","ž":"frac34","⅗":"frac35","⅜":"frac38","⅘":"frac45","⅚":"frac56","⅝":"frac58","⅞":"frac78",đ’ļ:"ascr",𝕒:"aopf",𝔞:"afr",𝔸:"Aopf",𝔄:"Afr",𝒜:"Ascr",ÂĒ:"ordf",ÃĄ:"aacute",Á:"Aacute",à:"agrave",À:"Agrave",ă:"abreve",Ă:"Abreve",Ãĸ:"acirc",Â:"Acirc",ÃĨ:"aring",Å:"angst",ä:"auml",Ä:"Auml",ÃŖ:"atilde",Ã:"Atilde",ą:"aogon",Ą:"Aogon",ā:"amacr",Ā:"Amacr",ÃĻ:"aelig",Æ:"AElig",𝒷:"bscr",𝕓:"bopf",𝔟:"bfr",𝔹:"Bopf",â„Ŧ:"Bscr",𝔅:"Bfr",𝔠:"cfr",𝒸:"cscr",𝕔:"copf",ℭ:"Cfr",𝒞:"Cscr",ℂ:"Copf",ć:"cacute",Ć:"Cacute",ĉ:"ccirc",Ĉ:"Ccirc",č:"ccaron",Č:"Ccaron",ċ:"cdot",Ċ:"Cdot",ç:"ccedil",Ç:"Ccedil","℅":"incare",𝔡:"dfr",ⅆ:"dd",𝕕:"dopf",𝒹:"dscr",𝒟:"Dscr",𝔇:"Dfr",ⅅ:"DD",đ”ģ:"Dopf",ď:"dcaron",Ď:"Dcaron",đ:"dstrok",Đ:"Dstrok",ð:"eth",Ð:"ETH",ⅇ:"ee",ℯ:"escr",đ”ĸ:"efr",𝕖:"eopf",ℰ:"Escr",𝔈:"Efr",đ”ŧ:"Eopf",Ê:"eacute",É:"Eacute",è:"egrave",È:"Egrave",ÃĒ:"ecirc",Ê:"Ecirc",ě:"ecaron",Ě:"Ecaron",ÃĢ:"euml",Ë:"Euml",ė:"edot",Ė:"Edot",ę:"eogon",Ę:"Eogon",ē:"emacr",Ē:"Emacr",đ”Ŗ:"ffr",𝕗:"fopf",đ’ģ:"fscr",𝔉:"Ffr",đ”Ŋ:"Fopf",ℱ:"Fscr",īŦ€:"fflig",īŦƒ:"ffilig",īŦ„:"ffllig",īŦ:"filig",fj:"fjlig",īŦ‚:"fllig",ƒ:"fnof",ℊ:"gscr",𝕘:"gopf",𝔤:"gfr",đ’ĸ:"Gscr",𝔾:"Gopf",𝔊:"Gfr",Įĩ:"gacute",ğ:"gbreve",Ğ:"Gbreve",ĝ:"gcirc",Ĝ:"Gcirc",ÄĄ:"gdot",Ä :"Gdot",Äĸ:"Gcedil",đ”Ĩ:"hfr",ℎ:"planckh",đ’Ŋ:"hscr",𝕙:"hopf",ℋ:"Hscr",ℌ:"Hfr",ℍ:"Hopf",ÄĨ:"hcirc",Ĥ:"Hcirc",ℏ:"hbar",ħ:"hstrok",ÄĻ:"Hstrok",𝕚:"iopf",đ”Ļ:"ifr",𝒾:"iscr",ⅈ:"ii",𝕀:"Iopf",ℐ:"Iscr",ℑ:"Im",í:"iacute",Í:"Iacute",ÃŦ:"igrave",Ì:"Igrave",ÃŽ:"icirc",Î:"Icirc",ï:"iuml",Ï:"Iuml",ÄŠ:"itilde",Ĩ:"Itilde",İ:"Idot",į:"iogon",ÄŽ:"Iogon",ÄĢ:"imacr",ÄĒ:"Imacr",Äŗ:"ijlig",IJ:"IJlig",Äą:"imath",đ’ŋ:"jscr",𝕛:"jopf",𝔧:"jfr",đ’Ĩ:"Jscr",𝔍:"Jfr",𝕁:"Jopf",Äĩ:"jcirc",Ä´:"Jcirc",ȡ:"jmath",𝕜:"kopf",𝓀:"kscr",𝔨:"kfr",đ’Ļ:"Kscr",𝕂:"Kopf",𝔎:"Kfr",ġ:"kcedil",Äļ:"Kcedil",𝔩:"lfr",𝓁:"lscr",ℓ:"ell",𝕝:"lopf",ℒ:"Lscr",𝔏:"Lfr",𝕃:"Lopf",Äē:"lacute",Äš:"Lacute",Äž:"lcaron",ÄŊ:"Lcaron",Äŧ:"lcedil",Äģ:"Lcedil",ł:"lstrok",Ł:"Lstrok",ŀ:"lmidot",Äŋ:"Lmidot",đ”Ē:"mfr",𝕞:"mopf",𝓂:"mscr",𝔐:"Mfr",𝕄:"Mopf",â„ŗ:"Mscr",đ”Ģ:"nfr",𝕟:"nopf",𝓃:"nscr",ℕ:"Nopf",𝒩:"Nscr",𝔑:"Nfr",ń:"nacute",Ń:"Nacute",ň:"ncaron",Ň:"Ncaron",Ãą:"ntilde",Ñ:"Ntilde",ņ:"ncedil",Ņ:"Ncedil","№":"numero",ŋ:"eng",Ŋ:"ENG",𝕠:"oopf",đ”Ŧ:"ofr",ℴ:"oscr",đ’Ē:"Oscr",𝔒:"Ofr",𝕆:"Oopf",Âē:"ordm",Ãŗ:"oacute",Ó:"Oacute",Ã˛:"ograve",Ò:"Ograve",ô:"ocirc",Ô:"Ocirc",Ãļ:"ouml",Ö:"Ouml",ő:"odblac",Ő:"Odblac",Ãĩ:"otilde",Õ:"Otilde",ø:"oslash",Ø:"Oslash",ō:"omacr",Ō:"Omacr",œ:"oelig",Œ:"OElig",𝔭:"pfr",𝓅:"pscr",𝕡:"popf",ℙ:"Popf",𝔓:"Pfr",đ’Ģ:"Pscr",đ•ĸ:"qopf",𝔮:"qfr",𝓆:"qscr",đ’Ŧ:"Qscr",𝔔:"Qfr",ℚ:"Qopf",ĸ:"kgreen",đ”¯:"rfr",đ•Ŗ:"ropf",𝓇:"rscr",ℛ:"Rscr",ℜ:"Re",ℝ:"Ropf",ŕ:"racute",Ŕ:"Racute",ř:"rcaron",Ř:"Rcaron",ŗ:"rcedil",Ŗ:"Rcedil",𝕤:"sopf",𝓈:"sscr",𝔰:"sfr",𝕊:"Sopf",𝔖:"Sfr",𝒮:"Sscr","Ⓢ":"oS",ś:"sacute",Ś:"Sacute",ŝ:"scirc",Ŝ:"Scirc",ÅĄ:"scaron",Å :"Scaron",ş:"scedil",Ş:"Scedil",ß:"szlig",𝔱:"tfr",𝓉:"tscr",đ•Ĩ:"topf",đ’¯:"Tscr",𝔗:"Tfr",𝕋:"Topf",ÅĨ:"tcaron",Ť:"Tcaron",ÅŖ:"tcedil",Åĸ:"Tcedil","â„ĸ":"trade",ŧ:"tstrok",ÅĻ:"Tstrok",𝓊:"uscr",đ•Ļ:"uopf",𝔲:"ufr",𝕌:"Uopf",𝔘:"Ufr",𝒰:"Uscr",Ãē:"uacute",Ú:"Uacute",Ú:"ugrave",Ù:"Ugrave",Å­:"ubreve",ÅŦ:"Ubreve",Ãģ:"ucirc",Û:"Ucirc",ů:"uring",ÅŽ:"Uring",Ãŧ:"uuml",Ü:"Uuml",Åą:"udblac",Ű:"Udblac",ÅŠ:"utilde",Ũ:"Utilde",Åŗ:"uogon",Ş:"Uogon",ÅĢ:"umacr",ÅĒ:"Umacr",đ”ŗ:"vfr",𝕧:"vopf",𝓋:"vscr",𝔙:"Vfr",𝕍:"Vopf",𝒱:"Vscr",𝕨:"wopf",𝓌:"wscr",𝔴:"wfr",𝒲:"Wscr",𝕎:"Wopf",𝔚:"Wfr",Åĩ:"wcirc",Å´:"Wcirc",đ”ĩ:"xfr",𝓍:"xscr",𝕩:"xopf",𝕏:"Xopf",𝔛:"Xfr",đ’ŗ:"Xscr",đ”ļ:"yfr",𝓎:"yscr",đ•Ē:"yopf",𝒴:"Yscr",𝔜:"Yfr",𝕐:"Yopf",ÃŊ:"yacute",Ý:"Yacute",Ŏ:"ycirc",Åļ:"Ycirc",Ãŋ:"yuml",Ÿ:"Yuml",𝓏:"zscr",𝔷:"zfr",đ•Ģ:"zopf",ℨ:"Zfr",ℤ:"Zopf",đ’ĩ:"Zscr",Åē:"zacute",Åš:"Zacute",Åž:"zcaron",ÅŊ:"Zcaron",Åŧ:"zdot",Åģ:"Zdot",Æĩ:"imped",Þ:"thorn",Þ:"THORN",ʼn:"napos",Îą:"alpha",Α:"Alpha",β:"beta",Β:"Beta",Îŗ:"gamma",Γ:"Gamma",δ:"delta",Δ:"Delta",Îĩ:"epsi",Īĩ:"epsiv",Ε:"Epsilon",Ī:"gammad",Μ:"Gammad",Îļ:"zeta",Ζ:"Zeta",Ρ:"eta",Η:"Eta",θ:"theta",Ī‘:"thetav",Θ:"Theta",Κ:"iota",Ι:"Iota",Îē:"kappa",ΰ:"kappav",Κ:"Kappa",Îģ:"lambda",Λ:"Lambda",Îŧ:"mu",Âĩ:"micro",Μ:"Mu",ÎŊ:"nu",Ν:"Nu",Ξ:"xi",Ξ:"Xi",Îŋ:"omicron",Ο:"Omicron",Ī€:"pi",Ī–:"piv",Π:"Pi",΁:"rho",Īą:"rhov",ÎĄ:"Rho",΃:"sigma",ÎŖ:"Sigma",Ī‚:"sigmaf",Ī„:"tau",Τ:"Tau",Ī…:"upsi",ÎĨ:"Upsilon",Ī’:"Upsi",Ά:"phi",Ī•:"phiv",ÎĻ:"Phi",·:"chi",Χ:"Chi",Έ:"psi",Ψ:"Psi",Ή:"omega",Ί:"ohm",а:"acy",А:"Acy",Đą:"bcy",Б:"Bcy",в:"vcy",В:"Vcy",Đŗ:"gcy",Г:"Gcy",Ņ“:"gjcy",Ѓ:"GJcy",Đ´:"dcy",Д:"Dcy",Ņ’:"djcy",Ђ:"DJcy",Đĩ:"iecy",Е:"IEcy",Ņ‘:"iocy",Ё:"IOcy",Ņ”:"jukcy",Є:"Jukcy",Đļ:"zhcy",Ж:"ZHcy",С:"zcy",З:"Zcy",Ņ•:"dscy",Ѕ:"DScy",и:"icy",И:"Icy",Ņ–:"iukcy",І:"Iukcy",Ņ—:"yicy",Ї:"YIcy",Đš:"jcy",Й:"Jcy",Ҙ:"jsercy",Ј:"Jsercy",Đē:"kcy",К:"Kcy",Ҝ:"kjcy",Ќ:"KJcy",Đģ:"lcy",Л:"Lcy",Ņ™:"ljcy",Љ:"LJcy",Đŧ:"mcy",М:"Mcy",ĐŊ:"ncy",Н:"Ncy",Қ:"njcy",Њ:"NJcy",Đž:"ocy",О:"Ocy",Đŋ:"pcy",П:"Pcy",Ņ€:"rcy",Đ :"Rcy",ҁ:"scy",ĐĄ:"Scy",Ņ‚:"tcy",Đĸ:"Tcy",Ņ›:"tshcy",Ћ:"TSHcy",҃:"ucy",ĐŖ:"Ucy",Ņž:"ubrcy",Ў:"Ubrcy",Ņ„:"fcy",Ф:"Fcy",Ņ…:"khcy",ĐĨ:"KHcy",҆:"tscy",ĐĻ:"TScy",҇:"chcy",Ч:"CHcy",ҟ:"dzcy",Џ:"DZcy",҈:"shcy",Ш:"SHcy",҉:"shchcy",ĐŠ:"SHCHcy",Ҋ:"hardcy",ĐĒ:"HARDcy",Ņ‹:"ycy",ĐĢ:"Ycy",Ҍ:"softcy",ĐŦ:"SOFTcy",Ņ:"ecy",Đ­:"Ecy",ŅŽ:"yucy",ĐŽ:"YUcy",Ņ:"yacy",Đ¯:"YAcy",â„ĩ:"aleph",â„ļ:"beth",ℷ:"gimel",ℸ:"daleth"},f=/["&'<>`]/g,d={'"':""","&":"&","'":"'","<":"<",">":">","`":"`"},h=/&#(?:[xX][^a-fA-F0-9]|[^0-9xX])/,g=/[\0-\x08\x0B\x0E-\x1F\x7F-\x9F\uFDD0-\uFDEF\uFFFE\uFFFF]|[\uD83F\uD87F\uD8BF\uD8FF\uD93F\uD97F\uD9BF\uD9FF\uDA3F\uDA7F\uDABF\uDAFF\uDB3F\uDB7F\uDBBF\uDBFF][\uDFFE\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/,m=/&(CounterClockwiseContourIntegral|DoubleLongLeftRightArrow|ClockwiseContourIntegral|NotNestedGreaterGreater|NotSquareSupersetEqual|DiacriticalDoubleAcute|NotRightTriangleEqual|NotSucceedsSlantEqual|NotPrecedesSlantEqual|CloseCurlyDoubleQuote|NegativeVeryThinSpace|DoubleContourIntegral|FilledVerySmallSquare|CapitalDifferentialD|OpenCurlyDoubleQuote|EmptyVerySmallSquare|NestedGreaterGreater|DoubleLongRightArrow|NotLeftTriangleEqual|NotGreaterSlantEqual|ReverseUpEquilibrium|DoubleLeftRightArrow|NotSquareSubsetEqual|NotDoubleVerticalBar|RightArrowLeftArrow|NotGreaterFullEqual|NotRightTriangleBar|SquareSupersetEqual|DownLeftRightVector|DoubleLongLeftArrow|leftrightsquigarrow|LeftArrowRightArrow|NegativeMediumSpace|blacktriangleright|RightDownVectorBar|PrecedesSlantEqual|RightDoubleBracket|SucceedsSlantEqual|NotLeftTriangleBar|RightTriangleEqual|SquareIntersection|RightDownTeeVector|ReverseEquilibrium|NegativeThickSpace|longleftrightarrow|Longleftrightarrow|LongLeftRightArrow|DownRightTeeVector|DownRightVectorBar|GreaterSlantEqual|SquareSubsetEqual|LeftDownVectorBar|LeftDoubleBracket|VerticalSeparator|rightleftharpoons|NotGreaterGreater|NotSquareSuperset|blacktriangleleft|blacktriangledown|NegativeThinSpace|LeftDownTeeVector|NotLessSlantEqual|leftrightharpoons|DoubleUpDownArrow|DoubleVerticalBar|LeftTriangleEqual|FilledSmallSquare|twoheadrightarrow|NotNestedLessLess|DownLeftTeeVector|DownLeftVectorBar|RightAngleBracket|NotTildeFullEqual|NotReverseElement|RightUpDownVector|DiacriticalTilde|NotSucceedsTilde|circlearrowright|NotPrecedesEqual|rightharpoondown|DoubleRightArrow|NotSucceedsEqual|NonBreakingSpace|NotRightTriangle|LessEqualGreater|RightUpTeeVector|LeftAngleBracket|GreaterFullEqual|DownArrowUpArrow|RightUpVectorBar|twoheadleftarrow|GreaterEqualLess|downharpoonright|RightTriangleBar|ntrianglerighteq|NotSupersetEqual|LeftUpDownVector|DiacriticalAcute|rightrightarrows|vartriangleright|UpArrowDownArrow|DiacriticalGrave|UnderParenthesis|EmptySmallSquare|LeftUpVectorBar|leftrightarrows|DownRightVector|downharpoonleft|trianglerighteq|ShortRightArrow|OverParenthesis|DoubleLeftArrow|DoubleDownArrow|NotSquareSubset|bigtriangledown|ntrianglelefteq|UpperRightArrow|curvearrowright|vartriangleleft|NotLeftTriangle|nleftrightarrow|LowerRightArrow|NotHumpDownHump|NotGreaterTilde|rightthreetimes|LeftUpTeeVector|NotGreaterEqual|straightepsilon|LeftTriangleBar|rightsquigarrow|ContourIntegral|rightleftarrows|CloseCurlyQuote|RightDownVector|LeftRightVector|nLeftrightarrow|leftharpoondown|circlearrowleft|SquareSuperset|OpenCurlyQuote|hookrightarrow|HorizontalLine|DiacriticalDot|NotLessGreater|ntriangleright|DoubleRightTee|InvisibleComma|InvisibleTimes|LowerLeftArrow|DownLeftVector|NotSubsetEqual|curvearrowleft|trianglelefteq|NotVerticalBar|TildeFullEqual|downdownarrows|NotGreaterLess|RightTeeVector|ZeroWidthSpace|looparrowright|LongRightArrow|doublebarwedge|ShortLeftArrow|ShortDownArrow|RightVectorBar|GreaterGreater|ReverseElement|rightharpoonup|LessSlantEqual|leftthreetimes|upharpoonright|rightarrowtail|LeftDownVector|Longrightarrow|NestedLessLess|UpperLeftArrow|nshortparallel|leftleftarrows|leftrightarrow|Leftrightarrow|LeftRightArrow|longrightarrow|upharpoonleft|RightArrowBar|ApplyFunction|LeftTeeVector|leftarrowtail|NotEqualTilde|varsubsetneqq|varsupsetneqq|RightTeeArrow|SucceedsEqual|SucceedsTilde|LeftVectorBar|SupersetEqual|hookleftarrow|DifferentialD|VerticalTilde|VeryThinSpace|blacktriangle|bigtriangleup|LessFullEqual|divideontimes|leftharpoonup|UpEquilibrium|ntriangleleft|RightTriangle|measuredangle|shortparallel|longleftarrow|Longleftarrow|LongLeftArrow|DoubleLeftTee|Poincareplane|PrecedesEqual|triangleright|DoubleUpArrow|RightUpVector|fallingdotseq|looparrowleft|PrecedesTilde|NotTildeEqual|NotTildeTilde|smallsetminus|Proportional|triangleleft|triangledown|UnderBracket|NotHumpEqual|exponentiale|ExponentialE|NotLessTilde|HilbertSpace|RightCeiling|blacklozenge|varsupsetneq|HumpDownHump|GreaterEqual|VerticalLine|LeftTeeArrow|NotLessEqual|DownTeeArrow|LeftTriangle|varsubsetneq|Intersection|NotCongruent|DownArrowBar|LeftUpVector|LeftArrowBar|risingdotseq|GreaterTilde|RoundImplies|SquareSubset|ShortUpArrow|NotSuperset|quaternions|precnapprox|backepsilon|preccurlyeq|OverBracket|blacksquare|MediumSpace|VerticalBar|circledcirc|circleddash|CircleMinus|CircleTimes|LessGreater|curlyeqprec|curlyeqsucc|diamondsuit|UpDownArrow|Updownarrow|RuleDelayed|Rrightarrow|updownarrow|RightVector|nRightarrow|nrightarrow|eqslantless|LeftCeiling|Equilibrium|SmallCircle|expectation|NotSucceeds|thickapprox|GreaterLess|SquareUnion|NotPrecedes|NotLessLess|straightphi|succnapprox|succcurlyeq|SubsetEqual|sqsupseteq|Proportion|Laplacetrf|ImaginaryI|supsetneqq|NotGreater|gtreqqless|NotElement|ThickSpace|TildeEqual|TildeTilde|Fouriertrf|rmoustache|EqualTilde|eqslantgtr|UnderBrace|LeftVector|UpArrowBar|nLeftarrow|nsubseteqq|subsetneqq|nsupseteqq|nleftarrow|succapprox|lessapprox|UpTeeArrow|upuparrows|curlywedge|lesseqqgtr|varepsilon|varnothing|RightFloor|complement|CirclePlus|sqsubseteq|Lleftarrow|circledast|RightArrow|Rightarrow|rightarrow|lmoustache|Bernoullis|precapprox|mapstoleft|mapstodown|longmapsto|dotsquare|downarrow|DoubleDot|nsubseteq|supsetneq|leftarrow|nsupseteq|subsetneq|ThinSpace|ngeqslant|subseteqq|HumpEqual|NotSubset|triangleq|NotCupCap|lesseqgtr|heartsuit|TripleDot|Leftarrow|Coproduct|Congruent|varpropto|complexes|gvertneqq|LeftArrow|LessTilde|supseteqq|MinusPlus|CircleDot|nleqslant|NotExists|gtreqless|nparallel|UnionPlus|LeftFloor|checkmark|CenterDot|centerdot|Mellintrf|gtrapprox|bigotimes|OverBrace|spadesuit|therefore|pitchfork|rationals|PlusMinus|Backslash|Therefore|DownBreve|backsimeq|backprime|DownArrow|nshortmid|Downarrow|lvertneqq|eqvparsl|imagline|imagpart|infintie|integers|Integral|intercal|LessLess|Uarrocir|intlarhk|sqsupset|angmsdaf|sqsubset|llcorner|vartheta|cupbrcap|lnapprox|Superset|SuchThat|succnsim|succneqq|angmsdag|biguplus|curlyvee|trpezium|Succeeds|NotTilde|bigwedge|angmsdah|angrtvbd|triminus|cwconint|fpartint|lrcorner|smeparsl|subseteq|urcorner|lurdshar|laemptyv|DDotrahd|approxeq|ldrushar|awconint|mapstoup|backcong|shortmid|triangle|geqslant|gesdotol|timesbar|circledR|circledS|setminus|multimap|naturals|scpolint|ncongdot|RightTee|boxminus|gnapprox|boxtimes|andslope|thicksim|angmsdaa|varsigma|cirfnint|rtriltri|angmsdab|rppolint|angmsdac|barwedge|drbkarow|clubsuit|thetasym|bsolhsub|capbrcup|dzigrarr|doteqdot|DotEqual|dotminus|UnderBar|NotEqual|realpart|otimesas|ulcorner|hksearow|hkswarow|parallel|PartialD|elinters|emptyset|plusacir|bbrktbrk|angmsdad|pointint|bigoplus|angmsdae|Precedes|bigsqcup|varkappa|notindot|supseteq|precneqq|precnsim|profalar|profline|profsurf|leqslant|lesdotor|raemptyv|subplus|notnivb|notnivc|subrarr|zigrarr|vzigzag|submult|subedot|Element|between|cirscir|larrbfs|larrsim|lotimes|lbrksld|lbrkslu|lozenge|ldrdhar|dbkarow|bigcirc|epsilon|simrarr|simplus|ltquest|Epsilon|luruhar|gtquest|maltese|npolint|eqcolon|npreceq|bigodot|ddagger|gtrless|bnequiv|harrcir|ddotseq|equivDD|backsim|demptyv|nsqsube|nsqsupe|Upsilon|nsubset|upsilon|minusdu|nsucceq|swarrow|nsupset|coloneq|searrow|boxplus|napprox|natural|asympeq|alefsym|congdot|nearrow|bigstar|diamond|supplus|tritime|LeftTee|nvinfin|triplus|NewLine|nvltrie|nvrtrie|nwarrow|nexists|Diamond|ruluhar|Implies|supmult|angzarr|suplarr|suphsub|questeq|because|digamma|Because|olcross|bemptyv|omicron|Omicron|rotimes|NoBreak|intprod|angrtvb|orderof|uwangle|suphsol|lesdoto|orslope|DownTee|realine|cudarrl|rdldhar|OverBar|supedot|lessdot|supdsub|topfork|succsim|rbrkslu|rbrksld|pertenk|cudarrr|isindot|planckh|lessgtr|pluscir|gesdoto|plussim|plustwo|lesssim|cularrp|rarrsim|Cayleys|notinva|notinvb|notinvc|UpArrow|Uparrow|uparrow|NotLess|dwangle|precsim|Product|curarrm|Cconint|dotplus|rarrbfs|ccupssm|Cedilla|cemptyv|notniva|quatint|frac35|frac38|frac45|frac56|frac58|frac78|tridot|xoplus|gacute|gammad|Gammad|lfisht|lfloor|bigcup|sqsupe|gbreve|Gbreve|lharul|sqsube|sqcups|Gcedil|apacir|llhard|lmidot|Lmidot|lmoust|andand|sqcaps|approx|Abreve|spades|circeq|tprime|divide|topcir|Assign|topbot|gesdot|divonx|xuplus|timesd|gesles|atilde|solbar|SOFTcy|loplus|timesb|lowast|lowbar|dlcorn|dlcrop|softcy|dollar|lparlt|thksim|lrhard|Atilde|lsaquo|smashp|bigvee|thinsp|wreath|bkarow|lsquor|lstrok|Lstrok|lthree|ltimes|ltlarr|DotDot|simdot|ltrPar|weierp|xsqcup|angmsd|sigmav|sigmaf|zeetrf|Zcaron|zcaron|mapsto|vsupne|thetav|cirmid|marker|mcomma|Zacute|vsubnE|there4|gtlPar|vsubne|bottom|gtrarr|SHCHcy|shchcy|midast|midcir|middot|minusb|minusd|gtrdot|bowtie|sfrown|mnplus|models|colone|seswar|Colone|mstpos|searhk|gtrsim|nacute|Nacute|boxbox|telrec|hairsp|Tcedil|nbumpe|scnsim|ncaron|Ncaron|ncedil|Ncedil|hamilt|Scedil|nearhk|hardcy|HARDcy|tcedil|Tcaron|commat|nequiv|nesear|tcaron|target|hearts|nexist|varrho|scedil|Scaron|scaron|hellip|Sacute|sacute|hercon|swnwar|compfn|rtimes|rthree|rsquor|rsaquo|zacute|wedgeq|homtht|barvee|barwed|Barwed|rpargt|horbar|conint|swarhk|roplus|nltrie|hslash|hstrok|Hstrok|rmoust|Conint|bprime|hybull|hyphen|iacute|Iacute|supsup|supsub|supsim|varphi|coprod|brvbar|agrave|Supset|supset|igrave|Igrave|notinE|Agrave|iiiint|iinfin|copysr|wedbar|Verbar|vangrt|becaus|incare|verbar|inodot|bullet|drcorn|intcal|drcrop|cularr|vellip|Utilde|bumpeq|cupcap|dstrok|Dstrok|CupCap|cupcup|cupdot|eacute|Eacute|supdot|iquest|easter|ecaron|Ecaron|ecolon|isinsv|utilde|itilde|Itilde|curarr|succeq|Bumpeq|cacute|ulcrop|nparsl|Cacute|nprcue|egrave|Egrave|nrarrc|nrarrw|subsup|subsub|nrtrie|jsercy|nsccue|Jsercy|kappav|kcedil|Kcedil|subsim|ulcorn|nsimeq|egsdot|veebar|kgreen|capand|elsdot|Subset|subset|curren|aacute|lacute|Lacute|emptyv|ntilde|Ntilde|lagran|lambda|Lambda|capcap|Ugrave|langle|subdot|emsp13|numero|emsp14|nvdash|nvDash|nVdash|nVDash|ugrave|ufisht|nvHarr|larrfs|nvlArr|larrhk|larrlp|larrpl|nvrArr|Udblac|nwarhk|larrtl|nwnear|oacute|Oacute|latail|lAtail|sstarf|lbrace|odblac|Odblac|lbrack|udblac|odsold|eparsl|lcaron|Lcaron|ograve|Ograve|lcedil|Lcedil|Aacute|ssmile|ssetmn|squarf|ldquor|capcup|ominus|cylcty|rharul|eqcirc|dagger|rfloor|rfisht|Dagger|daleth|equals|origof|capdot|equest|dcaron|Dcaron|rdquor|oslash|Oslash|otilde|Otilde|otimes|Otimes|urcrop|Ubreve|ubreve|Yacute|Uacute|uacute|Rcedil|rcedil|urcorn|parsim|Rcaron|Vdashl|rcaron|Tstrok|percnt|period|permil|Exists|yacute|rbrack|rbrace|phmmat|ccaron|Ccaron|planck|ccedil|plankv|tstrok|female|plusdo|plusdu|ffilig|plusmn|ffllig|Ccedil|rAtail|dfisht|bernou|ratail|Rarrtl|rarrtl|angsph|rarrpl|rarrlp|rarrhk|xwedge|xotime|forall|ForAll|Vvdash|vsupnE|preceq|bigcap|frac12|frac13|frac14|primes|rarrfs|prnsim|frac15|Square|frac16|square|lesdot|frac18|frac23|propto|prurel|rarrap|rangle|puncsp|frac25|Racute|qprime|racute|lesges|frac34|abreve|AElig|eqsim|utdot|setmn|urtri|Equal|Uring|seArr|uring|searr|dashv|Dashv|mumap|nabla|iogon|Iogon|sdote|sdotb|scsim|napid|napos|equiv|natur|Acirc|dblac|erarr|nbump|iprod|erDot|ucirc|awint|esdot|angrt|ncong|isinE|scnap|Scirc|scirc|ndash|isins|Ubrcy|nearr|neArr|isinv|nedot|ubrcy|acute|Ycirc|iukcy|Iukcy|xutri|nesim|caret|jcirc|Jcirc|caron|twixt|ddarr|sccue|exist|jmath|sbquo|ngeqq|angst|ccaps|lceil|ngsim|UpTee|delta|Delta|rtrif|nharr|nhArr|nhpar|rtrie|jukcy|Jukcy|kappa|rsquo|Kappa|nlarr|nlArr|TSHcy|rrarr|aogon|Aogon|fflig|xrarr|tshcy|ccirc|nleqq|filig|upsih|nless|dharl|nlsim|fjlig|ropar|nltri|dharr|robrk|roarr|fllig|fltns|roang|rnmid|subnE|subne|lAarr|trisb|Ccirc|acirc|ccups|blank|VDash|forkv|Vdash|langd|cedil|blk12|blk14|laquo|strns|diams|notin|vDash|larrb|blk34|block|disin|uplus|vdash|vBarv|aelig|starf|Wedge|check|xrArr|lates|lbarr|lBarr|notni|lbbrk|bcong|frasl|lbrke|frown|vrtri|vprop|vnsup|gamma|Gamma|wedge|xodot|bdquo|srarr|doteq|ldquo|boxdl|boxdL|gcirc|Gcirc|boxDl|boxDL|boxdr|boxdR|boxDr|TRADE|trade|rlhar|boxDR|vnsub|npart|vltri|rlarr|boxhd|boxhD|nprec|gescc|nrarr|nrArr|boxHd|boxHD|boxhu|boxhU|nrtri|boxHu|clubs|boxHU|times|colon|Colon|gimel|xlArr|Tilde|nsime|tilde|nsmid|nspar|THORN|thorn|xlarr|nsube|nsubE|thkap|xhArr|comma|nsucc|boxul|boxuL|nsupe|nsupE|gneqq|gnsim|boxUl|boxUL|grave|boxur|boxuR|boxUr|boxUR|lescc|angle|bepsi|boxvh|varpi|boxvH|numsp|Theta|gsime|gsiml|theta|boxVh|boxVH|boxvl|gtcir|gtdot|boxvL|boxVl|boxVL|crarr|cross|Cross|nvsim|boxvr|nwarr|nwArr|sqsup|dtdot|Uogon|lhard|lharu|dtrif|ocirc|Ocirc|lhblk|duarr|odash|sqsub|Hacek|sqcup|llarr|duhar|oelig|OElig|ofcir|boxvR|uogon|lltri|boxVr|csube|uuarr|ohbar|csupe|ctdot|olarr|olcir|harrw|oline|sqcap|omacr|Omacr|omega|Omega|boxVR|aleph|lneqq|lnsim|loang|loarr|rharu|lobrk|hcirc|operp|oplus|rhard|Hcirc|orarr|Union|order|ecirc|Ecirc|cuepr|szlig|cuesc|breve|reals|eDDot|Breve|hoarr|lopar|utrif|rdquo|Umacr|umacr|efDot|swArr|ultri|alpha|rceil|ovbar|swarr|Wcirc|wcirc|smtes|smile|bsemi|lrarr|aring|parsl|lrhar|bsime|uhblk|lrtri|cupor|Aring|uharr|uharl|slarr|rbrke|bsolb|lsime|rbbrk|RBarr|lsimg|phone|rBarr|rbarr|icirc|lsquo|Icirc|emacr|Emacr|ratio|simne|plusb|simlE|simgE|simeq|pluse|ltcir|ltdot|empty|xharr|xdtri|iexcl|Alpha|ltrie|rarrw|pound|ltrif|xcirc|bumpe|prcue|bumpE|asymp|amacr|cuvee|Sigma|sigma|iiint|udhar|iiota|ijlig|IJlig|supnE|imacr|Imacr|prime|Prime|image|prnap|eogon|Eogon|rarrc|mdash|mDDot|cuwed|imath|supne|imped|Amacr|udarr|prsim|micro|rarrb|cwint|raquo|infin|eplus|range|rangd|Ucirc|radic|minus|amalg|veeeq|rAarr|epsiv|ycirc|quest|sharp|quot|zwnj|Qscr|race|qscr|Qopf|qopf|qint|rang|Rang|Zscr|zscr|Zopf|zopf|rarr|rArr|Rarr|Pscr|pscr|prop|prod|prnE|prec|ZHcy|zhcy|prap|Zeta|zeta|Popf|popf|Zdot|plus|zdot|Yuml|yuml|phiv|YUcy|yucy|Yscr|yscr|perp|Yopf|yopf|part|para|YIcy|Ouml|rcub|yicy|YAcy|rdca|ouml|osol|Oscr|rdsh|yacy|real|oscr|xvee|andd|rect|andv|Xscr|oror|ordm|ordf|xscr|ange|aopf|Aopf|rHar|Xopf|opar|Oopf|xopf|xnis|rhov|oopf|omid|xmap|oint|apid|apos|ogon|ascr|Ascr|odot|odiv|xcup|xcap|ocir|oast|nvlt|nvle|nvgt|nvge|nvap|Wscr|wscr|auml|ntlg|ntgl|nsup|nsub|nsim|Nscr|nscr|nsce|Wopf|ring|npre|wopf|npar|Auml|Barv|bbrk|Nopf|nopf|nmid|nLtv|beta|ropf|Ropf|Beta|beth|nles|rpar|nleq|bnot|bNot|nldr|NJcy|rscr|Rscr|Vscr|vscr|rsqb|njcy|bopf|nisd|Bopf|rtri|Vopf|nGtv|ngtr|vopf|boxh|boxH|boxv|nges|ngeq|boxV|bscr|scap|Bscr|bsim|Vert|vert|bsol|bull|bump|caps|cdot|ncup|scnE|ncap|nbsp|napE|Cdot|cent|sdot|Vbar|nang|vBar|chcy|Mscr|mscr|sect|semi|CHcy|Mopf|mopf|sext|circ|cire|mldr|mlcp|cirE|comp|shcy|SHcy|vArr|varr|cong|copf|Copf|copy|COPY|malt|male|macr|lvnE|cscr|ltri|sime|ltcc|simg|Cscr|siml|csub|Uuml|lsqb|lsim|uuml|csup|Lscr|lscr|utri|smid|lpar|cups|smte|lozf|darr|Lopf|Uscr|solb|lopf|sopf|Sopf|lneq|uscr|spar|dArr|lnap|Darr|dash|Sqrt|LJcy|ljcy|lHar|dHar|Upsi|upsi|diam|lesg|djcy|DJcy|leqq|dopf|Dopf|dscr|Dscr|dscy|ldsh|ldca|squf|DScy|sscr|Sscr|dsol|lcub|late|star|Star|Uopf|Larr|lArr|larr|uopf|dtri|dzcy|sube|subE|Lang|lang|Kscr|kscr|Kopf|kopf|KJcy|kjcy|KHcy|khcy|DZcy|ecir|edot|eDot|Jscr|jscr|succ|Jopf|jopf|Edot|uHar|emsp|ensp|Iuml|iuml|eopf|isin|Iscr|iscr|Eopf|epar|sung|epsi|escr|sup1|sup2|sup3|Iota|iota|supe|supE|Iopf|iopf|IOcy|iocy|Escr|esim|Esim|imof|Uarr|QUOT|uArr|uarr|euml|IEcy|iecy|Idot|Euml|euro|excl|Hscr|hscr|Hopf|hopf|TScy|tscy|Tscr|hbar|tscr|flat|tbrk|fnof|hArr|harr|half|fopf|Fopf|tdot|gvnE|fork|trie|gtcc|fscr|Fscr|gdot|gsim|Gscr|gscr|Gopf|gopf|gneq|Gdot|tosa|gnap|Topf|topf|geqq|toea|GJcy|gjcy|tint|gesl|mid|Sfr|ggg|top|ges|gla|glE|glj|geq|gne|gEl|gel|gnE|Gcy|gcy|gap|Tfr|tfr|Tcy|tcy|Hat|Tau|Ffr|tau|Tab|hfr|Hfr|ffr|Fcy|fcy|icy|Icy|iff|ETH|eth|ifr|Ifr|Eta|eta|int|Int|Sup|sup|ucy|Ucy|Sum|sum|jcy|ENG|ufr|Ufr|eng|Jcy|jfr|els|ell|egs|Efr|efr|Jfr|uml|kcy|Kcy|Ecy|ecy|kfr|Kfr|lap|Sub|sub|lat|lcy|Lcy|leg|Dot|dot|lEg|leq|les|squ|div|die|lfr|Lfr|lgE|Dfr|dfr|Del|deg|Dcy|dcy|lne|lnE|sol|loz|smt|Cup|lrm|cup|lsh|Lsh|sim|shy|map|Map|mcy|Mcy|mfr|Mfr|mho|gfr|Gfr|sfr|cir|Chi|chi|nap|Cfr|vcy|Vcy|cfr|Scy|scy|ncy|Ncy|vee|Vee|Cap|cap|nfr|scE|sce|Nfr|nge|ngE|nGg|vfr|Vfr|ngt|bot|nGt|nis|niv|Rsh|rsh|nle|nlE|bne|Bfr|bfr|nLl|nlt|nLt|Bcy|bcy|not|Not|rlm|wfr|Wfr|npr|nsc|num|ocy|ast|Ocy|ofr|xfr|Xfr|Ofr|ogt|ohm|apE|olt|Rho|ape|rho|Rfr|rfr|ord|REG|ang|reg|orv|And|and|AMP|Rcy|amp|Afr|ycy|Ycy|yen|yfr|Yfr|rcy|par|pcy|Pcy|pfr|Pfr|phi|Phi|afr|Acy|acy|zcy|Zcy|piv|acE|acd|zfr|Zfr|pre|prE|psi|Psi|qfr|Qfr|zwj|Or|ge|Gg|gt|gg|el|oS|lt|Lt|LT|Re|lg|gl|eg|ne|Im|it|le|DD|wp|wr|nu|Nu|dd|lE|Sc|sc|pi|Pi|ee|af|ll|Ll|rx|gE|xi|pm|Xi|ic|pr|Pr|in|ni|mp|mu|ac|Mu|or|ap|Gt|GT|ii);|&(Aacute|Agrave|Atilde|Ccedil|Eacute|Egrave|Iacute|Igrave|Ntilde|Oacute|Ograve|Oslash|Otilde|Uacute|Ugrave|Yacute|aacute|agrave|atilde|brvbar|ccedil|curren|divide|eacute|egrave|frac12|frac14|frac34|iacute|igrave|iquest|middot|ntilde|oacute|ograve|oslash|otilde|plusmn|uacute|ugrave|yacute|AElig|Acirc|Aring|Ecirc|Icirc|Ocirc|THORN|Ucirc|acirc|acute|aelig|aring|cedil|ecirc|icirc|iexcl|laquo|micro|ocirc|pound|raquo|szlig|thorn|times|ucirc|Auml|COPY|Euml|Iuml|Ouml|QUOT|Uuml|auml|cent|copy|euml|iuml|macr|nbsp|ordf|ordm|ouml|para|quot|sect|sup1|sup2|sup3|uuml|yuml|AMP|ETH|REG|amp|deg|eth|not|reg|shy|uml|yen|GT|LT|gt|lt)(?!;)([=a-zA-Z0-9]?)|&#([0-9]+)(;?)|&#[xX]([a-fA-F0-9]+)(;?)|&([0-9a-zA-Z]+)/g,v={aacute:"ÃĄ",Aacute:"Á",abreve:"ă",Abreve:"Ă",ac:"∞",acd:"âˆŋ",acE:"âˆžĖŗ",acirc:"Ãĸ",Acirc:"Â",acute:"´",acy:"а",Acy:"А",aelig:"ÃĻ",AElig:"Æ",af:"⁥",afr:"𝔞",Afr:"𝔄",agrave:"à",Agrave:"À",alefsym:"â„ĩ",aleph:"â„ĩ",alpha:"Îą",Alpha:"Α",amacr:"ā",Amacr:"Ā",amalg:"â¨ŋ",amp:"&",AMP:"&",and:"∧",And:"⩓",andand:"⩕",andd:"⩜",andslope:"⊘",andv:"⩚",ang:"∠",ange:"âϤ",angle:"∠",angmsd:"∥",angmsdaa:"âύ",angmsdab:"âĻŠ",angmsdac:"âĻĒ",angmsdad:"âĻĢ",angmsdae:"âĻŦ",angmsdaf:"âĻ­",angmsdag:"âĻŽ",angmsdah:"âϝ",angrt:"∟",angrtvb:"⊾",angrtvbd:"âĻ",angsph:"âˆĸ",angst:"Å",angzarr:"âŧ",aogon:"ą",Aogon:"Ą",aopf:"𝕒",Aopf:"𝔸",ap:"≈",apacir:"⊯",ape:"≊",apE:"⊰",apid:"≋",apos:"'",ApplyFunction:"⁥",approx:"≈",approxeq:"≊",aring:"ÃĨ",Aring:"Å",ascr:"đ’ļ",Ascr:"𝒜",Assign:"≔",ast:"*",asymp:"≈",asympeq:"≍",atilde:"ÃŖ",Atilde:"Ã",auml:"ä",Auml:"Ä",awconint:"âˆŗ",awint:"⨑",backcong:"≌",backepsilon:"Īļ",backprime:"â€ĩ",backsim:"âˆŊ",backsimeq:"⋍",Backslash:"∖",Barv:"â̧",barvee:"âŠŊ",barwed:"⌅",Barwed:"⌆",barwedge:"⌅",bbrk:"âŽĩ",bbrktbrk:"âŽļ",bcong:"≌",bcy:"Đą",Bcy:"Б",bdquo:"„",becaus:"âˆĩ",because:"âˆĩ",Because:"âˆĩ",bemptyv:"âϰ",bepsi:"Īļ",bernou:"â„Ŧ",Bernoullis:"â„Ŧ",beta:"β",Beta:"Β",beth:"â„ļ",between:"â‰Ŧ",bfr:"𝔟",Bfr:"𝔅",bigcap:"⋂",bigcirc:"◯",bigcup:"⋃",bigodot:"⨀",bigoplus:"⨁",bigotimes:"⨂",bigsqcup:"⨆",bigstar:"★",bigtriangledown:"â–Ŋ",bigtriangleup:"â–ŗ",biguplus:"⨄",bigvee:"⋁",bigwedge:"⋀",bkarow:"⤍",blacklozenge:"â§Ģ",blacksquare:"â–Ē",blacktriangle:"▴",blacktriangledown:"▾",blacktriangleleft:"◂",blacktriangleright:"▸",blank:"âŖ",blk12:"▒",blk14:"░",blk34:"▓",block:"█",bne:"=âƒĨ",bnequiv:"≡âƒĨ",bnot:"⌐",bNot:"âĢ­",bopf:"𝕓",Bopf:"𝔹",bot:"âŠĨ",bottom:"âŠĨ",bowtie:"⋈",boxbox:"⧉",boxdl:"┐",boxdL:"╕",boxDl:"╖",boxDL:"╗",boxdr:"┌",boxdR:"╒",boxDr:"╓",boxDR:"╔",boxh:"─",boxH:"═",boxhd:"â”Ŧ",boxhD:"â•Ĩ",boxHd:"╤",boxHD:"â•Ļ",boxhu:"┴",boxhU:"╨",boxHu:"╧",boxHU:"╩",boxminus:"⊟",boxplus:"⊞",boxtimes:"⊠",boxul:"┘",boxuL:"╛",boxUl:"╜",boxUL:"╝",boxur:"└",boxuR:"╘",boxUr:"╙",boxUR:"╚",boxv:"│",boxV:"║",boxvh:"â”ŧ",boxvH:"â•Ē",boxVh:"â•Ģ",boxVH:"â•Ŧ",boxvl:"┤",boxvL:"╡",boxVl:"â•ĸ",boxVL:"â•Ŗ",boxvr:"├",boxvR:"╞",boxVr:"╟",boxVR:"╠",bprime:"â€ĩ",breve:"˘",Breve:"˘",brvbar:"ÂĻ",bscr:"𝒷",Bscr:"â„Ŧ",bsemi:"⁏",bsim:"âˆŊ",bsime:"⋍",bsol:"\\",bsolb:"⧅",bsolhsub:"⟈",bull:"â€ĸ",bullet:"â€ĸ",bump:"≎",bumpe:"≏",bumpE:"âĒŽ",bumpeq:"≏",Bumpeq:"≎",cacute:"ć",Cacute:"Ć",cap:"∊",Cap:"⋒",capand:"⩄",capbrcup:"⩉",capcap:"⩋",capcup:"⩇",capdot:"⩀",CapitalDifferentialD:"ⅅ",caps:"âˆŠī¸€",caret:"⁁",caron:"ˇ",Cayleys:"ℭ",ccaps:"⊍",ccaron:"č",Ccaron:"Č",ccedil:"ç",Ccedil:"Ç",ccirc:"ĉ",Ccirc:"Ĉ",Cconint:"∰",ccups:"⩌",ccupssm:"⊐",cdot:"ċ",Cdot:"Ċ",cedil:"¸",Cedilla:"¸",cemptyv:"âϞ",cent:"Âĸ",centerdot:"¡",CenterDot:"¡",cfr:"𝔠",Cfr:"ℭ",chcy:"҇",CHcy:"Ч",check:"✓",checkmark:"✓",chi:"·",Chi:"Χ",cir:"○",circ:"ˆ",circeq:"≗",circlearrowleft:"â†ē",circlearrowright:"â†ģ",circledast:"⊛",circledcirc:"⊚",circleddash:"⊝",CircleDot:"⊙",circledR:"ÂŽ",circledS:"Ⓢ",CircleMinus:"⊖",CirclePlus:"⊕",CircleTimes:"⊗",cire:"≗",cirE:"⧃",cirfnint:"⨐",cirmid:"â̝",cirscir:"⧂",ClockwiseContourIntegral:"∲",CloseCurlyDoubleQuote:"”",CloseCurlyQuote:"’",clubs:"â™Ŗ",clubsuit:"â™Ŗ",colon:":",Colon:"∡",colone:"≔",Colone:"⊴",coloneq:"≔",comma:",",commat:"@",comp:"∁",compfn:"∘",complement:"∁",complexes:"ℂ",cong:"≅",congdot:"⊭",Congruent:"≡",conint:"∎",Conint:"∯",ContourIntegral:"∎",copf:"𝕔",Copf:"ℂ",coprod:"∐",Coproduct:"∐",copy:"Š",COPY:"Š",copysr:"℗",CounterClockwiseContourIntegral:"âˆŗ",crarr:"â†ĩ",cross:"✗",Cross:"⨯",cscr:"𝒸",Cscr:"𝒞",csub:"âĢ",csube:"âĢ‘",csup:"â̐",csupe:"âĢ’",ctdot:"⋯",cudarrl:"⤸",cudarrr:"â¤ĩ",cuepr:"⋞",cuesc:"⋟",cularr:"â†ļ",cularrp:"â¤Ŋ",cup:"âˆĒ",Cup:"⋓",cupbrcap:"⊈",cupcap:"⩆",CupCap:"≍",cupcup:"⩊",cupdot:"⊍",cupor:"⩅",cups:"âˆĒ",curarr:"↷",curarrm:"â¤ŧ",curlyeqprec:"⋞",curlyeqsucc:"⋟",curlyvee:"⋎",curlywedge:"⋏",curren:"¤",curvearrowleft:"â†ļ",curvearrowright:"↷",cuvee:"⋎",cuwed:"⋏",cwconint:"∲",cwint:"∹",cylcty:"⌭",dagger:"†",Dagger:"‡",daleth:"ℸ",darr:"↓",dArr:"⇓",Darr:"↡",dash:"‐",dashv:"âŠŖ",Dashv:"â̤",dbkarow:"⤏",dblac:"˝",dcaron:"ď",Dcaron:"Ď",dcy:"Đ´",Dcy:"Д",dd:"ⅆ",DD:"ⅅ",ddagger:"‡",ddarr:"⇊",DDotrahd:"⤑",ddotseq:"⊡",deg:"°",Del:"∇",delta:"δ",Delta:"Δ",demptyv:"âĻą",dfisht:"âĨŋ",dfr:"𝔡",Dfr:"𝔇",dHar:"âĨĨ",dharl:"⇃",dharr:"⇂",DiacriticalAcute:"´",DiacriticalDot:"˙",DiacriticalDoubleAcute:"˝",DiacriticalGrave:"`",DiacriticalTilde:"˜",diam:"⋄",diamond:"⋄",Diamond:"⋄",diamondsuit:"â™Ļ",diams:"â™Ļ",die:"¨",DifferentialD:"ⅆ",digamma:"Ī",disin:"⋲",div:"Ãˇ",divide:"Ãˇ",divideontimes:"⋇",divonx:"⋇",djcy:"Ņ’",DJcy:"Ђ",dlcorn:"⌞",dlcrop:"⌍",dollar:"$",dopf:"𝕕",Dopf:"đ”ģ",dot:"˙",Dot:"¨",DotDot:"⃜",doteq:"≐",doteqdot:"≑",DotEqual:"≐",dotminus:"∸",dotplus:"∔",dotsquare:"⊡",doublebarwedge:"⌆",DoubleContourIntegral:"∯",DoubleDot:"¨",DoubleDownArrow:"⇓",DoubleLeftArrow:"⇐",DoubleLeftRightArrow:"⇔",DoubleLeftTee:"â̤",DoubleLongLeftArrow:"⟸",DoubleLongLeftRightArrow:"âŸē",DoubleLongRightArrow:"⟹",DoubleRightArrow:"⇒",DoubleRightTee:"⊨",DoubleUpArrow:"⇑",DoubleUpDownArrow:"⇕",DoubleVerticalBar:"âˆĨ",downarrow:"↓",Downarrow:"⇓",DownArrow:"↓",DownArrowBar:"⤓",DownArrowUpArrow:"â‡ĩ",DownBreve:"Ė‘",downdownarrows:"⇊",downharpoonleft:"⇃",downharpoonright:"⇂",DownLeftRightVector:"âĨ",DownLeftTeeVector:"âĨž",DownLeftVector:"â†Ŋ",DownLeftVectorBar:"âĨ–",DownRightTeeVector:"âĨŸ",DownRightVector:"⇁",DownRightVectorBar:"âĨ—",DownTee:"⊤",DownTeeArrow:"↧",drbkarow:"⤐",drcorn:"⌟",drcrop:"⌌",dscr:"𝒹",Dscr:"𝒟",dscy:"Ņ•",DScy:"Ѕ",dsol:"â§ļ",dstrok:"đ",Dstrok:"Đ",dtdot:"⋱",dtri:"â–ŋ",dtrif:"▾",duarr:"â‡ĩ",duhar:"âĨ¯",dwangle:"âĻĻ",dzcy:"ҟ",DZcy:"Џ",dzigrarr:"âŸŋ",eacute:"Ê",Eacute:"É",easter:"⊎",ecaron:"ě",Ecaron:"Ě",ecir:"≖",ecirc:"ÃĒ",Ecirc:"Ê",ecolon:"≕",ecy:"Ņ",Ecy:"Đ­",eDDot:"⊡",edot:"ė",eDot:"≑",Edot:"Ė",ee:"ⅇ",efDot:"≒",efr:"đ”ĸ",Efr:"𝔈",eg:"âǚ",egrave:"è",Egrave:"È",egs:"âĒ–",egsdot:"âǘ",el:"âĒ™",Element:"∈",elinters:"⏧",ell:"ℓ",els:"âĒ•",elsdot:"âĒ—",emacr:"ē",Emacr:"Ē",empty:"∅",emptyset:"∅",EmptySmallSquare:"â—ģ",emptyv:"∅",EmptyVerySmallSquare:"â–Ģ",emsp:" ",emsp13:" ",emsp14:" ",eng:"ŋ",ENG:"Ŋ",ensp:" ",eogon:"ę",Eogon:"Ę",eopf:"𝕖",Eopf:"đ”ŧ",epar:"⋕",eparsl:"â§Ŗ",eplus:"⊹",epsi:"Îĩ",epsilon:"Îĩ",Epsilon:"Ε",epsiv:"Īĩ",eqcirc:"≖",eqcolon:"≕",eqsim:"≂",eqslantgtr:"âĒ–",eqslantless:"âĒ•",Equal:"âŠĩ",equals:"=",EqualTilde:"≂",equest:"≟",Equilibrium:"⇌",equiv:"≡",equivDD:"⊸",eqvparsl:"â§Ĩ",erarr:"âĨą",erDot:"≓",escr:"ℯ",Escr:"ℰ",esdot:"≐",esim:"≂",Esim:"âŠŗ",eta:"Ρ",Eta:"Η",eth:"ð",ETH:"Ð",euml:"ÃĢ",Euml:"Ë",euro:"â‚Ŧ",excl:"!",exist:"∃",Exists:"∃",expectation:"ℰ",exponentiale:"ⅇ",ExponentialE:"ⅇ",fallingdotseq:"≒",fcy:"Ņ„",Fcy:"Ф",female:"♀",ffilig:"īŦƒ",fflig:"īŦ€",ffllig:"īŦ„",ffr:"đ”Ŗ",Ffr:"𝔉",filig:"īŦ",FilledSmallSquare:"â—ŧ",FilledVerySmallSquare:"â–Ē",fjlig:"fj",flat:"♭",fllig:"īŦ‚",fltns:"▱",fnof:"ƒ",fopf:"𝕗",Fopf:"đ”Ŋ",forall:"∀",ForAll:"∀",fork:"⋔",forkv:"âĢ™",Fouriertrf:"ℱ",fpartint:"⨍",frac12:"ÂŊ",frac13:"⅓",frac14:"Âŧ",frac15:"⅕",frac16:"⅙",frac18:"⅛",frac23:"⅔",frac25:"⅖",frac34:"ž",frac35:"⅗",frac38:"⅜",frac45:"⅘",frac56:"⅚",frac58:"⅝",frac78:"⅞",frasl:"⁄",frown:"âŒĸ",fscr:"đ’ģ",Fscr:"ℱ",gacute:"Įĩ",gamma:"Îŗ",Gamma:"Γ",gammad:"Ī",Gammad:"Μ",gap:"âdž",gbreve:"ğ",Gbreve:"Ğ",Gcedil:"Äĸ",gcirc:"ĝ",Gcirc:"Ĝ",gcy:"Đŗ",Gcy:"Г",gdot:"ÄĄ",Gdot:"Ä ",ge:"â‰Ĩ",gE:"≧",gel:"⋛",gEl:"ânj",geq:"â‰Ĩ",geqq:"≧",geqslant:"⊞",ges:"⊞",gescc:"âĒŠ",gesdot:"âĒ€",gesdoto:"âĒ‚",gesdotol:"âĒ„",gesl:"â‹›ī¸€",gesles:"âĒ”",gfr:"𝔤",Gfr:"𝔊",gg:"â‰Ģ",Gg:"⋙",ggg:"⋙",gimel:"ℷ",gjcy:"Ņ“",GJcy:"Ѓ",gl:"≷",gla:"âĒĨ",glE:"âĒ’",glj:"âǤ",gnap:"âNJ",gnapprox:"âNJ",gne:"âLj",gnE:"≩",gneq:"âLj",gneqq:"≩",gnsim:"⋧",gopf:"𝕘",Gopf:"𝔾",grave:"`",GreaterEqual:"â‰Ĩ",GreaterEqualLess:"⋛",GreaterFullEqual:"≧",GreaterGreater:"âĒĸ",GreaterLess:"≷",GreaterSlantEqual:"⊞",GreaterTilde:"â‰ŗ",gscr:"ℊ",Gscr:"đ’ĸ",gsim:"â‰ŗ",gsime:"âĒŽ",gsiml:"âǐ",gt:">",Gt:"â‰Ģ",GT:">",gtcc:"âǧ",gtcir:"âŠē",gtdot:"⋗",gtlPar:"âĻ•",gtquest:"âŠŧ",gtrapprox:"âdž",gtrarr:"âĨ¸",gtrdot:"⋗",gtreqless:"⋛",gtreqqless:"ânj",gtrless:"≷",gtrsim:"â‰ŗ",gvertneqq:"â‰Šī¸€",gvnE:"â‰Šī¸€",Hacek:"ˇ",hairsp:" ",half:"ÂŊ",hamilt:"ℋ",hardcy:"Ҋ",HARDcy:"ĐĒ",harr:"↔",hArr:"⇔",harrcir:"âĨˆ",harrw:"↭",Hat:"^",hbar:"ℏ",hcirc:"ÄĨ",Hcirc:"Ĥ",hearts:"â™Ĩ",heartsuit:"â™Ĩ",hellip:"â€Ļ",hercon:"⊹",hfr:"đ”Ĩ",Hfr:"ℌ",HilbertSpace:"ℋ",hksearow:"â¤Ĩ",hkswarow:"â¤Ļ",hoarr:"â‡ŋ",homtht:"âˆģ",hookleftarrow:"↩",hookrightarrow:"â†Ē",hopf:"𝕙",Hopf:"ℍ",horbar:"―",HorizontalLine:"─",hscr:"đ’Ŋ",Hscr:"ℋ",hslash:"ℏ",hstrok:"ħ",Hstrok:"ÄĻ",HumpDownHump:"≎",HumpEqual:"≏",hybull:"⁃",hyphen:"‐",iacute:"í",Iacute:"Í",ic:"âŖ",icirc:"ÃŽ",Icirc:"Î",icy:"и",Icy:"И",Idot:"İ",iecy:"Đĩ",IEcy:"Е",iexcl:"ÂĄ",iff:"⇔",ifr:"đ”Ļ",Ifr:"ℑ",igrave:"ÃŦ",Igrave:"Ì",ii:"ⅈ",iiiint:"⨌",iiint:"∭",iinfin:"⧜",iiota:"℩",ijlig:"Äŗ",IJlig:"IJ",Im:"ℑ",imacr:"ÄĢ",Imacr:"ÄĒ",image:"ℑ",ImaginaryI:"ⅈ",imagline:"ℐ",imagpart:"ℑ",imath:"Äą",imof:"⊷",imped:"Æĩ",Implies:"⇒",in:"∈",incare:"℅",infin:"∞",infintie:"⧝",inodot:"Äą",int:"âˆĢ",Int:"âˆŦ",intcal:"âŠē",integers:"ℤ",Integral:"âˆĢ",intercal:"âŠē",Intersection:"⋂",intlarhk:"⨗",intprod:"â¨ŧ",InvisibleComma:"âŖ",InvisibleTimes:"âĸ",iocy:"Ņ‘",IOcy:"Ё",iogon:"į",Iogon:"ÄŽ",iopf:"𝕚",Iopf:"𝕀",iota:"Κ",Iota:"Ι",iprod:"â¨ŧ",iquest:"Âŋ",iscr:"𝒾",Iscr:"ℐ",isin:"∈",isindot:"â‹ĩ",isinE:"⋹",isins:"⋴",isinsv:"â‹ŗ",isinv:"∈",it:"âĸ",itilde:"ÄŠ",Itilde:"Ĩ",iukcy:"Ņ–",Iukcy:"І",iuml:"ï",Iuml:"Ï",jcirc:"Äĩ",Jcirc:"Ä´",jcy:"Đš",Jcy:"Й",jfr:"𝔧",Jfr:"𝔍",jmath:"ȡ",jopf:"𝕛",Jopf:"𝕁",jscr:"đ’ŋ",Jscr:"đ’Ĩ",jsercy:"Ҙ",Jsercy:"Ј",jukcy:"Ņ”",Jukcy:"Є",kappa:"Îē",Kappa:"Κ",kappav:"ΰ",kcedil:"ġ",Kcedil:"Äļ",kcy:"Đē",Kcy:"К",kfr:"𝔨",Kfr:"𝔎",kgreen:"ĸ",khcy:"Ņ…",KHcy:"ĐĨ",kjcy:"Ҝ",KJcy:"Ќ",kopf:"𝕜",Kopf:"𝕂",kscr:"𝓀",Kscr:"đ’Ļ",lAarr:"⇚",lacute:"Äē",Lacute:"Äš",laemptyv:"âĻ´",lagran:"ℒ",lambda:"Îģ",Lambda:"Λ",lang:"⟨",Lang:"âŸĒ",langd:"âĻ‘",langle:"⟨",lap:"âĒ…",Laplacetrf:"ℒ",laquo:"ÂĢ",larr:"←",lArr:"⇐",Larr:"↞",larrb:"⇤",larrbfs:"⤟",larrfs:"⤝",larrhk:"↩",larrlp:"â†Ģ",larrpl:"⤚",larrsim:"âĨŗ",larrtl:"â†ĸ",lat:"âĒĢ",latail:"⤙",lAtail:"⤛",late:"âĒ­",lates:"âǭ",lbarr:"⤌",lBarr:"⤎",lbbrk:"❲",lbrace:"{",lbrack:"[",lbrke:"âĻ‹",lbrksld:"âĻ",lbrkslu:"âĻ",lcaron:"Äž",Lcaron:"ÄŊ",lcedil:"Äŧ",Lcedil:"Äģ",lceil:"⌈",lcub:"{",lcy:"Đģ",Lcy:"Л",ldca:"â¤ļ",ldquo:"“",ldquor:"„",ldrdhar:"âĨ§",ldrushar:"âĨ‹",ldsh:"↲",le:"≤",lE:"â‰Ļ",LeftAngleBracket:"⟨",leftarrow:"←",Leftarrow:"⇐",LeftArrow:"←",LeftArrowBar:"⇤",LeftArrowRightArrow:"⇆",leftarrowtail:"â†ĸ",LeftCeiling:"⌈",LeftDoubleBracket:"âŸĻ",LeftDownTeeVector:"âĨĄ",LeftDownVector:"⇃",LeftDownVectorBar:"âĨ™",LeftFloor:"⌊",leftharpoondown:"â†Ŋ",leftharpoonup:"â†ŧ",leftleftarrows:"⇇",leftrightarrow:"↔",Leftrightarrow:"⇔",LeftRightArrow:"↔",leftrightarrows:"⇆",leftrightharpoons:"⇋",leftrightsquigarrow:"↭",LeftRightVector:"âĨŽ",LeftTee:"âŠŖ",LeftTeeArrow:"↤",LeftTeeVector:"âĨš",leftthreetimes:"⋋",LeftTriangle:"⊲",LeftTriangleBar:"⧏",LeftTriangleEqual:"⊴",LeftUpDownVector:"âĨ‘",LeftUpTeeVector:"âĨ ",LeftUpVector:"â†ŋ",LeftUpVectorBar:"âĨ˜",LeftVector:"â†ŧ",LeftVectorBar:"âĨ’",leg:"⋚",lEg:"âĒ‹",leq:"≤",leqq:"â‰Ļ",leqslant:"âŠŊ",les:"âŠŊ",lescc:"âǍ",lesdot:"âŠŋ",lesdoto:"âǁ",lesdotor:"âǃ",lesg:"â‹šī¸€",lesges:"âĒ“",lessapprox:"âĒ…",lessdot:"⋖",lesseqgtr:"⋚",lesseqqgtr:"âĒ‹",LessEqualGreater:"⋚",LessFullEqual:"â‰Ļ",LessGreater:"â‰ļ",lessgtr:"â‰ļ",LessLess:"âĒĄ",lesssim:"≲",LessSlantEqual:"âŠŊ",LessTilde:"≲",lfisht:"âĨŧ",lfloor:"⌊",lfr:"𝔩",Lfr:"𝔏",lg:"â‰ļ",lgE:"âĒ‘",lHar:"âĨĸ",lhard:"â†Ŋ",lharu:"â†ŧ",lharul:"âĨĒ",lhblk:"▄",ljcy:"Ņ™",LJcy:"Љ",ll:"â‰Ē",Ll:"⋘",llarr:"⇇",llcorner:"⌞",Lleftarrow:"⇚",llhard:"âĨĢ",lltri:"â—ē",lmidot:"ŀ",Lmidot:"Äŋ",lmoust:"⎰",lmoustache:"⎰",lnap:"âlj",lnapprox:"âlj",lne:"âLJ",lnE:"≨",lneq:"âLJ",lneqq:"≨",lnsim:"â‹Ļ",loang:"âŸŦ",loarr:"â‡Ŋ",lobrk:"âŸĻ",longleftarrow:"âŸĩ",Longleftarrow:"⟸",LongLeftArrow:"âŸĩ",longleftrightarrow:"⟷",Longleftrightarrow:"âŸē",LongLeftRightArrow:"⟷",longmapsto:"âŸŧ",longrightarrow:"âŸļ",Longrightarrow:"⟹",LongRightArrow:"âŸļ",looparrowleft:"â†Ģ",looparrowright:"â†Ŧ",lopar:"âĻ…",lopf:"𝕝",Lopf:"𝕃",loplus:"⨭",lotimes:"⨴",lowast:"∗",lowbar:"_",LowerLeftArrow:"↙",LowerRightArrow:"↘",loz:"◊",lozenge:"◊",lozf:"â§Ģ",lpar:"(",lparlt:"âĻ“",lrarr:"⇆",lrcorner:"⌟",lrhar:"⇋",lrhard:"âĨ­",lrm:"‎",lrtri:"âŠŋ",lsaquo:"‹",lscr:"𝓁",Lscr:"ℒ",lsh:"↰",Lsh:"↰",lsim:"≲",lsime:"âĒ",lsimg:"âĒ",lsqb:"[",lsquo:"‘",lsquor:"‚",lstrok:"ł",Lstrok:"Ł",lt:"<",Lt:"â‰Ē",LT:"<",ltcc:"âĒĻ",ltcir:"⊚",ltdot:"⋖",lthree:"⋋",ltimes:"⋉",ltlarr:"âĨļ",ltquest:"âŠģ",ltri:"◃",ltrie:"⊴",ltrif:"◂",ltrPar:"âĻ–",lurdshar:"âĨŠ",luruhar:"âĨĻ",lvertneqq:"â‰¨ī¸€",lvnE:"â‰¨ī¸€",macr:"¯",male:"♂",malt:"✠",maltese:"✠",map:"â†Ļ",Map:"⤅",mapsto:"â†Ļ",mapstodown:"↧",mapstoleft:"↤",mapstoup:"â†Ĩ",marker:"▮",mcomma:"⨊",mcy:"Đŧ",Mcy:"М",mdash:"—",mDDot:"âˆē",measuredangle:"∥",MediumSpace:" ",Mellintrf:"â„ŗ",mfr:"đ”Ē",Mfr:"𝔐",mho:"℧",micro:"Âĩ",mid:"âˆŖ",midast:"*",midcir:"â̰",middot:"¡",minus:"−",minusb:"⊟",minusd:"∸",minusdu:"â¨Ē",MinusPlus:"∓",mlcp:"âĢ›",mldr:"â€Ļ",mnplus:"∓",models:"⊧",mopf:"𝕞",Mopf:"𝕄",mp:"∓",mscr:"𝓂",Mscr:"â„ŗ",mstpos:"∞",mu:"Îŧ",Mu:"Μ",multimap:"⊸",mumap:"⊸",nabla:"∇",nacute:"ń",Nacute:"Ń",nang:"∠⃒",nap:"≉",napE:"âŠ°Ė¸",napid:"â‰‹Ė¸",napos:"ʼn",napprox:"≉",natur:"♮",natural:"♮",naturals:"ℕ",nbsp:" ",nbump:"â‰ŽĖ¸",nbumpe:"â‰Ė¸",ncap:"⊃",ncaron:"ň",Ncaron:"Ň",ncedil:"ņ",Ncedil:"Ņ",ncong:"≇",ncongdot:"âŠ­Ė¸",ncup:"⩂",ncy:"ĐŊ",Ncy:"Н",ndash:"–",ne:"≠",nearhk:"⤤",nearr:"↗",neArr:"⇗",nearrow:"↗",nedot:"â‰Ė¸",NegativeMediumSpace:"​",NegativeThickSpace:"​",NegativeThinSpace:"​",NegativeVeryThinSpace:"​",nequiv:"â‰ĸ",nesear:"⤨",nesim:"â‰‚Ė¸",NestedGreaterGreater:"â‰Ģ",NestedLessLess:"â‰Ē",NewLine:"\n",nexist:"∄",nexists:"∄",nfr:"đ”Ģ",Nfr:"𝔑",nge:"≱",ngE:"â‰§Ė¸",ngeq:"≱",ngeqq:"â‰§Ė¸",ngeqslant:"âŠžĖ¸",nges:"âŠžĖ¸",nGg:"â‹™Ė¸",ngsim:"â‰ĩ",ngt:"≯",nGt:"â‰Ģ⃒",ngtr:"≯",nGtv:"â‰Ģˏ",nharr:"↮",nhArr:"⇎",nhpar:"â̞",ni:"∋",nis:"â‹ŧ",nisd:"â‹ē",niv:"∋",njcy:"Қ",NJcy:"Њ",nlarr:"↚",nlArr:"⇍",nldr:"â€Ĩ",nle:"≰",nlE:"â‰Ļˏ",nleftarrow:"↚",nLeftarrow:"⇍",nleftrightarrow:"↮",nLeftrightarrow:"⇎",nleq:"≰",nleqq:"â‰Ļˏ",nleqslant:"âŠŊˏ",nles:"âŠŊˏ",nless:"≮",nLl:"â‹˜Ė¸",nlsim:"≴",nlt:"≮",nLt:"â‰Ē⃒",nltri:"â‹Ē",nltrie:"â‹Ŧ",nLtv:"â‰Ēˏ",nmid:"∤",NoBreak:"⁠",NonBreakingSpace:" ",nopf:"𝕟",Nopf:"ℕ",not:"ÂŦ",Not:"âĢŦ",NotCongruent:"â‰ĸ",NotCupCap:"≭",NotDoubleVerticalBar:"âˆĻ",NotElement:"∉",NotEqual:"≠",NotEqualTilde:"â‰‚Ė¸",NotExists:"∄",NotGreater:"≯",NotGreaterEqual:"≱",NotGreaterFullEqual:"â‰§Ė¸",NotGreaterGreater:"â‰Ģˏ",NotGreaterLess:"≹",NotGreaterSlantEqual:"âŠžĖ¸",NotGreaterTilde:"â‰ĩ",NotHumpDownHump:"â‰ŽĖ¸",NotHumpEqual:"â‰Ė¸",notin:"∉",notindot:"â‹ĩˏ",notinE:"â‹šĖ¸",notinva:"∉",notinvb:"⋷",notinvc:"â‹ļ",NotLeftTriangle:"â‹Ē",NotLeftTriangleBar:"â§Ė¸",NotLeftTriangleEqual:"â‹Ŧ",NotLess:"≮",NotLessEqual:"≰",NotLessGreater:"≸",NotLessLess:"â‰Ēˏ",NotLessSlantEqual:"âŠŊˏ",NotLessTilde:"≴",NotNestedGreaterGreater:"âĒĸˏ",NotNestedLessLess:"âĒĄĖ¸",notni:"∌",notniva:"∌",notnivb:"⋾",notnivc:"â‹Ŋ",NotPrecedes:"⊀",NotPrecedesEqual:"âǝˏ",NotPrecedesSlantEqual:"⋠",NotReverseElement:"∌",NotRightTriangle:"â‹Ģ",NotRightTriangleBar:"â§Ė¸",NotRightTriangleEqual:"⋭",NotSquareSubset:"âŠĖ¸",NotSquareSubsetEqual:"â‹ĸ",NotSquareSuperset:"âŠĖ¸",NotSquareSupersetEqual:"â‹Ŗ",NotSubset:"⊂⃒",NotSubsetEqual:"⊈",NotSucceeds:"⊁",NotSucceedsEqual:"âǰˏ",NotSucceedsSlantEqual:"⋡",NotSucceedsTilde:"â‰ŋˏ",NotSuperset:"⊃⃒",NotSupersetEqual:"⊉",NotTilde:"≁",NotTildeEqual:"≄",NotTildeFullEqual:"≇",NotTildeTilde:"≉",NotVerticalBar:"∤",npar:"âˆĻ",nparallel:"âˆĻ",nparsl:"âĢŊâƒĨ",npart:"âˆ‚Ė¸",npolint:"⨔",npr:"⊀",nprcue:"⋠",npre:"âǝˏ",nprec:"⊀",npreceq:"âǝˏ",nrarr:"↛",nrArr:"⇏",nrarrc:"â¤ŗĖ¸",nrarrw:"â†Ė¸",nrightarrow:"↛",nRightarrow:"⇏",nrtri:"â‹Ģ",nrtrie:"⋭",nsc:"⊁",nsccue:"⋡",nsce:"âǰˏ",nscr:"𝓃",Nscr:"𝒩",nshortmid:"∤",nshortparallel:"âˆĻ",nsim:"≁",nsime:"≄",nsimeq:"≄",nsmid:"∤",nspar:"âˆĻ",nsqsube:"â‹ĸ",nsqsupe:"â‹Ŗ",nsub:"⊄",nsube:"⊈",nsubE:"â̅ˏ",nsubset:"⊂⃒",nsubseteq:"⊈",nsubseteqq:"â̅ˏ",nsucc:"⊁",nsucceq:"âǰˏ",nsup:"⊅",nsupe:"⊉",nsupE:"â̆ˏ",nsupset:"⊃⃒",nsupseteq:"⊉",nsupseteqq:"â̆ˏ",ntgl:"≹",ntilde:"Ãą",Ntilde:"Ñ",ntlg:"≸",ntriangleleft:"â‹Ē",ntrianglelefteq:"â‹Ŧ",ntriangleright:"â‹Ģ",ntrianglerighteq:"⋭",nu:"ÎŊ",Nu:"Ν",num:"#",numero:"№",numsp:" ",nvap:"≍⃒",nvdash:"âŠŦ",nvDash:"⊭",nVdash:"⊮",nVDash:"⊯",nvge:"â‰Ĩ⃒",nvgt:">⃒",nvHarr:"⤄",nvinfin:"⧞",nvlArr:"⤂",nvle:"≤⃒",nvlt:"<⃒",nvltrie:"⊴⃒",nvrArr:"⤃",nvrtrie:"âŠĩ⃒",nvsim:"âˆŧ⃒",nwarhk:"â¤Ŗ",nwarr:"↖",nwArr:"⇖",nwarrow:"↖",nwnear:"⤧",oacute:"Ãŗ",Oacute:"Ó",oast:"⊛",ocir:"⊚",ocirc:"ô",Ocirc:"Ô",ocy:"Đž",Ocy:"О",odash:"⊝",odblac:"ő",Odblac:"Ő",odiv:"⨸",odot:"⊙",odsold:"âĻŧ",oelig:"œ",OElig:"Œ",ofcir:"âĻŋ",ofr:"đ”Ŧ",Ofr:"𝔒",ogon:"˛",ograve:"Ã˛",Ograve:"Ò",ogt:"⧁",ohbar:"âĻĩ",ohm:"Ί",oint:"∎",olarr:"â†ē",olcir:"âĻž",olcross:"âĻģ",oline:"‾",olt:"⧀",omacr:"ō",Omacr:"Ō",omega:"Ή",Omega:"Ί",omicron:"Îŋ",Omicron:"Ο",omid:"âĻļ",ominus:"⊖",oopf:"𝕠",Oopf:"𝕆",opar:"âώ",OpenCurlyDoubleQuote:"“",OpenCurlyQuote:"‘",operp:"âĻš",oplus:"⊕",or:"∨",Or:"⩔",orarr:"â†ģ",ord:"⊝",order:"ℴ",orderof:"ℴ",ordf:"ÂĒ",ordm:"Âē",origof:"âŠļ",oror:"⩖",orslope:"⩗",orv:"⩛",oS:"Ⓢ",oscr:"ℴ",Oscr:"đ’Ē",oslash:"ø",Oslash:"Ø",osol:"⊘",otilde:"Ãĩ",Otilde:"Õ",otimes:"⊗",Otimes:"⨡",otimesas:"â¨ļ",ouml:"Ãļ",Ouml:"Ö",ovbar:"âŒŊ",OverBar:"‾",OverBrace:"⏞",OverBracket:"⎴",OverParenthesis:"⏜",par:"âˆĨ",para:"Âļ",parallel:"âˆĨ",parsim:"âĢŗ",parsl:"âĢŊ",part:"∂",PartialD:"∂",pcy:"Đŋ",Pcy:"П",percnt:"%",period:".",permil:"‰",perp:"âŠĨ",pertenk:"‱",pfr:"𝔭",Pfr:"𝔓",phi:"Ά",Phi:"ÎĻ",phiv:"Ī•",phmmat:"â„ŗ",phone:"☎",pi:"Ī€",Pi:"Π",pitchfork:"⋔",piv:"Ī–",planck:"ℏ",planckh:"ℎ",plankv:"ℏ",plus:"+",plusacir:"â¨Ŗ",plusb:"⊞",pluscir:"â¨ĸ",plusdo:"∔",plusdu:"â¨Ĩ",pluse:"⊲",PlusMinus:"Âą",plusmn:"Âą",plussim:"â¨Ļ",plustwo:"⨧",pm:"Âą",Poincareplane:"ℌ",pointint:"⨕",popf:"𝕡",Popf:"ℙ",pound:"ÂŖ",pr:"â‰ē",Pr:"âĒģ",prap:"âǎ",prcue:"â‰ŧ",pre:"âǝ",prE:"âĒŗ",prec:"â‰ē",precapprox:"âǎ",preccurlyeq:"â‰ŧ",Precedes:"â‰ē",PrecedesEqual:"âǝ",PrecedesSlantEqual:"â‰ŧ",PrecedesTilde:"≾",preceq:"âǝ",precnapprox:"âĒš",precneqq:"âĒĩ",precnsim:"⋨",precsim:"≾",prime:"′",Prime:"â€ŗ",primes:"ℙ",prnap:"âĒš",prnE:"âĒĩ",prnsim:"⋨",prod:"∏",Product:"∏",profalar:"⌮",profline:"⌒",profsurf:"⌓",prop:"∝",Proportion:"∡",Proportional:"∝",propto:"∝",prsim:"≾",prurel:"⊰",pscr:"𝓅",Pscr:"đ’Ģ",psi:"Έ",Psi:"Ψ",puncsp:" ",qfr:"𝔮",Qfr:"𝔔",qint:"⨌",qopf:"đ•ĸ",Qopf:"ℚ",qprime:"⁗",qscr:"𝓆",Qscr:"đ’Ŧ",quaternions:"ℍ",quatint:"⨖",quest:"?",questeq:"≟",quot:'"',QUOT:'"',rAarr:"⇛",race:"âˆŊĖą",racute:"ŕ",Racute:"Ŕ",radic:"√",raemptyv:"âĻŗ",rang:"⟩",Rang:"âŸĢ",rangd:"âĻ’",range:"âĻĨ",rangle:"⟩",raquo:"Âģ",rarr:"→",rArr:"⇒",Rarr:"↠",rarrap:"âĨĩ",rarrb:"â‡Ĩ",rarrbfs:"⤠",rarrc:"â¤ŗ",rarrfs:"⤞",rarrhk:"â†Ē",rarrlp:"â†Ŧ",rarrpl:"âĨ…",rarrsim:"âĨ´",rarrtl:"â†Ŗ",Rarrtl:"⤖",rarrw:"↝",ratail:"⤚",rAtail:"⤜",ratio:"âˆļ",rationals:"ℚ",rbarr:"⤍",rBarr:"⤏",RBarr:"⤐",rbbrk:"âŗ",rbrace:"}",rbrack:"]",rbrke:"âό",rbrksld:"âĻŽ",rbrkslu:"âϐ",rcaron:"ř",Rcaron:"Ř",rcedil:"ŗ",Rcedil:"Ŗ",rceil:"⌉",rcub:"}",rcy:"Ņ€",Rcy:"Đ ",rdca:"⤡",rdldhar:"âĨŠ",rdquo:"”",rdquor:"”",rdsh:"â†ŗ",Re:"ℜ",real:"ℜ",realine:"ℛ",realpart:"ℜ",reals:"ℝ",rect:"▭",reg:"ÂŽ",REG:"ÂŽ",ReverseElement:"∋",ReverseEquilibrium:"⇋",ReverseUpEquilibrium:"âĨ¯",rfisht:"âĨŊ",rfloor:"⌋",rfr:"đ”¯",Rfr:"ℜ",rHar:"âĨ¤",rhard:"⇁",rharu:"⇀",rharul:"âĨŦ",rho:"΁",Rho:"ÎĄ",rhov:"Īą",RightAngleBracket:"⟩",rightarrow:"→",Rightarrow:"⇒",RightArrow:"→",RightArrowBar:"â‡Ĩ",RightArrowLeftArrow:"⇄",rightarrowtail:"â†Ŗ",RightCeiling:"⌉",RightDoubleBracket:"⟧",RightDownTeeVector:"âĨ",RightDownVector:"⇂",RightDownVectorBar:"âĨ•",RightFloor:"⌋",rightharpoondown:"⇁",rightharpoonup:"⇀",rightleftarrows:"⇄",rightleftharpoons:"⇌",rightrightarrows:"⇉",rightsquigarrow:"↝",RightTee:"âŠĸ",RightTeeArrow:"â†Ļ",RightTeeVector:"âĨ›",rightthreetimes:"⋌",RightTriangle:"âŠŗ",RightTriangleBar:"⧐",RightTriangleEqual:"âŠĩ",RightUpDownVector:"âĨ",RightUpTeeVector:"âĨœ",RightUpVector:"↾",RightUpVectorBar:"âĨ”",RightVector:"⇀",RightVectorBar:"âĨ“",ring:"˚",risingdotseq:"≓",rlarr:"⇄",rlhar:"⇌",rlm:"‏",rmoust:"⎱",rmoustache:"⎱",rnmid:"âĢŽ",roang:"⟭",roarr:"⇾",robrk:"⟧",ropar:"âφ",ropf:"đ•Ŗ",Ropf:"ℝ",roplus:"⨎",rotimes:"â¨ĩ",RoundImplies:"âĨ°",rpar:")",rpargt:"âĻ”",rppolint:"⨒",rrarr:"⇉",Rrightarrow:"⇛",rsaquo:"â€ē",rscr:"𝓇",Rscr:"ℛ",rsh:"↱",Rsh:"↱",rsqb:"]",rsquo:"’",rsquor:"’",rthree:"⋌",rtimes:"⋊",rtri:"▹",rtrie:"âŠĩ",rtrif:"▸",rtriltri:"⧎",RuleDelayed:"â§´",ruluhar:"âĨ¨",rx:"℞",sacute:"ś",Sacute:"Ś",sbquo:"‚",sc:"â‰ģ",Sc:"âĒŧ",scap:"âǏ",scaron:"ÅĄ",Scaron:"Å ",sccue:"â‰Ŋ",sce:"âǰ",scE:"âĒ´",scedil:"ş",Scedil:"Ş",scirc:"ŝ",Scirc:"Ŝ",scnap:"âĒē",scnE:"âĒļ",scnsim:"⋩",scpolint:"⨓",scsim:"â‰ŋ",scy:"ҁ",Scy:"ĐĄ",sdot:"⋅",sdotb:"⊡",sdote:"âŠĻ",searhk:"â¤Ĩ",searr:"↘",seArr:"⇘",searrow:"↘",sect:"§",semi:";",seswar:"⤊",setminus:"∖",setmn:"∖",sext:"âœļ",sfr:"𝔰",Sfr:"𝔖",sfrown:"âŒĸ",sharp:"♯",shchcy:"҉",SHCHcy:"ĐŠ",shcy:"҈",SHcy:"Ш",ShortDownArrow:"↓",ShortLeftArrow:"←",shortmid:"âˆŖ",shortparallel:"âˆĨ",ShortRightArrow:"→",ShortUpArrow:"↑",shy:"­",sigma:"΃",Sigma:"ÎŖ",sigmaf:"Ī‚",sigmav:"Ī‚",sim:"âˆŧ",simdot:"âŠĒ",sime:"≃",simeq:"≃",simg:"âĒž",simgE:"âĒ ",siml:"âĒ",simlE:"âǟ",simne:"≆",simplus:"⨤",simrarr:"âĨ˛",slarr:"←",SmallCircle:"∘",smallsetminus:"∖",smashp:"â¨ŗ",smeparsl:"⧤",smid:"âˆŖ",smile:"âŒŖ",smt:"âĒĒ",smte:"âĒŦ",smtes:"âĒŦ",softcy:"Ҍ",SOFTcy:"ĐŦ",sol:"/",solb:"⧄",solbar:"âŒŋ",sopf:"𝕤",Sopf:"𝕊",spades:"♠",spadesuit:"♠",spar:"âˆĨ",sqcap:"⊓",sqcaps:"âŠ“ī¸€",sqcup:"⊔",sqcups:"âŠ”ī¸€",Sqrt:"√",sqsub:"⊏",sqsube:"⊑",sqsubset:"⊏",sqsubseteq:"⊑",sqsup:"⊐",sqsupe:"⊒",sqsupset:"⊐",sqsupseteq:"⊒",squ:"□",square:"□",Square:"□",SquareIntersection:"⊓",SquareSubset:"⊏",SquareSubsetEqual:"⊑",SquareSuperset:"⊐",SquareSupersetEqual:"⊒",SquareUnion:"⊔",squarf:"â–Ē",squf:"â–Ē",srarr:"→",sscr:"𝓈",Sscr:"𝒮",ssetmn:"∖",ssmile:"âŒŖ",sstarf:"⋆",star:"☆",Star:"⋆",starf:"★",straightepsilon:"Īĩ",straightphi:"Ī•",strns:"¯",sub:"⊂",Sub:"⋐",subdot:"âĒŊ",sube:"⊆",subE:"âĢ…",subedot:"ẫ",submult:"ấ",subne:"⊊",subnE:"âĢ‹",subplus:"âĒŋ",subrarr:"âĨš",subset:"⊂",Subset:"⋐",subseteq:"⊆",subseteqq:"âĢ…",SubsetEqual:"⊆",subsetneq:"⊊",subsetneqq:"âĢ‹",subsim:"â̇",subsub:"âĢ•",subsup:"âĢ“",succ:"â‰ģ",succapprox:"âǏ",succcurlyeq:"â‰Ŋ",Succeeds:"â‰ģ",SucceedsEqual:"âǰ",SucceedsSlantEqual:"â‰Ŋ",SucceedsTilde:"â‰ŋ",succeq:"âǰ",succnapprox:"âĒē",succneqq:"âĒļ",succnsim:"⋩",succsim:"â‰ŋ",SuchThat:"∋",sum:"∑",Sum:"∑",sung:"â™Ē",sup:"⊃",Sup:"⋑",sup1:"š",sup2:"²",sup3:"Âŗ",supdot:"âĒž",supdsub:"â̘",supe:"⊇",supE:"â̆",supedot:"âĢ„",Superset:"⊃",SupersetEqual:"⊇",suphsol:"⟉",suphsub:"âĢ—",suplarr:"âĨģ",supmult:"âĢ‚",supne:"⊋",supnE:"â̌",supplus:"âĢ€",supset:"⊃",Supset:"⋑",supseteq:"⊇",supseteqq:"â̆",supsetneq:"⊋",supsetneqq:"â̌",supsim:"â̈",supsub:"âĢ”",supsup:"âĢ–",swarhk:"â¤Ļ",swarr:"↙",swArr:"⇙",swarrow:"↙",swnwar:"â¤Ē",szlig:"ß",Tab:"\t",target:"⌖",tau:"Ī„",Tau:"Τ",tbrk:"⎴",tcaron:"ÅĨ",Tcaron:"Ť",tcedil:"ÅŖ",Tcedil:"Åĸ",tcy:"Ņ‚",Tcy:"Đĸ",tdot:"⃛",telrec:"⌕",tfr:"𝔱",Tfr:"𝔗",there4:"∴",therefore:"∴",Therefore:"∴",theta:"θ",Theta:"Θ",thetasym:"Ī‘",thetav:"Ī‘",thickapprox:"≈",thicksim:"âˆŧ",ThickSpace:"  ",thinsp:" ",ThinSpace:" ",thkap:"≈",thksim:"âˆŧ",thorn:"Þ",THORN:"Þ",tilde:"˜",Tilde:"âˆŧ",TildeEqual:"≃",TildeFullEqual:"≅",TildeTilde:"≈",times:"×",timesb:"⊠",timesbar:"⨹",timesd:"⨰",tint:"∭",toea:"⤨",top:"⊤",topbot:"âŒļ",topcir:"âĢą",topf:"đ•Ĩ",Topf:"𝕋",topfork:"â̚",tosa:"⤊",tprime:"‴",trade:"â„ĸ",TRADE:"â„ĸ",triangle:"â–ĩ",triangledown:"â–ŋ",triangleleft:"◃",trianglelefteq:"⊴",triangleq:"≜",triangleright:"▹",trianglerighteq:"âŠĩ",tridot:"â—Ŧ",trie:"≜",triminus:"â¨ē",TripleDot:"⃛",triplus:"⨚",trisb:"⧍",tritime:"â¨ģ",trpezium:"âĸ",tscr:"𝓉",Tscr:"đ’¯",tscy:"҆",TScy:"ĐĻ",tshcy:"Ņ›",TSHcy:"Ћ",tstrok:"ŧ",Tstrok:"ÅĻ",twixt:"â‰Ŧ",twoheadleftarrow:"↞",twoheadrightarrow:"↠",uacute:"Ãē",Uacute:"Ú",uarr:"↑",uArr:"⇑",Uarr:"↟",Uarrocir:"âĨ‰",ubrcy:"Ņž",Ubrcy:"Ў",ubreve:"Å­",Ubreve:"ÅŦ",ucirc:"Ãģ",Ucirc:"Û",ucy:"҃",Ucy:"ĐŖ",udarr:"⇅",udblac:"Åą",Udblac:"Ű",udhar:"âĨŽ",ufisht:"âĨž",ufr:"𝔲",Ufr:"𝔘",ugrave:"Ú",Ugrave:"Ù",uHar:"âĨŖ",uharl:"â†ŋ",uharr:"↾",uhblk:"▀",ulcorn:"⌜",ulcorner:"⌜",ulcrop:"⌏",ultri:"◸",umacr:"ÅĢ",Umacr:"ÅĒ",uml:"¨",UnderBar:"_",UnderBrace:"⏟",UnderBracket:"âŽĩ",UnderParenthesis:"⏝",Union:"⋃",UnionPlus:"⊎",uogon:"Åŗ",Uogon:"Ş",uopf:"đ•Ļ",Uopf:"𝕌",uparrow:"↑",Uparrow:"⇑",UpArrow:"↑",UpArrowBar:"⤒",UpArrowDownArrow:"⇅",updownarrow:"↕",Updownarrow:"⇕",UpDownArrow:"↕",UpEquilibrium:"âĨŽ",upharpoonleft:"â†ŋ",upharpoonright:"↾",uplus:"⊎",UpperLeftArrow:"↖",UpperRightArrow:"↗",upsi:"Ī…",Upsi:"Ī’",upsih:"Ī’",upsilon:"Ī…",Upsilon:"ÎĨ",UpTee:"âŠĨ",UpTeeArrow:"â†Ĩ",upuparrows:"⇈",urcorn:"⌝",urcorner:"⌝",urcrop:"⌎",uring:"ů",Uring:"ÅŽ",urtri:"◹",uscr:"𝓊",Uscr:"𝒰",utdot:"⋰",utilde:"ÅŠ",Utilde:"Ũ",utri:"â–ĩ",utrif:"▴",uuarr:"⇈",uuml:"Ãŧ",Uuml:"Ü",uwangle:"âϧ",vangrt:"âϜ",varepsilon:"Īĩ",varkappa:"ΰ",varnothing:"∅",varphi:"Ī•",varpi:"Ī–",varpropto:"∝",varr:"↕",vArr:"⇕",varrho:"Īą",varsigma:"Ī‚",varsubsetneq:"âŠŠī¸€",varsubsetneqq:"â̋",varsupsetneq:"âŠ‹ī¸€",varsupsetneqq:"âĢŒī¸€",vartheta:"Ī‘",vartriangleleft:"⊲",vartriangleright:"âŠŗ",vBar:"â̍",Vbar:"âĢĢ",vBarv:"âĢŠ",vcy:"в",Vcy:"В",vdash:"âŠĸ",vDash:"⊨",Vdash:"⊩",VDash:"âŠĢ",Vdashl:"âĢĻ",vee:"∨",Vee:"⋁",veebar:"âŠģ",veeeq:"≚",vellip:"⋮",verbar:"|",Verbar:"‖",vert:"|",Vert:"‖",VerticalBar:"âˆŖ",VerticalLine:"|",VerticalSeparator:"❘",VerticalTilde:"≀",VeryThinSpace:" ",vfr:"đ”ŗ",Vfr:"𝔙",vltri:"⊲",vnsub:"⊂⃒",vnsup:"⊃⃒",vopf:"𝕧",Vopf:"𝕍",vprop:"∝",vrtri:"âŠŗ",vscr:"𝓋",Vscr:"𝒱",vsubne:"âŠŠī¸€",vsubnE:"â̋",vsupne:"âŠ‹ī¸€",vsupnE:"âĢŒī¸€",Vvdash:"âŠĒ",vzigzag:"âϚ",wcirc:"Åĩ",Wcirc:"Å´",wedbar:"⩟",wedge:"∧",Wedge:"⋀",wedgeq:"≙",weierp:"℘",wfr:"𝔴",Wfr:"𝔚",wopf:"𝕨",Wopf:"𝕎",wp:"℘",wr:"≀",wreath:"≀",wscr:"𝓌",Wscr:"𝒲",xcap:"⋂",xcirc:"◯",xcup:"⋃",xdtri:"â–Ŋ",xfr:"đ”ĩ",Xfr:"𝔛",xharr:"⟷",xhArr:"âŸē",xi:"Ξ",Xi:"Ξ",xlarr:"âŸĩ",xlArr:"⟸",xmap:"âŸŧ",xnis:"â‹ģ",xodot:"⨀",xopf:"𝕩",Xopf:"𝕏",xoplus:"⨁",xotime:"⨂",xrarr:"âŸļ",xrArr:"⟹",xscr:"𝓍",Xscr:"đ’ŗ",xsqcup:"⨆",xuplus:"⨄",xutri:"â–ŗ",xvee:"⋁",xwedge:"⋀",yacute:"ÃŊ",Yacute:"Ý",yacy:"Ņ",YAcy:"Đ¯",ycirc:"Ŏ",Ycirc:"Åļ",ycy:"Ņ‹",Ycy:"ĐĢ",yen:"ÂĨ",yfr:"đ”ļ",Yfr:"𝔜",yicy:"Ņ—",YIcy:"Ї",yopf:"đ•Ē",Yopf:"𝕐",yscr:"𝓎",Yscr:"𝒴",yucy:"ŅŽ",YUcy:"ĐŽ",yuml:"Ãŋ",Yuml:"Ÿ",zacute:"Åē",Zacute:"Åš",zcaron:"Åž",Zcaron:"ÅŊ",zcy:"С",Zcy:"З",zdot:"Åŧ",Zdot:"Åģ",zeetrf:"ℨ",ZeroWidthSpace:"​",zeta:"Îļ",Zeta:"Ζ",zfr:"𝔷",Zfr:"ℨ",zhcy:"Đļ",ZHcy:"Ж",zigrarr:"⇝",zopf:"đ•Ģ",Zopf:"ℤ",zscr:"𝓏",Zscr:"đ’ĩ",zwj:"‍",zwnj:"‌"},b={aacute:"ÃĄ",Aacute:"Á",acirc:"Ãĸ",Acirc:"Â",acute:"´",aelig:"ÃĻ",AElig:"Æ",agrave:"à",Agrave:"À",amp:"&",AMP:"&",aring:"ÃĨ",Aring:"Å",atilde:"ÃŖ",Atilde:"Ã",auml:"ä",Auml:"Ä",brvbar:"ÂĻ",ccedil:"ç",Ccedil:"Ç",cedil:"¸",cent:"Âĸ",copy:"Š",COPY:"Š",curren:"¤",deg:"°",divide:"Ãˇ",eacute:"Ê",Eacute:"É",ecirc:"ÃĒ",Ecirc:"Ê",egrave:"è",Egrave:"È",eth:"ð",ETH:"Ð",euml:"ÃĢ",Euml:"Ë",frac12:"ÂŊ",frac14:"Âŧ",frac34:"ž",gt:">",GT:">",iacute:"í",Iacute:"Í",icirc:"ÃŽ",Icirc:"Î",iexcl:"ÂĄ",igrave:"ÃŦ",Igrave:"Ì",iquest:"Âŋ",iuml:"ï",Iuml:"Ï",laquo:"ÂĢ",lt:"<",LT:"<",macr:"¯",micro:"Âĩ",middot:"¡",nbsp:" ",not:"ÂŦ",ntilde:"Ãą",Ntilde:"Ñ",oacute:"Ãŗ",Oacute:"Ó",ocirc:"ô",Ocirc:"Ô",ograve:"Ã˛",Ograve:"Ò",ordf:"ÂĒ",ordm:"Âē",oslash:"ø",Oslash:"Ø",otilde:"Ãĩ",Otilde:"Õ",ouml:"Ãļ",Ouml:"Ö",para:"Âļ",plusmn:"Âą",pound:"ÂŖ",quot:'"',QUOT:'"',raquo:"Âģ",reg:"ÂŽ",REG:"ÂŽ",sect:"§",shy:"­",sup1:"š",sup2:"²",sup3:"Âŗ",szlig:"ß",thorn:"Þ",THORN:"Þ",times:"×",uacute:"Ãē",Uacute:"Ú",ucirc:"Ãģ",Ucirc:"Û",ugrave:"Ú",Ugrave:"Ù",uml:"¨",uuml:"Ãŧ",Uuml:"Ü",yacute:"ÃŊ",Yacute:"Ý",yen:"ÂĨ",yuml:"Ãŋ"},y={0:"īŋŊ",128:"â‚Ŧ",130:"‚",131:"ƒ",132:"„",133:"â€Ļ",134:"†",135:"‡",136:"ˆ",137:"‰",138:"Å ",139:"‹",140:"Œ",142:"ÅŊ",145:"‘",146:"’",147:"“",148:"”",149:"â€ĸ",150:"–",151:"—",152:"˜",153:"â„ĸ",154:"ÅĄ",155:"â€ē",156:"œ",158:"Åž",159:"Ÿ"},w=[1,2,3,4,5,6,7,8,11,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,64976,64977,64978,64979,64980,64981,64982,64983,64984,64985,64986,64987,64988,64989,64990,64991,64992,64993,64994,64995,64996,64997,64998,64999,65e3,65001,65002,65003,65004,65005,65006,65007,65534,65535,131070,131071,196606,196607,262142,262143,327678,327679,393214,393215,458750,458751,524286,524287,589822,589823,655358,655359,720894,720895,786430,786431,851966,851967,917502,917503,983038,983039,1048574,1048575,1114110,1114111],_=String.fromCharCode,x={}.hasOwnProperty,A=function(e,t){return x.call(e,t)},E=function(e,t){if(!e)return t;var r,n={};for(r in t)n[r]=A(e,r)?e[r]:t[r];return n},D=function(e,t){var r="";return e>=55296&&e<=57343||e>1114111?(t&&q("character reference outside the permissible Unicode range"),"īŋŊ"):A(y,e)?(t&&q("disallowed character reference"),y[e]):(t&&function(e,t){for(var r=-1,n=e.length;++r65535&&(r+=_((e-=65536)>>>10&1023|55296),e=56320|1023&e),r+=_(e))},T=function(e){return"&#x"+e.toString(16).toUpperCase()+";"},C=function(e){return"&#"+e+";"},q=function(e){throw Error("Parse error: "+e)},k=function(e,t){(t=E(t,k.options)).strict&&g.test(e)&&q("forbidden code point");var r=t.encodeEverything,n=t.useNamedReferences,i=t.allowUnsafeSymbols,o=t.decimal?C:T,s=function(e){return o(e.charCodeAt(0))};return r?(e=e.replace(l,(function(e){return n&&A(p,e)?"&"+p[e]+";":s(e)})),n&&(e=e.replace(/>\u20D2/g,">⃒").replace(/<\u20D2/g,"<⃒").replace(/fj/g,"fj")),n&&(e=e.replace(u,(function(e){return"&"+p[e]+";"})))):n?(i||(e=e.replace(f,(function(e){return"&"+p[e]+";"}))),e=(e=e.replace(/>\u20D2/g,">⃒").replace(/<\u20D2/g,"<⃒")).replace(u,(function(e){return"&"+p[e]+";"}))):i||(e=e.replace(f,s)),e.replace(a,(function(e){var t=e.charCodeAt(0),r=e.charCodeAt(1);return o(1024*(t-55296)+r-56320+65536)})).replace(c,s)};k.options={allowUnsafeSymbols:!1,encodeEverything:!1,strict:!1,useNamedReferences:!1,decimal:!1};var L=function(e,t){var r=(t=E(t,L.options)).strict;return r&&h.test(e)&&q("malformed character reference"),e.replace(m,(function(e,n,i,o,s,a,l,c,u){var p,f,d,h,g,m;return n?v[g=n]:i?(g=i,(m=o)&&t.isAttributeValue?(r&&"="==m&&q("`&` did not start a character reference"),e):(r&&q("named character reference was not terminated by a semicolon"),b[g]+(m||""))):s?(d=s,f=a,r&&!f&&q("character reference was not terminated by a semicolon"),p=parseInt(d,10),D(p,r)):l?(h=l,f=c,r&&!f&&q("character reference was not terminated by a semicolon"),p=parseInt(h,16),D(p,r)):(r&&q("named character reference was not terminated by a semicolon"),e)}))};L.options={isAttributeValue:!1,strict:!1};var S={version:"1.2.0",encode:k,decode:L,escape:function(e){return e.replace(f,(function(e){return d[e]}))},unescape:L};void 0===(n=function(){return S}.call(t,r,t,e))||(e.exports=n)}()},755:function(e,t){var r;!function(t,r){"use strict";"object"==typeof e.exports?e.exports=t.document?r(t,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return r(e)}:r(t)}("undefined"!=typeof window?window:this,(function(n,i){"use strict";var o=[],s=Object.getPrototypeOf,a=o.slice,l=o.flat?function(e){return o.flat.call(e)}:function(e){return o.concat.apply([],e)},c=o.push,u=o.indexOf,p={},f=p.toString,d=p.hasOwnProperty,h=d.toString,g=h.call(Object),m={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},b=function(e){return null!=e&&e===e.window},y=n.document,w={type:!0,src:!0,nonce:!0,noModule:!0};function _(e,t,r){var n,i,o=(r=r||y).createElement("script");if(o.text=e,t)for(n in w)(i=t[n]||t.getAttribute&&t.getAttribute(n))&&o.setAttribute(n,i);r.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?p[f.call(e)]||"object":typeof e}var A="3.6.4",E=function(e,t){return new E.fn.init(e,t)};function D(e){var t=!!e&&"length"in e&&e.length,r=x(e);return!v(e)&&!b(e)&&("array"===r||0===t||"number"==typeof t&&t>0&&t-1 in e)}E.fn=E.prototype={jquery:A,constructor:E,length:0,toArray:function(){return a.call(this)},get:function(e){return null==e?a.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=E.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return E.each(this,e)},map:function(e){return this.pushStack(E.map(this,(function(t,r){return e.call(t,r,t)})))},slice:function(){return this.pushStack(a.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(E.grep(this,(function(e,t){return(t+1)%2})))},odd:function(){return this.pushStack(E.grep(this,(function(e,t){return t%2})))},eq:function(e){var t=this.length,r=+e+(e<0?t:0);return this.pushStack(r>=0&&r+~]|"+I+")"+I+"*"),z=new RegExp(I+"|>"),G=new RegExp(H),W=new RegExp("^"+P+"$"),Y={ID:new RegExp("^#("+P+")"),CLASS:new RegExp("^\\.("+P+")"),TAG:new RegExp("^("+P+"|[*])"),ATTR:new RegExp("^"+F),PSEUDO:new RegExp("^"+H),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+I+"*(even|odd|(([+-]|)(\\d*)n|)"+I+"*(?:([+-]|)"+I+"*(\\d+)|))"+I+"*\\)|)","i"),bool:new RegExp("^(?:"+B+")$","i"),needsContext:new RegExp("^"+I+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+I+"*((?:-\\d)?\\d*)"+I+"*\\)|)(?=[^-]|$)","i")},X=/HTML$/i,K=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,Q=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+I+"?|\\\\([^\\r\\n\\f])","g"),re=function(e,t){var r="0x"+e.slice(1)-65536;return t||(r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320))},ne=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"īŋŊ":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){f()},se=we((function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()}),{dir:"parentNode",next:"legend"});try{O.apply(L=j.call(_.childNodes),_.childNodes),L[_.childNodes.length].nodeType}catch(e){O={apply:L.length?function(e,t){N.apply(e,j.call(t))}:function(e,t){for(var r=e.length,n=0;e[r++]=t[n++];);e.length=r-1}}}function ae(e,t,n,i){var o,a,c,u,p,h,v,b=t&&t.ownerDocument,_=t?t.nodeType:9;if(n=n||[],"string"!=typeof e||!e||1!==_&&9!==_&&11!==_)return n;if(!i&&(f(t),t=t||d,g)){if(11!==_&&(p=Z.exec(e)))if(o=p[1]){if(9===_){if(!(c=t.getElementById(o)))return n;if(c.id===o)return n.push(c),n}else if(b&&(c=b.getElementById(o))&&y(t,c)&&c.id===o)return n.push(c),n}else{if(p[2])return O.apply(n,t.getElementsByTagName(e)),n;if((o=p[3])&&r.getElementsByClassName&&t.getElementsByClassName)return O.apply(n,t.getElementsByClassName(o)),n}if(r.qsa&&!C[e+" "]&&(!m||!m.test(e))&&(1!==_||"object"!==t.nodeName.toLowerCase())){if(v=e,b=t,1===_&&(z.test(e)||$.test(e))){for((b=ee.test(e)&&ve(t.parentNode)||t)===t&&r.scope||((u=t.getAttribute("id"))?u=u.replace(ne,ie):t.setAttribute("id",u=w)),a=(h=s(e)).length;a--;)h[a]=(u?"#"+u:":scope")+" "+ye(h[a]);v=h.join(",")}try{return O.apply(n,b.querySelectorAll(v)),n}catch(t){C(e,!0)}finally{u===w&&t.removeAttribute("id")}}}return l(e.replace(U,"$1"),t,n,i)}function le(){var e=[];return function t(r,i){return e.push(r+" ")>n.cacheLength&&delete t[e.shift()],t[r+" "]=i}}function ce(e){return e[w]=!0,e}function ue(e){var t=d.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function pe(e,t){for(var r=e.split("|"),i=r.length;i--;)n.attrHandle[r[i]]=t}function fe(e,t){var r=t&&e,n=r&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(n)return n;if(r)for(;r=r.nextSibling;)if(r===t)return-1;return e?1:-1}function de(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function he(e){return function(t){var r=t.nodeName.toLowerCase();return("input"===r||"button"===r)&&t.type===e}}function ge(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&se(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function me(e){return ce((function(t){return t=+t,ce((function(r,n){for(var i,o=e([],r.length,t),s=o.length;s--;)r[i=o[s]]&&(r[i]=!(n[i]=r[i]))}))}))}function ve(e){return e&&void 0!==e.getElementsByTagName&&e}for(t in r=ae.support={},o=ae.isXML=function(e){var t=e&&e.namespaceURI,r=e&&(e.ownerDocument||e).documentElement;return!X.test(t||r&&r.nodeName||"HTML")},f=ae.setDocument=function(e){var t,i,s=e?e.ownerDocument||e:_;return s!=d&&9===s.nodeType&&s.documentElement?(h=(d=s).documentElement,g=!o(d),_!=d&&(i=d.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",oe,!1):i.attachEvent&&i.attachEvent("onunload",oe)),r.scope=ue((function(e){return h.appendChild(e).appendChild(d.createElement("div")),void 0!==e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length})),r.cssHas=ue((function(){try{return d.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}})),r.attributes=ue((function(e){return e.className="i",!e.getAttribute("className")})),r.getElementsByTagName=ue((function(e){return e.appendChild(d.createComment("")),!e.getElementsByTagName("*").length})),r.getElementsByClassName=Q.test(d.getElementsByClassName),r.getById=ue((function(e){return h.appendChild(e).id=w,!d.getElementsByName||!d.getElementsByName(w).length})),r.getById?(n.filter.ID=function(e){var t=e.replace(te,re);return function(e){return e.getAttribute("id")===t}},n.find.ID=function(e,t){if(void 0!==t.getElementById&&g){var r=t.getElementById(e);return r?[r]:[]}}):(n.filter.ID=function(e){var t=e.replace(te,re);return function(e){var r=void 0!==e.getAttributeNode&&e.getAttributeNode("id");return r&&r.value===t}},n.find.ID=function(e,t){if(void 0!==t.getElementById&&g){var r,n,i,o=t.getElementById(e);if(o){if((r=o.getAttributeNode("id"))&&r.value===e)return[o];for(i=t.getElementsByName(e),n=0;o=i[n++];)if((r=o.getAttributeNode("id"))&&r.value===e)return[o]}return[]}}),n.find.TAG=r.getElementsByTagName?function(e,t){return void 0!==t.getElementsByTagName?t.getElementsByTagName(e):r.qsa?t.querySelectorAll(e):void 0}:function(e,t){var r,n=[],i=0,o=t.getElementsByTagName(e);if("*"===e){for(;r=o[i++];)1===r.nodeType&&n.push(r);return n}return o},n.find.CLASS=r.getElementsByClassName&&function(e,t){if(void 0!==t.getElementsByClassName&&g)return t.getElementsByClassName(e)},v=[],m=[],(r.qsa=Q.test(d.querySelectorAll))&&(ue((function(e){var t;h.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&m.push("[*^$]="+I+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||m.push("\\["+I+"*(?:value|"+B+")"),e.querySelectorAll("[id~="+w+"-]").length||m.push("~="),(t=d.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||m.push("\\["+I+"*name"+I+"*="+I+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||m.push(":checked"),e.querySelectorAll("a#"+w+"+*").length||m.push(".#.+[+~]"),e.querySelectorAll("\\\f"),m.push("[\\r\\n\\f]")})),ue((function(e){e.innerHTML="";var t=d.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&m.push("name"+I+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&m.push(":enabled",":disabled"),h.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&m.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),m.push(",.*:")}))),(r.matchesSelector=Q.test(b=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&ue((function(e){r.disconnectedMatch=b.call(e,"*"),b.call(e,"[s!='']:x"),v.push("!=",H)})),r.cssHas||m.push(":has"),m=m.length&&new RegExp(m.join("|")),v=v.length&&new RegExp(v.join("|")),t=Q.test(h.compareDocumentPosition),y=t||Q.test(h.contains)?function(e,t){var r=9===e.nodeType&&e.documentElement||e,n=t&&t.parentNode;return e===n||!(!n||1!==n.nodeType||!(r.contains?r.contains(n):e.compareDocumentPosition&&16&e.compareDocumentPosition(n)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},q=t?function(e,t){if(e===t)return p=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!r.sortDetached&&t.compareDocumentPosition(e)===n?e==d||e.ownerDocument==_&&y(_,e)?-1:t==d||t.ownerDocument==_&&y(_,t)?1:u?R(u,e)-R(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return p=!0,0;var r,n=0,i=e.parentNode,o=t.parentNode,s=[e],a=[t];if(!i||!o)return e==d?-1:t==d?1:i?-1:o?1:u?R(u,e)-R(u,t):0;if(i===o)return fe(e,t);for(r=e;r=r.parentNode;)s.unshift(r);for(r=t;r=r.parentNode;)a.unshift(r);for(;s[n]===a[n];)n++;return n?fe(s[n],a[n]):s[n]==_?-1:a[n]==_?1:0},d):d},ae.matches=function(e,t){return ae(e,null,null,t)},ae.matchesSelector=function(e,t){if(f(e),r.matchesSelector&&g&&!C[t+" "]&&(!v||!v.test(t))&&(!m||!m.test(t)))try{var n=b.call(e,t);if(n||r.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){C(t,!0)}return ae(t,d,null,[e]).length>0},ae.contains=function(e,t){return(e.ownerDocument||e)!=d&&f(e),y(e,t)},ae.attr=function(e,t){(e.ownerDocument||e)!=d&&f(e);var i=n.attrHandle[t.toLowerCase()],o=i&&k.call(n.attrHandle,t.toLowerCase())?i(e,t,!g):void 0;return void 0!==o?o:r.attributes||!g?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null},ae.escape=function(e){return(e+"").replace(ne,ie)},ae.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},ae.uniqueSort=function(e){var t,n=[],i=0,o=0;if(p=!r.detectDuplicates,u=!r.sortStable&&e.slice(0),e.sort(q),p){for(;t=e[o++];)t===e[o]&&(i=n.push(o));for(;i--;)e.splice(n[i],1)}return u=null,e},i=ae.getText=function(e){var t,r="",n=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)r+=i(e)}else if(3===o||4===o)return e.nodeValue}else for(;t=e[n++];)r+=i(t);return r},n=ae.selectors={cacheLength:50,createPseudo:ce,match:Y,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,re),e[3]=(e[3]||e[4]||e[5]||"").replace(te,re),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||ae.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&ae.error(e[0]),e},PSEUDO:function(e){var t,r=!e[6]&&e[2];return Y.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":r&&G.test(r)&&(t=s(r,!0))&&(t=r.indexOf(")",r.length-t)-r.length)&&(e[0]=e[0].slice(0,t),e[2]=r.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,re).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=E[e+" "];return t||(t=new RegExp("(^|"+I+")"+e+"("+I+"|$)"))&&E(e,(function(e){return t.test("string"==typeof e.className&&e.className||void 0!==e.getAttribute&&e.getAttribute("class")||"")}))},ATTR:function(e,t,r){return function(n){var i=ae.attr(n,e);return null==i?"!="===t:!t||(i+="","="===t?i===r:"!="===t?i!==r:"^="===t?r&&0===i.indexOf(r):"*="===t?r&&i.indexOf(r)>-1:"$="===t?r&&i.slice(-r.length)===r:"~="===t?(" "+i.replace(M," ")+" ").indexOf(r)>-1:"|="===t&&(i===r||i.slice(0,r.length+1)===r+"-"))}},CHILD:function(e,t,r,n,i){var o="nth"!==e.slice(0,3),s="last"!==e.slice(-4),a="of-type"===t;return 1===n&&0===i?function(e){return!!e.parentNode}:function(t,r,l){var c,u,p,f,d,h,g=o!==s?"nextSibling":"previousSibling",m=t.parentNode,v=a&&t.nodeName.toLowerCase(),b=!l&&!a,y=!1;if(m){if(o){for(;g;){for(f=t;f=f[g];)if(a?f.nodeName.toLowerCase()===v:1===f.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[s?m.firstChild:m.lastChild],s&&b){for(y=(d=(c=(u=(p=(f=m)[w]||(f[w]={}))[f.uniqueID]||(p[f.uniqueID]={}))[e]||[])[0]===x&&c[1])&&c[2],f=d&&m.childNodes[d];f=++d&&f&&f[g]||(y=d=0)||h.pop();)if(1===f.nodeType&&++y&&f===t){u[e]=[x,d,y];break}}else if(b&&(y=d=(c=(u=(p=(f=t)[w]||(f[w]={}))[f.uniqueID]||(p[f.uniqueID]={}))[e]||[])[0]===x&&c[1]),!1===y)for(;(f=++d&&f&&f[g]||(y=d=0)||h.pop())&&((a?f.nodeName.toLowerCase()!==v:1!==f.nodeType)||!++y||(b&&((u=(p=f[w]||(f[w]={}))[f.uniqueID]||(p[f.uniqueID]={}))[e]=[x,y]),f!==t)););return(y-=i)===n||y%n==0&&y/n>=0}}},PSEUDO:function(e,t){var r,i=n.pseudos[e]||n.setFilters[e.toLowerCase()]||ae.error("unsupported pseudo: "+e);return i[w]?i(t):i.length>1?(r=[e,e,"",t],n.setFilters.hasOwnProperty(e.toLowerCase())?ce((function(e,r){for(var n,o=i(e,t),s=o.length;s--;)e[n=R(e,o[s])]=!(r[n]=o[s])})):function(e){return i(e,0,r)}):i}},pseudos:{not:ce((function(e){var t=[],r=[],n=a(e.replace(U,"$1"));return n[w]?ce((function(e,t,r,i){for(var o,s=n(e,null,i,[]),a=e.length;a--;)(o=s[a])&&(e[a]=!(t[a]=o))})):function(e,i,o){return t[0]=e,n(t,null,o,r),t[0]=null,!r.pop()}})),has:ce((function(e){return function(t){return ae(e,t).length>0}})),contains:ce((function(e){return e=e.replace(te,re),function(t){return(t.textContent||i(t)).indexOf(e)>-1}})),lang:ce((function(e){return W.test(e||"")||ae.error("unsupported lang: "+e),e=e.replace(te,re).toLowerCase(),function(t){var r;do{if(r=g?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(r=r.toLowerCase())===e||0===r.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}})),target:function(t){var r=e.location&&e.location.hash;return r&&r.slice(1)===t.id},root:function(e){return e===h},focus:function(e){return e===d.activeElement&&(!d.hasFocus||d.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:ge(!1),disabled:ge(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!n.pseudos.empty(e)},header:function(e){return J.test(e.nodeName)},input:function(e){return K.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:me((function(){return[0]})),last:me((function(e,t){return[t-1]})),eq:me((function(e,t,r){return[r<0?r+t:r]})),even:me((function(e,t){for(var r=0;rt?t:r;--n>=0;)e.push(n);return e})),gt:me((function(e,t,r){for(var n=r<0?r+t:r;++n1?function(t,r,n){for(var i=e.length;i--;)if(!e[i](t,r,n))return!1;return!0}:e[0]}function xe(e,t,r,n,i){for(var o,s=[],a=0,l=e.length,c=null!=t;a-1&&(o[c]=!(s[c]=p))}}else v=xe(v===s?v.splice(h,v.length):v),i?i(null,s,v,l):O.apply(s,v)}))}function Ee(e){for(var t,r,i,o=e.length,s=n.relative[e[0].type],a=s||n.relative[" "],l=s?1:0,u=we((function(e){return e===t}),a,!0),p=we((function(e){return R(t,e)>-1}),a,!0),f=[function(e,r,n){var i=!s&&(n||r!==c)||((t=r).nodeType?u(e,r,n):p(e,r,n));return t=null,i}];l1&&_e(f),l>1&&ye(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(U,"$1"),r,l0,i=e.length>0,o=function(o,s,a,l,u){var p,h,m,v=0,b="0",y=o&&[],w=[],_=c,A=o||i&&n.find.TAG("*",u),E=x+=null==_?1:Math.random()||.1,D=A.length;for(u&&(c=s==d||s||u);b!==D&&null!=(p=A[b]);b++){if(i&&p){for(h=0,s||p.ownerDocument==d||(f(p),a=!g);m=e[h++];)if(m(p,s||d,a)){l.push(p);break}u&&(x=E)}r&&((p=!m&&p)&&v--,o&&y.push(p))}if(v+=b,r&&b!==v){for(h=0;m=t[h++];)m(y,w,s,a);if(o){if(v>0)for(;b--;)y[b]||w[b]||(w[b]=S.call(l));w=xe(w)}O.apply(l,w),u&&!o&&w.length>0&&v+t.length>1&&ae.uniqueSort(l)}return u&&(x=E,c=_),y};return r?ce(o):o}(o,i)),a.selector=e}return a},l=ae.select=function(e,t,r,i){var o,l,c,u,p,f="function"==typeof e&&e,d=!i&&s(e=f.selector||e);if(r=r||[],1===d.length){if((l=d[0]=d[0].slice(0)).length>2&&"ID"===(c=l[0]).type&&9===t.nodeType&&g&&n.relative[l[1].type]){if(!(t=(n.find.ID(c.matches[0].replace(te,re),t)||[])[0]))return r;f&&(t=t.parentNode),e=e.slice(l.shift().value.length)}for(o=Y.needsContext.test(e)?0:l.length;o--&&(c=l[o],!n.relative[u=c.type]);)if((p=n.find[u])&&(i=p(c.matches[0].replace(te,re),ee.test(l[0].type)&&ve(t.parentNode)||t))){if(l.splice(o,1),!(e=i.length&&ye(l)))return O.apply(r,i),r;break}}return(f||a(e,d))(i,t,!g,r,!t||ee.test(e)&&ve(t.parentNode)||t),r},r.sortStable=w.split("").sort(q).join("")===w,r.detectDuplicates=!!p,f(),r.sortDetached=ue((function(e){return 1&e.compareDocumentPosition(d.createElement("fieldset"))})),ue((function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")}))||pe("type|href|height|width",(function(e,t,r){if(!r)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)})),r.attributes&&ue((function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")}))||pe("value",(function(e,t,r){if(!r&&"input"===e.nodeName.toLowerCase())return e.defaultValue})),ue((function(e){return null==e.getAttribute("disabled")}))||pe(B,(function(e,t,r){var n;if(!r)return!0===e[t]?t.toLowerCase():(n=e.getAttributeNode(t))&&n.specified?n.value:null})),ae}(n);E.find=T,E.expr=T.selectors,E.expr[":"]=E.expr.pseudos,E.uniqueSort=E.unique=T.uniqueSort,E.text=T.getText,E.isXMLDoc=T.isXML,E.contains=T.contains,E.escapeSelector=T.escape;var C=function(e,t,r){for(var n=[],i=void 0!==r;(e=e[t])&&9!==e.nodeType;)if(1===e.nodeType){if(i&&E(e).is(r))break;n.push(e)}return n},q=function(e,t){for(var r=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&r.push(e);return r},k=E.expr.match.needsContext;function L(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var S=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function N(e,t,r){return v(t)?E.grep(e,(function(e,n){return!!t.call(e,n,e)!==r})):t.nodeType?E.grep(e,(function(e){return e===t!==r})):"string"!=typeof t?E.grep(e,(function(e){return u.call(t,e)>-1!==r})):E.filter(t,e,r)}E.filter=function(e,t,r){var n=t[0];return r&&(e=":not("+e+")"),1===t.length&&1===n.nodeType?E.find.matchesSelector(n,e)?[n]:[]:E.find.matches(e,E.grep(t,(function(e){return 1===e.nodeType})))},E.fn.extend({find:function(e){var t,r,n=this.length,i=this;if("string"!=typeof e)return this.pushStack(E(e).filter((function(){for(t=0;t1?E.uniqueSort(r):r},filter:function(e){return this.pushStack(N(this,e||[],!1))},not:function(e){return this.pushStack(N(this,e||[],!0))},is:function(e){return!!N(this,"string"==typeof e&&k.test(e)?E(e):e||[],!1).length}});var O,j=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(E.fn.init=function(e,t,r){var n,i;if(!e)return this;if(r=r||O,"string"==typeof e){if(!(n="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:j.exec(e))||!n[1]&&t)return!t||t.jquery?(t||r).find(e):this.constructor(t).find(e);if(n[1]){if(t=t instanceof E?t[0]:t,E.merge(this,E.parseHTML(n[1],t&&t.nodeType?t.ownerDocument||t:y,!0)),S.test(n[1])&&E.isPlainObject(t))for(n in t)v(this[n])?this[n](t[n]):this.attr(n,t[n]);return this}return(i=y.getElementById(n[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==r.ready?r.ready(e):e(E):E.makeArray(e,this)}).prototype=E.fn,O=E(y);var R=/^(?:parents|prev(?:Until|All))/,B={children:!0,contents:!0,next:!0,prev:!0};function I(e,t){for(;(e=e[t])&&1!==e.nodeType;);return e}E.fn.extend({has:function(e){var t=E(e,this),r=t.length;return this.filter((function(){for(var e=0;e-1:1===r.nodeType&&E.find.matchesSelector(r,e))){o.push(r);break}return this.pushStack(o.length>1?E.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?u.call(E(e),this[0]):u.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(E.uniqueSort(E.merge(this.get(),E(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),E.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return C(e,"parentNode")},parentsUntil:function(e,t,r){return C(e,"parentNode",r)},next:function(e){return I(e,"nextSibling")},prev:function(e){return I(e,"previousSibling")},nextAll:function(e){return C(e,"nextSibling")},prevAll:function(e){return C(e,"previousSibling")},nextUntil:function(e,t,r){return C(e,"nextSibling",r)},prevUntil:function(e,t,r){return C(e,"previousSibling",r)},siblings:function(e){return q((e.parentNode||{}).firstChild,e)},children:function(e){return q(e.firstChild)},contents:function(e){return null!=e.contentDocument&&s(e.contentDocument)?e.contentDocument:(L(e,"template")&&(e=e.content||e),E.merge([],e.childNodes))}},(function(e,t){E.fn[e]=function(r,n){var i=E.map(this,t,r);return"Until"!==e.slice(-5)&&(n=r),n&&"string"==typeof n&&(i=E.filter(n,i)),this.length>1&&(B[e]||E.uniqueSort(i),R.test(e)&&i.reverse()),this.pushStack(i)}}));var P=/[^\x20\t\r\n\f]+/g;function F(e){return e}function H(e){throw e}function M(e,t,r,n){var i;try{e&&v(i=e.promise)?i.call(e).done(t).fail(r):e&&v(i=e.then)?i.call(e,t,r):t.apply(void 0,[e].slice(n))}catch(e){r.apply(void 0,[e])}}E.Callbacks=function(e){e="string"==typeof e?function(e){var t={};return E.each(e.match(P)||[],(function(e,r){t[r]=!0})),t}(e):E.extend({},e);var t,r,n,i,o=[],s=[],a=-1,l=function(){for(i=i||e.once,n=t=!0;s.length;a=-1)for(r=s.shift();++a-1;)o.splice(r,1),r<=a&&a--})),this},has:function(e){return e?E.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=s=[],o=r="",this},disabled:function(){return!o},lock:function(){return i=s=[],r||t||(o=r=""),this},locked:function(){return!!i},fireWith:function(e,r){return i||(r=[e,(r=r||[]).slice?r.slice():r],s.push(r),t||l()),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!n}};return c},E.extend({Deferred:function(e){var t=[["notify","progress",E.Callbacks("memory"),E.Callbacks("memory"),2],["resolve","done",E.Callbacks("once memory"),E.Callbacks("once memory"),0,"resolved"],["reject","fail",E.Callbacks("once memory"),E.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},catch:function(e){return i.then(null,e)},pipe:function(){var e=arguments;return E.Deferred((function(r){E.each(t,(function(t,n){var i=v(e[n[4]])&&e[n[4]];o[n[1]]((function(){var e=i&&i.apply(this,arguments);e&&v(e.promise)?e.promise().progress(r.notify).done(r.resolve).fail(r.reject):r[n[0]+"With"](this,i?[e]:arguments)}))})),e=null})).promise()},then:function(e,r,i){var o=0;function s(e,t,r,i){return function(){var a=this,l=arguments,c=function(){var n,c;if(!(e=o&&(r!==H&&(a=void 0,l=[n]),t.rejectWith(a,l))}};e?u():(E.Deferred.getStackHook&&(u.stackTrace=E.Deferred.getStackHook()),n.setTimeout(u))}}return E.Deferred((function(n){t[0][3].add(s(0,n,v(i)?i:F,n.notifyWith)),t[1][3].add(s(0,n,v(e)?e:F)),t[2][3].add(s(0,n,v(r)?r:H))})).promise()},promise:function(e){return null!=e?E.extend(e,i):i}},o={};return E.each(t,(function(e,n){var s=n[2],a=n[5];i[n[1]]=s.add,a&&s.add((function(){r=a}),t[3-e][2].disable,t[3-e][3].disable,t[0][2].lock,t[0][3].lock),s.add(n[3].fire),o[n[0]]=function(){return o[n[0]+"With"](this===o?void 0:this,arguments),this},o[n[0]+"With"]=s.fireWith})),i.promise(o),e&&e.call(o,o),o},when:function(e){var t=arguments.length,r=t,n=Array(r),i=a.call(arguments),o=E.Deferred(),s=function(e){return function(r){n[e]=this,i[e]=arguments.length>1?a.call(arguments):r,--t||o.resolveWith(n,i)}};if(t<=1&&(M(e,o.done(s(r)).resolve,o.reject,!t),"pending"===o.state()||v(i[r]&&i[r].then)))return o.then();for(;r--;)M(i[r],s(r),o.reject);return o.promise()}});var U=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;E.Deferred.exceptionHook=function(e,t){n.console&&n.console.warn&&e&&U.test(e.name)&&n.console.warn("jQuery.Deferred exception: "+e.message,e.stack,t)},E.readyException=function(e){n.setTimeout((function(){throw e}))};var V=E.Deferred();function $(){y.removeEventListener("DOMContentLoaded",$),n.removeEventListener("load",$),E.ready()}E.fn.ready=function(e){return V.then(e).catch((function(e){E.readyException(e)})),this},E.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--E.readyWait:E.isReady)||(E.isReady=!0,!0!==e&&--E.readyWait>0||V.resolveWith(y,[E]))}}),E.ready.then=V.then,"complete"===y.readyState||"loading"!==y.readyState&&!y.documentElement.doScroll?n.setTimeout(E.ready):(y.addEventListener("DOMContentLoaded",$),n.addEventListener("load",$));var z=function(e,t,r,n,i,o,s){var a=0,l=e.length,c=null==r;if("object"===x(r))for(a in i=!0,r)z(e,t,a,r[a],!0,o,s);else if(void 0!==n&&(i=!0,v(n)||(s=!0),c&&(s?(t.call(e,n),t=null):(c=t,t=function(e,t,r){return c.call(E(e),r)})),t))for(;a1,null,!0)},removeData:function(e){return this.each((function(){Z.remove(this,e)}))}}),E.extend({queue:function(e,t,r){var n;if(e)return t=(t||"fx")+"queue",n=Q.get(e,t),r&&(!n||Array.isArray(r)?n=Q.access(e,t,E.makeArray(r)):n.push(r)),n||[]},dequeue:function(e,t){t=t||"fx";var r=E.queue(e,t),n=r.length,i=r.shift(),o=E._queueHooks(e,t);"inprogress"===i&&(i=r.shift(),n--),i&&("fx"===t&&r.unshift("inprogress"),delete o.stop,i.call(e,(function(){E.dequeue(e,t)}),o)),!n&&o&&o.empty.fire()},_queueHooks:function(e,t){var r=t+"queueHooks";return Q.get(e,r)||Q.access(e,r,{empty:E.Callbacks("once memory").add((function(){Q.remove(e,[t+"queue",r])}))})}}),E.fn.extend({queue:function(e,t){var r=2;return"string"!=typeof e&&(t=e,e="fx",r--),arguments.length\x20\t\r\n\f]*)/i,be=/^$|^module$|\/(?:java|ecma)script/i;he=y.createDocumentFragment().appendChild(y.createElement("div")),(ge=y.createElement("input")).setAttribute("type","radio"),ge.setAttribute("checked","checked"),ge.setAttribute("name","t"),he.appendChild(ge),m.checkClone=he.cloneNode(!0).cloneNode(!0).lastChild.checked,he.innerHTML="",m.noCloneChecked=!!he.cloneNode(!0).lastChild.defaultValue,he.innerHTML="",m.option=!!he.lastChild;var ye={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function we(e,t){var r;return r=void 0!==e.getElementsByTagName?e.getElementsByTagName(t||"*"):void 0!==e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&L(e,t)?E.merge([e],r):r}function _e(e,t){for(var r=0,n=e.length;r",""]);var xe=/<|&#?\w+;/;function Ae(e,t,r,n,i){for(var o,s,a,l,c,u,p=t.createDocumentFragment(),f=[],d=0,h=e.length;d-1)i&&i.push(o);else if(c=ae(o),s=we(p.appendChild(o),"script"),c&&_e(s),r)for(u=0;o=s[u++];)be.test(o.type||"")&&r.push(o);return p}var Ee=/^([^.]*)(?:\.(.+)|)/;function De(){return!0}function Te(){return!1}function Ce(e,t){return e===function(){try{return y.activeElement}catch(e){}}()==("focus"===t)}function qe(e,t,r,n,i,o){var s,a;if("object"==typeof t){for(a in"string"!=typeof r&&(n=n||r,r=void 0),t)qe(e,a,r,n,t[a],o);return e}if(null==n&&null==i?(i=r,n=r=void 0):null==i&&("string"==typeof r?(i=n,n=void 0):(i=n,n=r,r=void 0)),!1===i)i=Te;else if(!i)return e;return 1===o&&(s=i,i=function(e){return E().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=E.guid++)),e.each((function(){E.event.add(this,t,i,n,r)}))}function ke(e,t,r){r?(Q.set(e,t,!1),E.event.add(e,t,{namespace:!1,handler:function(e){var n,i,o=Q.get(this,t);if(1&e.isTrigger&&this[t]){if(o.length)(E.event.special[t]||{}).delegateType&&e.stopPropagation();else if(o=a.call(arguments),Q.set(this,t,o),n=r(this,t),this[t](),o!==(i=Q.get(this,t))||n?Q.set(this,t,!1):i={},o!==i)return e.stopImmediatePropagation(),e.preventDefault(),i&&i.value}else o.length&&(Q.set(this,t,{value:E.event.trigger(E.extend(o[0],E.Event.prototype),o.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,t)&&E.event.add(e,t,De)}E.event={global:{},add:function(e,t,r,n,i){var o,s,a,l,c,u,p,f,d,h,g,m=Q.get(e);if(K(e))for(r.handler&&(r=(o=r).handler,i=o.selector),i&&E.find.matchesSelector(se,i),r.guid||(r.guid=E.guid++),(l=m.events)||(l=m.events=Object.create(null)),(s=m.handle)||(s=m.handle=function(t){return void 0!==E&&E.event.triggered!==t.type?E.event.dispatch.apply(e,arguments):void 0}),c=(t=(t||"").match(P)||[""]).length;c--;)d=g=(a=Ee.exec(t[c])||[])[1],h=(a[2]||"").split(".").sort(),d&&(p=E.event.special[d]||{},d=(i?p.delegateType:p.bindType)||d,p=E.event.special[d]||{},u=E.extend({type:d,origType:g,data:n,handler:r,guid:r.guid,selector:i,needsContext:i&&E.expr.match.needsContext.test(i),namespace:h.join(".")},o),(f=l[d])||((f=l[d]=[]).delegateCount=0,p.setup&&!1!==p.setup.call(e,n,h,s)||e.addEventListener&&e.addEventListener(d,s)),p.add&&(p.add.call(e,u),u.handler.guid||(u.handler.guid=r.guid)),i?f.splice(f.delegateCount++,0,u):f.push(u),E.event.global[d]=!0)},remove:function(e,t,r,n,i){var o,s,a,l,c,u,p,f,d,h,g,m=Q.hasData(e)&&Q.get(e);if(m&&(l=m.events)){for(c=(t=(t||"").match(P)||[""]).length;c--;)if(d=g=(a=Ee.exec(t[c])||[])[1],h=(a[2]||"").split(".").sort(),d){for(p=E.event.special[d]||{},f=l[d=(n?p.delegateType:p.bindType)||d]||[],a=a[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=o=f.length;o--;)u=f[o],!i&&g!==u.origType||r&&r.guid!==u.guid||a&&!a.test(u.namespace)||n&&n!==u.selector&&("**"!==n||!u.selector)||(f.splice(o,1),u.selector&&f.delegateCount--,p.remove&&p.remove.call(e,u));s&&!f.length&&(p.teardown&&!1!==p.teardown.call(e,h,m.handle)||E.removeEvent(e,d,m.handle),delete l[d])}else for(d in l)E.event.remove(e,d+t[c],r,n,!0);E.isEmptyObject(l)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,r,n,i,o,s,a=new Array(arguments.length),l=E.event.fix(e),c=(Q.get(this,"events")||Object.create(null))[l.type]||[],u=E.event.special[l.type]||{};for(a[0]=l,t=1;t=1))for(;c!==this;c=c.parentNode||this)if(1===c.nodeType&&("click"!==e.type||!0!==c.disabled)){for(o=[],s={},r=0;r-1:E.find(i,this,null,[c]).length),s[i]&&o.push(n);o.length&&a.push({elem:c,handlers:o})}return c=this,l\s*$/g;function Oe(e,t){return L(e,"table")&&L(11!==t.nodeType?t:t.firstChild,"tr")&&E(e).children("tbody")[0]||e}function je(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Re(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Be(e,t){var r,n,i,o,s,a;if(1===t.nodeType){if(Q.hasData(e)&&(a=Q.get(e).events))for(i in Q.remove(t,"handle events"),a)for(r=0,n=a[i].length;r1&&"string"==typeof h&&!m.checkClone&&Se.test(h))return e.each((function(i){var o=e.eq(i);g&&(t[0]=h.call(this,i,o.html())),Pe(o,t,r,n)}));if(f&&(o=(i=Ae(t,e[0].ownerDocument,!1,e,n)).firstChild,1===i.childNodes.length&&(i=o),o||n)){for(a=(s=E.map(we(i,"script"),je)).length;p0&&_e(s,!l&&we(e,"script")),a},cleanData:function(e){for(var t,r,n,i=E.event.special,o=0;void 0!==(r=e[o]);o++)if(K(r)){if(t=r[Q.expando]){if(t.events)for(n in t.events)i[n]?E.event.remove(r,n):E.removeEvent(r,n,t.handle);r[Q.expando]=void 0}r[Z.expando]&&(r[Z.expando]=void 0)}}}),E.fn.extend({detach:function(e){return Fe(this,e,!0)},remove:function(e){return Fe(this,e)},text:function(e){return z(this,(function(e){return void 0===e?E.text(this):this.empty().each((function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)}))}),null,e,arguments.length)},append:function(){return Pe(this,arguments,(function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Oe(this,e).appendChild(e)}))},prepend:function(){return Pe(this,arguments,(function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Oe(this,e);t.insertBefore(e,t.firstChild)}}))},before:function(){return Pe(this,arguments,(function(e){this.parentNode&&this.parentNode.insertBefore(e,this)}))},after:function(){return Pe(this,arguments,(function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)}))},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(E.cleanData(we(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map((function(){return E.clone(this,e,t)}))},html:function(e){return z(this,(function(e){var t=this[0]||{},r=0,n=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Le.test(e)&&!ye[(ve.exec(e)||["",""])[1].toLowerCase()]){e=E.htmlPrefilter(e);try{for(;r=0&&(l+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-l-a-.5))||0),l}function it(e,t,r){var n=Ue(e),i=(!m.boxSizingReliable()||r)&&"border-box"===E.css(e,"boxSizing",!1,n),o=i,s=We(e,t,n),a="offset"+t[0].toUpperCase()+t.slice(1);if(He.test(s)){if(!r)return s;s="auto"}return(!m.boxSizingReliable()&&i||!m.reliableTrDimensions()&&L(e,"tr")||"auto"===s||!parseFloat(s)&&"inline"===E.css(e,"display",!1,n))&&e.getClientRects().length&&(i="border-box"===E.css(e,"boxSizing",!1,n),(o=a in e)&&(s=e[a])),(s=parseFloat(s)||0)+nt(e,t,r||(i?"border":"content"),o,n,s)+"px"}function ot(e,t,r,n,i){return new ot.prototype.init(e,t,r,n,i)}E.extend({cssHooks:{opacity:{get:function(e,t){if(t){var r=We(e,"opacity");return""===r?"1":r}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,r,n){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,s,a=X(t),l=Me.test(t),c=e.style;if(l||(t=Qe(a)),s=E.cssHooks[t]||E.cssHooks[a],void 0===r)return s&&"get"in s&&void 0!==(i=s.get(e,!1,n))?i:c[t];"string"===(o=typeof r)&&(i=ie.exec(r))&&i[1]&&(r=ue(e,t,i),o="number"),null!=r&&r==r&&("number"!==o||l||(r+=i&&i[3]||(E.cssNumber[a]?"":"px")),m.clearCloneStyle||""!==r||0!==t.indexOf("background")||(c[t]="inherit"),s&&"set"in s&&void 0===(r=s.set(e,r,n))||(l?c.setProperty(t,r):c[t]=r))}},css:function(e,t,r,n){var i,o,s,a=X(t);return Me.test(t)||(t=Qe(a)),(s=E.cssHooks[t]||E.cssHooks[a])&&"get"in s&&(i=s.get(e,!0,r)),void 0===i&&(i=We(e,t,n)),"normal"===i&&t in tt&&(i=tt[t]),""===r||r?(o=parseFloat(i),!0===r||isFinite(o)?o||0:i):i}}),E.each(["height","width"],(function(e,t){E.cssHooks[t]={get:function(e,r,n){if(r)return!Ze.test(E.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?it(e,t,n):Ve(e,et,(function(){return it(e,t,n)}))},set:function(e,r,n){var i,o=Ue(e),s=!m.scrollboxSize()&&"absolute"===o.position,a=(s||n)&&"border-box"===E.css(e,"boxSizing",!1,o),l=n?nt(e,t,n,a,o):0;return a&&s&&(l-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-nt(e,t,"border",!1,o)-.5)),l&&(i=ie.exec(r))&&"px"!==(i[3]||"px")&&(e.style[t]=r,r=E.css(e,t)),rt(0,r,l)}}})),E.cssHooks.marginLeft=Ye(m.reliableMarginLeft,(function(e,t){if(t)return(parseFloat(We(e,"marginLeft"))||e.getBoundingClientRect().left-Ve(e,{marginLeft:0},(function(){return e.getBoundingClientRect().left})))+"px"})),E.each({margin:"",padding:"",border:"Width"},(function(e,t){E.cssHooks[e+t]={expand:function(r){for(var n=0,i={},o="string"==typeof r?r.split(" "):[r];n<4;n++)i[e+oe[n]+t]=o[n]||o[n-2]||o[0];return i}},"margin"!==e&&(E.cssHooks[e+t].set=rt)})),E.fn.extend({css:function(e,t){return z(this,(function(e,t,r){var n,i,o={},s=0;if(Array.isArray(t)){for(n=Ue(e),i=t.length;s1)}}),E.Tween=ot,ot.prototype={constructor:ot,init:function(e,t,r,n,i,o){this.elem=e,this.prop=r,this.easing=i||E.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=n,this.unit=o||(E.cssNumber[r]?"":"px")},cur:function(){var e=ot.propHooks[this.prop];return e&&e.get?e.get(this):ot.propHooks._default.get(this)},run:function(e){var t,r=ot.propHooks[this.prop];return this.options.duration?this.pos=t=E.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),r&&r.set?r.set(this):ot.propHooks._default.set(this),this}},ot.prototype.init.prototype=ot.prototype,ot.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=E.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){E.fx.step[e.prop]?E.fx.step[e.prop](e):1!==e.elem.nodeType||!E.cssHooks[e.prop]&&null==e.elem.style[Qe(e.prop)]?e.elem[e.prop]=e.now:E.style(e.elem,e.prop,e.now+e.unit)}}},ot.propHooks.scrollTop=ot.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},E.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},E.fx=ot.prototype.init,E.fx.step={};var st,at,lt=/^(?:toggle|show|hide)$/,ct=/queueHooks$/;function ut(){at&&(!1===y.hidden&&n.requestAnimationFrame?n.requestAnimationFrame(ut):n.setTimeout(ut,E.fx.interval),E.fx.tick())}function pt(){return n.setTimeout((function(){st=void 0})),st=Date.now()}function ft(e,t){var r,n=0,i={height:e};for(t=t?1:0;n<4;n+=2-t)i["margin"+(r=oe[n])]=i["padding"+r]=e;return t&&(i.opacity=i.width=e),i}function dt(e,t,r){for(var n,i=(ht.tweeners[t]||[]).concat(ht.tweeners["*"]),o=0,s=i.length;o1)},removeAttr:function(e){return this.each((function(){E.removeAttr(this,e)}))}}),E.extend({attr:function(e,t,r){var n,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return void 0===e.getAttribute?E.prop(e,t,r):(1===o&&E.isXMLDoc(e)||(i=E.attrHooks[t.toLowerCase()]||(E.expr.match.bool.test(t)?gt:void 0)),void 0!==r?null===r?void E.removeAttr(e,t):i&&"set"in i&&void 0!==(n=i.set(e,r,t))?n:(e.setAttribute(t,r+""),r):i&&"get"in i&&null!==(n=i.get(e,t))?n:null==(n=E.find.attr(e,t))?void 0:n)},attrHooks:{type:{set:function(e,t){if(!m.radioValue&&"radio"===t&&L(e,"input")){var r=e.value;return e.setAttribute("type",t),r&&(e.value=r),t}}}},removeAttr:function(e,t){var r,n=0,i=t&&t.match(P);if(i&&1===e.nodeType)for(;r=i[n++];)e.removeAttribute(r)}}),gt={set:function(e,t,r){return!1===t?E.removeAttr(e,r):e.setAttribute(r,r),r}},E.each(E.expr.match.bool.source.match(/\w+/g),(function(e,t){var r=mt[t]||E.find.attr;mt[t]=function(e,t,n){var i,o,s=t.toLowerCase();return n||(o=mt[s],mt[s]=i,i=null!=r(e,t,n)?s:null,mt[s]=o),i}}));var vt=/^(?:input|select|textarea|button)$/i,bt=/^(?:a|area)$/i;function yt(e){return(e.match(P)||[]).join(" ")}function wt(e){return e.getAttribute&&e.getAttribute("class")||""}function _t(e){return Array.isArray(e)?e:"string"==typeof e&&e.match(P)||[]}E.fn.extend({prop:function(e,t){return z(this,E.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each((function(){delete this[E.propFix[e]||e]}))}}),E.extend({prop:function(e,t,r){var n,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&E.isXMLDoc(e)||(t=E.propFix[t]||t,i=E.propHooks[t]),void 0!==r?i&&"set"in i&&void 0!==(n=i.set(e,r,t))?n:e[t]=r:i&&"get"in i&&null!==(n=i.get(e,t))?n:e[t]},propHooks:{tabIndex:{get:function(e){var t=E.find.attr(e,"tabindex");return t?parseInt(t,10):vt.test(e.nodeName)||bt.test(e.nodeName)&&e.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),m.optSelected||(E.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),E.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],(function(){E.propFix[this.toLowerCase()]=this})),E.fn.extend({addClass:function(e){var t,r,n,i,o,s;return v(e)?this.each((function(t){E(this).addClass(e.call(this,t,wt(this)))})):(t=_t(e)).length?this.each((function(){if(n=wt(this),r=1===this.nodeType&&" "+yt(n)+" "){for(o=0;o-1;)r=r.replace(" "+i+" "," ");s=yt(r),n!==s&&this.setAttribute("class",s)}})):this:this.attr("class","")},toggleClass:function(e,t){var r,n,i,o,s=typeof e,a="string"===s||Array.isArray(e);return v(e)?this.each((function(r){E(this).toggleClass(e.call(this,r,wt(this),t),t)})):"boolean"==typeof t&&a?t?this.addClass(e):this.removeClass(e):(r=_t(e),this.each((function(){if(a)for(o=E(this),i=0;i-1)return!0;return!1}});var xt=/\r/g;E.fn.extend({val:function(e){var t,r,n,i=this[0];return arguments.length?(n=v(e),this.each((function(r){var i;1===this.nodeType&&(null==(i=n?e.call(this,r,E(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=E.map(i,(function(e){return null==e?"":e+""}))),(t=E.valHooks[this.type]||E.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))}))):i?(t=E.valHooks[i.type]||E.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(r=t.get(i,"value"))?r:"string"==typeof(r=i.value)?r.replace(xt,""):null==r?"":r:void 0}}),E.extend({valHooks:{option:{get:function(e){var t=E.find.attr(e,"value");return null!=t?t:yt(E.text(e))}},select:{get:function(e){var t,r,n,i=e.options,o=e.selectedIndex,s="select-one"===e.type,a=s?null:[],l=s?o+1:i.length;for(n=o<0?l:s?o:0;n-1)&&(r=!0);return r||(e.selectedIndex=-1),o}}}}),E.each(["radio","checkbox"],(function(){E.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=E.inArray(E(e).val(),t)>-1}},m.checkOn||(E.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})})),m.focusin="onfocusin"in n;var At=/^(?:focusinfocus|focusoutblur)$/,Et=function(e){e.stopPropagation()};E.extend(E.event,{trigger:function(e,t,r,i){var o,s,a,l,c,u,p,f,h=[r||y],g=d.call(e,"type")?e.type:e,m=d.call(e,"namespace")?e.namespace.split("."):[];if(s=f=a=r=r||y,3!==r.nodeType&&8!==r.nodeType&&!At.test(g+E.event.triggered)&&(g.indexOf(".")>-1&&(m=g.split("."),g=m.shift(),m.sort()),c=g.indexOf(":")<0&&"on"+g,(e=e[E.expando]?e:new E.Event(g,"object"==typeof e&&e)).isTrigger=i?2:3,e.namespace=m.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=r),t=null==t?[e]:E.makeArray(t,[e]),p=E.event.special[g]||{},i||!p.trigger||!1!==p.trigger.apply(r,t))){if(!i&&!p.noBubble&&!b(r)){for(l=p.delegateType||g,At.test(l+g)||(s=s.parentNode);s;s=s.parentNode)h.push(s),a=s;a===(r.ownerDocument||y)&&h.push(a.defaultView||a.parentWindow||n)}for(o=0;(s=h[o++])&&!e.isPropagationStopped();)f=s,e.type=o>1?l:p.bindType||g,(u=(Q.get(s,"events")||Object.create(null))[e.type]&&Q.get(s,"handle"))&&u.apply(s,t),(u=c&&s[c])&&u.apply&&K(s)&&(e.result=u.apply(s,t),!1===e.result&&e.preventDefault());return e.type=g,i||e.isDefaultPrevented()||p._default&&!1!==p._default.apply(h.pop(),t)||!K(r)||c&&v(r[g])&&!b(r)&&((a=r[c])&&(r[c]=null),E.event.triggered=g,e.isPropagationStopped()&&f.addEventListener(g,Et),r[g](),e.isPropagationStopped()&&f.removeEventListener(g,Et),E.event.triggered=void 0,a&&(r[c]=a)),e.result}},simulate:function(e,t,r){var n=E.extend(new E.Event,r,{type:e,isSimulated:!0});E.event.trigger(n,null,t)}}),E.fn.extend({trigger:function(e,t){return this.each((function(){E.event.trigger(e,t,this)}))},triggerHandler:function(e,t){var r=this[0];if(r)return E.event.trigger(e,t,r,!0)}}),m.focusin||E.each({focus:"focusin",blur:"focusout"},(function(e,t){var r=function(e){E.event.simulate(t,e.target,E.event.fix(e))};E.event.special[t]={setup:function(){var n=this.ownerDocument||this.document||this,i=Q.access(n,t);i||n.addEventListener(e,r,!0),Q.access(n,t,(i||0)+1)},teardown:function(){var n=this.ownerDocument||this.document||this,i=Q.access(n,t)-1;i?Q.access(n,t,i):(n.removeEventListener(e,r,!0),Q.remove(n,t))}}}));var Dt=n.location,Tt={guid:Date.now()},Ct=/\?/;E.parseXML=function(e){var t,r;if(!e||"string"!=typeof e)return null;try{t=(new n.DOMParser).parseFromString(e,"text/xml")}catch(e){}return r=t&&t.getElementsByTagName("parsererror")[0],t&&!r||E.error("Invalid XML: "+(r?E.map(r.childNodes,(function(e){return e.textContent})).join("\n"):e)),t};var qt=/\[\]$/,kt=/\r?\n/g,Lt=/^(?:submit|button|image|reset|file)$/i,St=/^(?:input|select|textarea|keygen)/i;function Nt(e,t,r,n){var i;if(Array.isArray(t))E.each(t,(function(t,i){r||qt.test(e)?n(e,i):Nt(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,r,n)}));else if(r||"object"!==x(t))n(e,t);else for(i in t)Nt(e+"["+i+"]",t[i],r,n)}E.param=function(e,t){var r,n=[],i=function(e,t){var r=v(t)?t():t;n[n.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==r?"":r)};if(null==e)return"";if(Array.isArray(e)||e.jquery&&!E.isPlainObject(e))E.each(e,(function(){i(this.name,this.value)}));else for(r in e)Nt(r,e[r],t,i);return n.join("&")},E.fn.extend({serialize:function(){return E.param(this.serializeArray())},serializeArray:function(){return this.map((function(){var e=E.prop(this,"elements");return e?E.makeArray(e):this})).filter((function(){var e=this.type;return this.name&&!E(this).is(":disabled")&&St.test(this.nodeName)&&!Lt.test(e)&&(this.checked||!me.test(e))})).map((function(e,t){var r=E(this).val();return null==r?null:Array.isArray(r)?E.map(r,(function(e){return{name:t.name,value:e.replace(kt,"\r\n")}})):{name:t.name,value:r.replace(kt,"\r\n")}})).get()}});var Ot=/%20/g,jt=/#.*$/,Rt=/([?&])_=[^&]*/,Bt=/^(.*?):[ \t]*([^\r\n]*)$/gm,It=/^(?:GET|HEAD)$/,Pt=/^\/\//,Ft={},Ht={},Mt="*/".concat("*"),Ut=y.createElement("a");function Vt(e){return function(t,r){"string"!=typeof t&&(r=t,t="*");var n,i=0,o=t.toLowerCase().match(P)||[];if(v(r))for(;n=o[i++];)"+"===n[0]?(n=n.slice(1)||"*",(e[n]=e[n]||[]).unshift(r)):(e[n]=e[n]||[]).push(r)}}function $t(e,t,r,n){var i={},o=e===Ht;function s(a){var l;return i[a]=!0,E.each(e[a]||[],(function(e,a){var c=a(t,r,n);return"string"!=typeof c||o||i[c]?o?!(l=c):void 0:(t.dataTypes.unshift(c),s(c),!1)})),l}return s(t.dataTypes[0])||!i["*"]&&s("*")}function zt(e,t){var r,n,i=E.ajaxSettings.flatOptions||{};for(r in t)void 0!==t[r]&&((i[r]?e:n||(n={}))[r]=t[r]);return n&&E.extend(!0,e,n),e}Ut.href=Dt.href,E.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Dt.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(Dt.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Mt,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":E.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?zt(zt(e,E.ajaxSettings),t):zt(E.ajaxSettings,e)},ajaxPrefilter:Vt(Ft),ajaxTransport:Vt(Ht),ajax:function(e,t){"object"==typeof e&&(t=e,e=void 0),t=t||{};var r,i,o,s,a,l,c,u,p,f,d=E.ajaxSetup({},t),h=d.context||d,g=d.context&&(h.nodeType||h.jquery)?E(h):E.event,m=E.Deferred(),v=E.Callbacks("once memory"),b=d.statusCode||{},w={},_={},x="canceled",A={readyState:0,getResponseHeader:function(e){var t;if(c){if(!s)for(s={};t=Bt.exec(o);)s[t[1].toLowerCase()+" "]=(s[t[1].toLowerCase()+" "]||[]).concat(t[2]);t=s[e.toLowerCase()+" "]}return null==t?null:t.join(", ")},getAllResponseHeaders:function(){return c?o:null},setRequestHeader:function(e,t){return null==c&&(e=_[e.toLowerCase()]=_[e.toLowerCase()]||e,w[e]=t),this},overrideMimeType:function(e){return null==c&&(d.mimeType=e),this},statusCode:function(e){var t;if(e)if(c)A.always(e[A.status]);else for(t in e)b[t]=[b[t],e[t]];return this},abort:function(e){var t=e||x;return r&&r.abort(t),D(0,t),this}};if(m.promise(A),d.url=((e||d.url||Dt.href)+"").replace(Pt,Dt.protocol+"//"),d.type=t.method||t.type||d.method||d.type,d.dataTypes=(d.dataType||"*").toLowerCase().match(P)||[""],null==d.crossDomain){l=y.createElement("a");try{l.href=d.url,l.href=l.href,d.crossDomain=Ut.protocol+"//"+Ut.host!=l.protocol+"//"+l.host}catch(e){d.crossDomain=!0}}if(d.data&&d.processData&&"string"!=typeof d.data&&(d.data=E.param(d.data,d.traditional)),$t(Ft,d,t,A),c)return A;for(p in(u=E.event&&d.global)&&0==E.active++&&E.event.trigger("ajaxStart"),d.type=d.type.toUpperCase(),d.hasContent=!It.test(d.type),i=d.url.replace(jt,""),d.hasContent?d.data&&d.processData&&0===(d.contentType||"").indexOf("application/x-www-form-urlencoded")&&(d.data=d.data.replace(Ot,"+")):(f=d.url.slice(i.length),d.data&&(d.processData||"string"==typeof d.data)&&(i+=(Ct.test(i)?"&":"?")+d.data,delete d.data),!1===d.cache&&(i=i.replace(Rt,"$1"),f=(Ct.test(i)?"&":"?")+"_="+Tt.guid+++f),d.url=i+f),d.ifModified&&(E.lastModified[i]&&A.setRequestHeader("If-Modified-Since",E.lastModified[i]),E.etag[i]&&A.setRequestHeader("If-None-Match",E.etag[i])),(d.data&&d.hasContent&&!1!==d.contentType||t.contentType)&&A.setRequestHeader("Content-Type",d.contentType),A.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+("*"!==d.dataTypes[0]?", "+Mt+"; q=0.01":""):d.accepts["*"]),d.headers)A.setRequestHeader(p,d.headers[p]);if(d.beforeSend&&(!1===d.beforeSend.call(h,A,d)||c))return A.abort();if(x="abort",v.add(d.complete),A.done(d.success),A.fail(d.error),r=$t(Ht,d,t,A)){if(A.readyState=1,u&&g.trigger("ajaxSend",[A,d]),c)return A;d.async&&d.timeout>0&&(a=n.setTimeout((function(){A.abort("timeout")}),d.timeout));try{c=!1,r.send(w,D)}catch(e){if(c)throw e;D(-1,e)}}else D(-1,"No Transport");function D(e,t,s,l){var p,f,y,w,_,x=t;c||(c=!0,a&&n.clearTimeout(a),r=void 0,o=l||"",A.readyState=e>0?4:0,p=e>=200&&e<300||304===e,s&&(w=function(e,t,r){for(var n,i,o,s,a=e.contents,l=e.dataTypes;"*"===l[0];)l.shift(),void 0===n&&(n=e.mimeType||t.getResponseHeader("Content-Type"));if(n)for(i in a)if(a[i]&&a[i].test(n)){l.unshift(i);break}if(l[0]in r)o=l[0];else{for(i in r){if(!l[0]||e.converters[i+" "+l[0]]){o=i;break}s||(s=i)}o=o||s}if(o)return o!==l[0]&&l.unshift(o),r[o]}(d,A,s)),!p&&E.inArray("script",d.dataTypes)>-1&&E.inArray("json",d.dataTypes)<0&&(d.converters["text script"]=function(){}),w=function(e,t,r,n){var i,o,s,a,l,c={},u=e.dataTypes.slice();if(u[1])for(s in e.converters)c[s.toLowerCase()]=e.converters[s];for(o=u.shift();o;)if(e.responseFields[o]&&(r[e.responseFields[o]]=t),!l&&n&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),l=o,o=u.shift())if("*"===o)o=l;else if("*"!==l&&l!==o){if(!(s=c[l+" "+o]||c["* "+o]))for(i in c)if((a=i.split(" "))[1]===o&&(s=c[l+" "+a[0]]||c["* "+a[0]])){!0===s?s=c[i]:!0!==c[i]&&(o=a[0],u.unshift(a[1]));break}if(!0!==s)if(s&&e.throws)t=s(t);else try{t=s(t)}catch(e){return{state:"parsererror",error:s?e:"No conversion from "+l+" to "+o}}}return{state:"success",data:t}}(d,w,A,p),p?(d.ifModified&&((_=A.getResponseHeader("Last-Modified"))&&(E.lastModified[i]=_),(_=A.getResponseHeader("etag"))&&(E.etag[i]=_)),204===e||"HEAD"===d.type?x="nocontent":304===e?x="notmodified":(x=w.state,f=w.data,p=!(y=w.error))):(y=x,!e&&x||(x="error",e<0&&(e=0))),A.status=e,A.statusText=(t||x)+"",p?m.resolveWith(h,[f,x,A]):m.rejectWith(h,[A,x,y]),A.statusCode(b),b=void 0,u&&g.trigger(p?"ajaxSuccess":"ajaxError",[A,d,p?f:y]),v.fireWith(h,[A,x]),u&&(g.trigger("ajaxComplete",[A,d]),--E.active||E.event.trigger("ajaxStop")))}return A},getJSON:function(e,t,r){return E.get(e,t,r,"json")},getScript:function(e,t){return E.get(e,void 0,t,"script")}}),E.each(["get","post"],(function(e,t){E[t]=function(e,r,n,i){return v(r)&&(i=i||n,n=r,r=void 0),E.ajax(E.extend({url:e,type:t,dataType:i,data:r,success:n},E.isPlainObject(e)&&e))}})),E.ajaxPrefilter((function(e){var t;for(t in e.headers)"content-type"===t.toLowerCase()&&(e.contentType=e.headers[t]||"")})),E._evalUrl=function(e,t,r){return E.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,converters:{"text script":function(){}},dataFilter:function(e){E.globalEval(e,t,r)}})},E.fn.extend({wrapAll:function(e){var t;return this[0]&&(v(e)&&(e=e.call(this[0])),t=E(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map((function(){for(var e=this;e.firstElementChild;)e=e.firstElementChild;return e})).append(this)),this},wrapInner:function(e){return v(e)?this.each((function(t){E(this).wrapInner(e.call(this,t))})):this.each((function(){var t=E(this),r=t.contents();r.length?r.wrapAll(e):t.append(e)}))},wrap:function(e){var t=v(e);return this.each((function(r){E(this).wrapAll(t?e.call(this,r):e)}))},unwrap:function(e){return this.parent(e).not("body").each((function(){E(this).replaceWith(this.childNodes)})),this}}),E.expr.pseudos.hidden=function(e){return!E.expr.pseudos.visible(e)},E.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},E.ajaxSettings.xhr=function(){try{return new n.XMLHttpRequest}catch(e){}};var Gt={0:200,1223:204},Wt=E.ajaxSettings.xhr();m.cors=!!Wt&&"withCredentials"in Wt,m.ajax=Wt=!!Wt,E.ajaxTransport((function(e){var t,r;if(m.cors||Wt&&!e.crossDomain)return{send:function(i,o){var s,a=e.xhr();if(a.open(e.type,e.url,e.async,e.username,e.password),e.xhrFields)for(s in e.xhrFields)a[s]=e.xhrFields[s];for(s in e.mimeType&&a.overrideMimeType&&a.overrideMimeType(e.mimeType),e.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest"),i)a.setRequestHeader(s,i[s]);t=function(e){return function(){t&&(t=r=a.onload=a.onerror=a.onabort=a.ontimeout=a.onreadystatechange=null,"abort"===e?a.abort():"error"===e?"number"!=typeof a.status?o(0,"error"):o(a.status,a.statusText):o(Gt[a.status]||a.status,a.statusText,"text"!==(a.responseType||"text")||"string"!=typeof a.responseText?{binary:a.response}:{text:a.responseText},a.getAllResponseHeaders()))}},a.onload=t(),r=a.onerror=a.ontimeout=t("error"),void 0!==a.onabort?a.onabort=r:a.onreadystatechange=function(){4===a.readyState&&n.setTimeout((function(){t&&r()}))},t=t("abort");try{a.send(e.hasContent&&e.data||null)}catch(e){if(t)throw e}},abort:function(){t&&t()}}})),E.ajaxPrefilter((function(e){e.crossDomain&&(e.contents.script=!1)})),E.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return E.globalEval(e),e}}}),E.ajaxPrefilter("script",(function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")})),E.ajaxTransport("script",(function(e){var t,r;if(e.crossDomain||e.scriptAttrs)return{send:function(n,i){t=E("