diff --git a/CMakeLists.txt b/CMakeLists.txt index 82284d6..e71f86b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,10 +35,11 @@ set(CORE_SOURCES ) if(TARGET_IMX) - list(APPEND CORE_SOURCES - src/Core/Platform/FBDisplay.cpp - src/Core/Platform/Ap3216cPhotoSensor.cpp - ) + list(APPEND CORE_SOURCES + src/Core/Platform/FBDisplay.cpp + src/Core/Platform/Ap3216cPhotoSensor.cpp + src/Core/Platform/Imx6ullAlphaLed.cpp + ) else() list(APPEND CORE_SOURCES src/Core/Platform/SDLDisplay.cpp diff --git a/src/Apps/Game/CMakeLists.txt b/src/Apps/Game/CMakeLists.txt index 5101059..f3be308 100644 --- a/src/Apps/Game/CMakeLists.txt +++ b/src/Apps/Game/CMakeLists.txt @@ -2,22 +2,45 @@ set(TOM_GAME_TARGET IMX6U-Game) set(TOM_ATLAS_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/generated/tom_atlas.h") set_source_files_properties(${TOM_ATLAS_HEADER} PROPERTIES GENERATED TRUE) +set(TOM_AUDIO_GENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated") +set(TOM_AUDIO_HEADER "${TOM_AUDIO_GENERATED_DIR}/TomAudioData.h") +set(TOM_AUDIO_SOURCE "${TOM_AUDIO_GENERATED_DIR}/TomAudioData.cpp") +add_custom_command( + OUTPUT ${TOM_AUDIO_HEADER} ${TOM_AUDIO_SOURCE} + COMMAND ${CMAKE_COMMAND} + "-DOPEN_LIGHT_WAV=${CMAKE_CURRENT_SOURCE_DIR}/assets/audio/OpenLight.wav" + "-DCLOSE_LIGHT_WAV=${CMAKE_CURRENT_SOURCE_DIR}/assets/audio/CloseLight.wav" + "-DOUTPUT_HEADER=${TOM_AUDIO_HEADER}" + "-DOUTPUT_SOURCE=${TOM_AUDIO_SOURCE}" + -P "${CMAKE_CURRENT_SOURCE_DIR}/tools/EmbedTomAudio.cmake" + DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/assets/audio/OpenLight.wav" + "${CMAKE_CURRENT_SOURCE_DIR}/assets/audio/CloseLight.wav" + "${CMAKE_CURRENT_SOURCE_DIR}/tools/EmbedTomAudio.cmake" + COMMENT "Embedding Tom voice response WAV files" + VERBATIM +) +set_source_files_properties(${TOM_AUDIO_HEADER} ${TOM_AUDIO_SOURCE} PROPERTIES GENERATED TRUE) + add_executable(${TOM_GAME_TARGET} Main.cpp src/app/TomGameApp.cpp src/audio/VoiceEffect.cpp src/audio/VoicePlayer.cpp src/audio/VoiceRecorder.cpp + src/audio/WavAudioData.cpp src/gameplay/KeywordCommandRouter.cpp src/gameplay/SpriteButton.cpp src/gameplay/TomAnimator.cpp src/gameplay/TomHud.cpp src/gameplay/VoiceInteractionController.cpp src/recognition/KeywordRecognizer.cpp - src/recognition/ResBnKwsRecognizer.cpp - src/recognition/ResBnKwsModelData.cpp + src/recognition/ResBnKwsCnRecognizer.cpp + src/recognition/ResBnKwsCnModelData.cpp src/ui/TomSettingsPanel.cpp ${TOM_ATLAS_HEADER} + ${TOM_AUDIO_HEADER} + ${TOM_AUDIO_SOURCE} ) target_include_directories(${TOM_GAME_TARGET} PRIVATE @@ -28,10 +51,29 @@ target_include_directories(${TOM_GAME_TARGET} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/recognition ${CMAKE_CURRENT_SOURCE_DIR}/src/ui ${CMAKE_CURRENT_SOURCE_DIR}/generated + ${TOM_AUDIO_GENERATED_DIR} ) imx6u_configure_app_target(${TOM_GAME_TARGET}) +if(BUILD_TESTING AND NOT TARGET_IMX) + add_executable(TomAudioDataTests EXCLUDE_FROM_ALL + tests/TomAudioDataTests.cpp + src/audio/WavAudioData.cpp + src/gameplay/KeywordCommandRouter.cpp + src/recognition/KeywordRecognizer.cpp + ${TOM_AUDIO_HEADER} + ${TOM_AUDIO_SOURCE} + ) + target_include_directories(TomAudioDataTests PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src/audio + ${CMAKE_CURRENT_SOURCE_DIR}/src/gameplay + ${CMAKE_CURRENT_SOURCE_DIR}/src/recognition + ${TOM_AUDIO_GENERATED_DIR} + ) + add_test(NAME tom_audio_data COMMAND TomAudioDataTests) +endif() + if(CMAKE_CROSSCOMPILING AND NOT EXISTS "${TOM_ATLAS_HEADER}") message(FATAL_ERROR "Tom atlas header is missing. Run GenerateTomAtlasHeader in a host build before cross compiling." diff --git a/src/Apps/Game/Main.cpp b/src/Apps/Game/Main.cpp index 52a43c9..e419049 100644 --- a/src/Apps/Game/Main.cpp +++ b/src/Apps/Game/Main.cpp @@ -13,7 +13,7 @@ #include "Timer.h" #include "app/TomGameApp.h" #include "recognition/KeywordRecognizer.h" -#include "recognition/ResBnKwsRecognizer.h" +#include "recognition/ResBnKwsCnRecognizer.h" #ifdef TARGET_IMX #include "FBDisplay.h" @@ -348,8 +348,9 @@ int main(int argc, char *argv[]) Platform::DefaultAudioOutput audioOutput; Platform::DefaultButtonInput buttonInput; Platform::DefaultPointerInput pointerInput; + Platform::DefaultBoardLed boardLed; - Game::ResBnKwsRecognizer keywordRecognizer; + Game::ResBnKwsCnRecognizer keywordRecognizer; keywordRecognizer.set_confidence_threshold(options.kws_threshold); keywordRecognizer.set_input_gain(options.kws_input_gain); @@ -361,6 +362,10 @@ int main(int argc, char *argv[]) { std::cerr << "[WARN] Pointer input init failed; on-screen button trigger is disabled." << std::endl; } + if (!boardLed.init()) + { + std::cerr << "[WARN] Board LED init failed; voice responses remain enabled." << std::endl; + } Core::DrawContext ctx(ScreenWidth, ScreenHeight); Core::Timer timer(options.target_fps); @@ -372,6 +377,7 @@ int main(int argc, char *argv[]) &audioOutput, &buttonInput, &pointerInput, + &boardLed, &keywordRecognizer, options.settings_path); app.configure_audio(options.audio_sample_rate, options.audio_channels); diff --git a/src/Apps/Game/README.md b/src/Apps/Game/README.md index 64706ae..603eedd 100644 --- a/src/Apps/Game/README.md +++ b/src/Apps/Game/README.md @@ -3,13 +3,14 @@ 本文说明 TomGame 当前的关键词识别方式,以及 Windows 验证、WSL 交叉编译、Windows SCP 部署和 IMX6ULL 板端运行流程。 -TomGame 使用内嵌的 `ResBnKwsRecognizer` INT8 模型,不需要 Python、 +TomGame 使用内嵌的 `ResBnKwsCnRecognizer` INT8 模型,不需要 Python、 TensorFlow、TensorFlow Lite 运行库或外部模型文件。 -模型支持以下 12 个类别: +模型支持以下 14 个类别: ```text -Down, Go, Left, No, Off, On, Right, Stop, Up, Yes, Silence, Unknown +Down, Go, Left, No, Off, On, Right, Stop, Up, Yes, +OpenLight, CloseLight, Silence, Unknown ``` 当前游戏动作映射: @@ -17,8 +18,15 @@ Down, Go, Left, No, Off, On, Right, Stop, Up, Yes, Silence, Unknown ```text Up / On -> Jump Stop -> Idle +OpenLight -> 打开 ALPHA 板载 LED,播放 Tom 音效语音 +CloseLight -> 关闭 ALPHA 板载 LED,播放 Tom 音效语音 ``` +`OpenLight.wav` 和 `CloseLight.wav` 会在构建阶段转换成只读 C++ 数据并 +编译进可执行文件,板端部署时不需要额外复制 WAV 文件。板载 LED 使用 +正点原子 I.MX6U-ALPHA 的 `GPIO1_IO03`,低电平点亮;程序优先使用 +`/dev/gpioled` 等正点原子教学驱动设备,并兼容 GPIO sysfs。 + ## Windows 验证 Windows 版本和板端版本使用同一套 C++ 特征提取、INT8 模型推理和 @@ -49,7 +57,7 @@ cmake --build build --config Release --target IMX6U-Game 识别成功或未达到阈值时,控制台会打印类似日志: ```text -[INFO] ResBN INT8 KWS result: command=Up, confidence=0.92, windows=... +[INFO] ResBN-CN INT8 KWS result: class=Up, confidence=0.92, windows=... ``` Windows Desktop 和全部游戏可以一起构建: diff --git a/src/Apps/Game/assets/audio/CloseLight.wav b/src/Apps/Game/assets/audio/CloseLight.wav new file mode 100644 index 0000000..f20577e Binary files /dev/null and b/src/Apps/Game/assets/audio/CloseLight.wav differ diff --git a/src/Apps/Game/assets/audio/OpenLight.wav b/src/Apps/Game/assets/audio/OpenLight.wav new file mode 100644 index 0000000..b6ea8d9 Binary files /dev/null and b/src/Apps/Game/assets/audio/OpenLight.wav differ diff --git a/src/Apps/Game/src/app/TomGameApp.cpp b/src/Apps/Game/src/app/TomGameApp.cpp index bd7fda3..9453e7e 100644 --- a/src/Apps/Game/src/app/TomGameApp.cpp +++ b/src/Apps/Game/src/app/TomGameApp.cpp @@ -1,4 +1,6 @@ #include "TomGameApp.h" +#include "IBoardLed.h" +#include "TomAudioData.h" #include "IButtonInput.h" #include "Color.h" #include "DrawContext.h" @@ -56,10 +58,12 @@ namespace Game Platform::IAudioOutput *audioOutput, Platform::IButtonInput *buttonInput, Platform::IPointerInput *pointerInput, + Platform::IBoardLed *boardLed, IKeywordRecognizer *keywordRecognizer, const std::string& settingsPath) : buttonInput(buttonInput), pointerInput(pointerInput), + boardLed(boardLed), state(TomGameState::Idle), voiceMode(VoiceMode::PitchRepeat), recordingVoiceMode(VoiceMode::PitchRepeat), @@ -384,6 +388,28 @@ namespace Game back_to_idle(); break; + case KeywordGameAction::OpenLight: + if (boardLed != nullptr) + { + boardLed->set_on(true); + } + handle_voice_result(voiceController.play_effected_wav( + EmbeddedAudio::OpenLightWav, + EmbeddedAudio::OpenLightWavSize, + "OpenLight.wav")); + break; + + case KeywordGameAction::CloseLight: + if (boardLed != nullptr) + { + boardLed->set_on(false); + } + handle_voice_result(voiceController.play_effected_wav( + EmbeddedAudio::CloseLightWav, + EmbeddedAudio::CloseLightWavSize, + "CloseLight.wav")); + break; + case KeywordGameAction::None: default: back_to_idle(); diff --git a/src/Apps/Game/src/app/TomGameApp.h b/src/Apps/Game/src/app/TomGameApp.h index 3f07120..295cd32 100644 --- a/src/Apps/Game/src/app/TomGameApp.h +++ b/src/Apps/Game/src/app/TomGameApp.h @@ -19,6 +19,7 @@ namespace Platform { class IAudioInput; class IAudioOutput; + class IBoardLed; class IButtonInput; class IPointerInput; } @@ -32,6 +33,7 @@ namespace Game private: Platform::IButtonInput* buttonInput; Platform::IPointerInput* pointerInput; + Platform::IBoardLed* boardLed; TomGameState state; VoiceMode voiceMode; @@ -75,6 +77,7 @@ namespace Game Platform::IAudioOutput* audioOutput, Platform::IButtonInput* buttonInput, Platform::IPointerInput* pointerInput, + Platform::IBoardLed* boardLed, IKeywordRecognizer* keywordRecognizer, const std::string& settingsPath); diff --git a/src/Apps/Game/src/audio/WavAudioData.cpp b/src/Apps/Game/src/audio/WavAudioData.cpp new file mode 100644 index 0000000..38be7a9 --- /dev/null +++ b/src/Apps/Game/src/audio/WavAudioData.cpp @@ -0,0 +1,95 @@ +#include "WavAudioData.h" +#include + +namespace +{ + static uint16_t ReadU16(const uint8_t* bytes) + { + return static_cast(bytes[0]) | + (static_cast(bytes[1]) << 8u); + } + + static uint32_t ReadU32(const uint8_t* bytes) + { + return static_cast(bytes[0]) | + (static_cast(bytes[1]) << 8u) | + (static_cast(bytes[2]) << 16u) | + (static_cast(bytes[3]) << 24u); + } + + static bool HasTag(const uint8_t* bytes, const char* tag) + { + return std::memcmp(bytes, tag, 4u) == 0; + } +} + +namespace Game +{ + bool DecodePcm16Wav( + const uint8_t* bytes, + size_t byteCount, + WavAudioData& audio) + { + audio = WavAudioData(); + if (bytes == nullptr || byteCount < 12u || + !HasTag(bytes, "RIFF") || !HasTag(bytes + 8u, "WAVE")) + { + return false; + } + + uint16_t audioFormat = 0u; + uint16_t bitsPerSample = 0u; + uint16_t blockAlign = 0u; + const uint8_t* sampleBytes = nullptr; + size_t sampleByteCount = 0u; + + size_t offset = 12u; + while (offset + 8u <= byteCount) + { + const uint8_t* chunk = bytes + offset; + const uint32_t chunkSize = ReadU32(chunk + 4u); + offset += 8u; + if (static_cast(chunkSize) > byteCount - offset) + { + return false; + } + + if (HasTag(chunk, "fmt ")) + { + if (chunkSize < 16u) + { + return false; + } + audioFormat = ReadU16(bytes + offset); + audio.channels = ReadU16(bytes + offset + 2u); + audio.sampleRate = ReadU32(bytes + offset + 4u); + blockAlign = ReadU16(bytes + offset + 12u); + bitsPerSample = ReadU16(bytes + offset + 14u); + } + else if (HasTag(chunk, "data")) + { + sampleBytes = bytes + offset; + sampleByteCount = chunkSize; + } + + offset += static_cast(chunkSize); + if ((chunkSize & 1u) != 0u && offset < byteCount) + { + ++offset; + } + } + + if (audioFormat != 1u || bitsPerSample != 16u || + audio.channels == 0u || audio.sampleRate == 0u || + blockAlign != audio.channels * sizeof(int16_t) || + sampleBytes == nullptr || sampleByteCount == 0u || + (sampleByteCount % blockAlign) != 0u) + { + return false; + } + + audio.samples.resize(sampleByteCount / sizeof(int16_t)); + std::memcpy(audio.samples.data(), sampleBytes, sampleByteCount); + return true; + } +} diff --git a/src/Apps/Game/src/audio/WavAudioData.h b/src/Apps/Game/src/audio/WavAudioData.h new file mode 100644 index 0000000..f91c92f --- /dev/null +++ b/src/Apps/Game/src/audio/WavAudioData.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +namespace Game +{ + struct WavAudioData + { + std::vector samples; + uint32_t sampleRate; + uint32_t channels; + + WavAudioData() : sampleRate(0u), channels(0u) {} + }; + + bool DecodePcm16Wav( + const uint8_t* bytes, + size_t byteCount, + WavAudioData& audio); +} diff --git a/src/Apps/Game/src/gameplay/KeywordCommandRouter.cpp b/src/Apps/Game/src/gameplay/KeywordCommandRouter.cpp index 53da863..8855571 100644 --- a/src/Apps/Game/src/gameplay/KeywordCommandRouter.cpp +++ b/src/Apps/Game/src/gameplay/KeywordCommandRouter.cpp @@ -13,12 +13,19 @@ namespace Game case KeywordCommand::Stop: return KeywordGameAction::BackToIdle; + case KeywordCommand::OpenLight: + return KeywordGameAction::OpenLight; + + case KeywordCommand::CloseLight: + return KeywordGameAction::CloseLight; + case KeywordCommand::Down: case KeywordCommand::Left: case KeywordCommand::Right: case KeywordCommand::Go: case KeywordCommand::Yes: case KeywordCommand::No: + case KeywordCommand::Off: case KeywordCommand::None: default: return KeywordGameAction::None; diff --git a/src/Apps/Game/src/gameplay/KeywordCommandRouter.h b/src/Apps/Game/src/gameplay/KeywordCommandRouter.h index d1bf33b..c00cab3 100644 --- a/src/Apps/Game/src/gameplay/KeywordCommandRouter.h +++ b/src/Apps/Game/src/gameplay/KeywordCommandRouter.h @@ -8,7 +8,9 @@ namespace Game { None, Jump, - BackToIdle + BackToIdle, + OpenLight, + CloseLight }; class KeywordCommandRouter diff --git a/src/Apps/Game/src/gameplay/VoiceInteractionController.cpp b/src/Apps/Game/src/gameplay/VoiceInteractionController.cpp index 5ec76ee..7c7c0bb 100644 --- a/src/Apps/Game/src/gameplay/VoiceInteractionController.cpp +++ b/src/Apps/Game/src/gameplay/VoiceInteractionController.cpp @@ -1,5 +1,6 @@ #include "VoiceInteractionController.h" #include "../audio/VoiceEffect.h" +#include "../audio/WavAudioData.h" #include "IAudioInput.h" #include "IAudioOutput.h" #include @@ -233,9 +234,13 @@ namespace Game return VoiceInteractionResult(VoiceInteractionEvent::KeywordRecognized, result); } - VoiceInteractionResult VoiceInteractionController::start_speaking(const std::vector& samples) + VoiceInteractionResult VoiceInteractionController::start_speaking( + const std::vector& samples, + uint32_t sourceSampleRate, + uint32_t sourceChannels) { - if (samples.empty() || audioOutput == nullptr) + if (samples.empty() || sourceSampleRate == 0u || + sourceChannels == 0u || audioOutput == nullptr) { return VoiceInteractionResult(VoiceInteractionEvent::IdleRequested); } @@ -256,13 +261,20 @@ namespace Game const uint32_t playbackSampleRate = audioOutput->get_sample_rate(); const uint32_t playbackChannels = audioOutput->get_channels(); std::vector playbackSamples = samples; - if (playbackChannels != audioChannels) + if (playbackChannels != sourceChannels) { - playbackSamples = VoiceEffect::convert_channels(playbackSamples, audioChannels, playbackChannels); + playbackSamples = VoiceEffect::convert_channels( + playbackSamples, + sourceChannels, + playbackChannels); } - if (playbackSampleRate != audioSampleRate) + if (playbackSampleRate != sourceSampleRate) { - playbackSamples = VoiceEffect::resample(playbackSamples, audioSampleRate, playbackSampleRate, playbackChannels); + playbackSamples = VoiceEffect::resample( + playbackSamples, + sourceSampleRate, + playbackSampleRate, + playbackChannels); } player.set_voice(playbackSamples, playbackSampleRate, playbackChannels); @@ -311,21 +323,10 @@ namespace Game << recorder.get_last_volume() << std::endl; - std::vector samples = VoiceEffect::amplify(recordedSamples, inputGain); - const std::vector trimmedSamples = VoiceEffect::trim_silence(samples, 0.02f, audioChannels); - const size_t minPlaybackSamples = static_cast(audioSampleRate * audioChannels / 4u); - if (!samples.empty() && trimmedSamples.size() < minPlaybackSamples) - { - std::cerr - << "[WARN] Trimmed recording is too short (" - << trimmedSamples.size() - << " samples); playing untrimmed samples for diagnostics." - << std::endl; - } - else - { - samples = trimmedSamples; - } + std::vector samples = apply_voice_effect( + recordedSamples, + audioSampleRate, + audioChannels); std::cout << "[INFO] Playback prepared: samples=" @@ -336,9 +337,62 @@ namespace Game << audioChannels << std::endl; - samples = VoiceEffect::pitch_up(samples, pitchFactor, audioChannels); - samples = VoiceEffect::amplify(samples, outputGain); - return start_speaking(samples); + return start_speaking(samples, audioSampleRate, audioChannels); + } + + VoiceInteractionResult VoiceInteractionController::play_effected_wav( + const uint8_t* wavBytes, + size_t wavByteCount, + const char* name) + { + WavAudioData audio; + if (!DecodePcm16Wav(wavBytes, wavByteCount, audio)) + { + std::cerr + << "[WARN] Failed to decode embedded WAV: " + << (name != nullptr ? name : "(unnamed)") + << std::endl; + return VoiceInteractionResult(VoiceInteractionEvent::IdleRequested); + } + + std::cout + << "[INFO] Embedded WAV loaded: " + << (name != nullptr ? name : "(unnamed)") + << ", samples=" << audio.samples.size() + << ", rate=" << audio.sampleRate + << ", channels=" << audio.channels + << std::endl; + + const std::vector effectedSamples = apply_voice_effect( + audio.samples, + audio.sampleRate, + audio.channels); + return start_speaking(effectedSamples, audio.sampleRate, audio.channels); + } + + std::vector VoiceInteractionController::apply_voice_effect( + const std::vector& sourceSamples, + uint32_t sampleRate, + uint32_t channels) const + { + std::vector samples = VoiceEffect::amplify(sourceSamples, inputGain); + const std::vector trimmedSamples = VoiceEffect::trim_silence(samples, 0.02f, channels); + const size_t minPlaybackSamples = static_cast(sampleRate * channels / 4u); + if (!samples.empty() && trimmedSamples.size() < minPlaybackSamples) + { + std::cerr + << "[WARN] Trimmed voice is too short (" + << trimmedSamples.size() + << " samples); using untrimmed audio." + << std::endl; + } + else + { + samples = trimmedSamples; + } + + samples = VoiceEffect::pitch_up(samples, pitchFactor, channels); + return VoiceEffect::amplify(samples, outputGain); } void VoiceInteractionController::stop_all() diff --git a/src/Apps/Game/src/gameplay/VoiceInteractionController.h b/src/Apps/Game/src/gameplay/VoiceInteractionController.h index 46225d6..4386afa 100644 --- a/src/Apps/Game/src/gameplay/VoiceInteractionController.h +++ b/src/Apps/Game/src/gameplay/VoiceInteractionController.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -61,7 +62,14 @@ namespace Game VoiceInteractionResult finish_pitch_repeat_recording(); VoiceInteractionResult finish_keyword_recording(); VoiceInteractionResult try_recognize_keyword(const std::vector& samples); - VoiceInteractionResult start_speaking(const std::vector& samples); + VoiceInteractionResult start_speaking( + const std::vector& samples, + uint32_t sourceSampleRate, + uint32_t sourceChannels); + std::vector apply_voice_effect( + const std::vector& samples, + uint32_t sampleRate, + uint32_t channels) const; public: VoiceInteractionController( @@ -76,6 +84,10 @@ namespace Game VoiceInteractionResult update_keyword_recording(uint32_t deltaMs); VoiceInteractionResult update_speaking(uint32_t deltaMs); VoiceInteractionResult repeat_last_recording(); + VoiceInteractionResult play_effected_wav( + const uint8_t* wavBytes, + size_t wavByteCount, + const char* name); void stop_all(); void set_input_gain(float gain); void set_output_gain(float gain); diff --git a/src/Apps/Game/src/recognition/KeywordRecognizer.cpp b/src/Apps/Game/src/recognition/KeywordRecognizer.cpp index c0ca8c3..cc7fcac 100644 --- a/src/Apps/Game/src/recognition/KeywordRecognizer.cpp +++ b/src/Apps/Game/src/recognition/KeywordRecognizer.cpp @@ -36,6 +36,12 @@ namespace Game return "No"; case KeywordCommand::On: return "On"; + case KeywordCommand::Off: + return "Off"; + case KeywordCommand::OpenLight: + return "OpenLight"; + case KeywordCommand::CloseLight: + return "CloseLight"; case KeywordCommand::None: default: return "None"; diff --git a/src/Apps/Game/src/recognition/KeywordRecognizer.h b/src/Apps/Game/src/recognition/KeywordRecognizer.h index 8a54369..a97c00f 100644 --- a/src/Apps/Game/src/recognition/KeywordRecognizer.h +++ b/src/Apps/Game/src/recognition/KeywordRecognizer.h @@ -16,7 +16,10 @@ namespace Game Stop, Yes, No, - On + On, + Off, + OpenLight, + CloseLight }; struct KeywordRecognitionResult diff --git a/src/Apps/Game/src/recognition/MODLE.md b/src/Apps/Game/src/recognition/MODLE.md new file mode 100644 index 0000000..c758064 --- /dev/null +++ b/src/Apps/Game/src/recognition/MODLE.md @@ -0,0 +1,392 @@ +# ResBN INT8 关键词识别模型研究总结 + +## 1. 研究目标 + +本项目面向 IMX6ULL-ALPHA 的离线关键词识别部署,核心约束包括: + +- 使用 Google Speech Commands v2 数据集; +- 保持 1 秒、16 kHz、单声道输入; +- 输入特征固定为 `49 x 10 x 1` MFCC,兼容现有识别流程; +- 支持 10 个命令词,以及 `_silence_` 和 `_unknown_`,共 12 类; +- 模型必须能够转换为 INT8,并在不依赖 TensorFlow Lite/LiteRT 运行时的情况下部署; +- 长音频识别不能为每个滑动窗口重复计算完整 MFCC; +- 在精度、模型体积、内存占用和 Cortex-A7 推理开销之间取得平衡。 + +最终选择的方案是: + +> **1 秒 ResBN DS-CNN + 全 INT8 量化 + 整段 mel 特征缓存 + 多窗口 logits 融合。** + +--- + +## 2. Baseline 模型 + +初始模型采用标准轻量 DS-CNN: + +```text +49 x 10 x 1 MFCC + ↓ +Conv2D 10x4, stride 2x2, 64 channels + ↓ +Depthwise 3x3 + Pointwise 1x1,共 4 个 DS-CNN 块 + ↓ +Global Average Pooling + ↓ +Dense 12 + Softmax +``` + +特征参数为: + +- 采样率:16 kHz; +- 窗长/FFT:512 点,即 32 ms; +- 帧移:320 点,即 20 ms; +- mel 滤波器:40 维; +- MFCC:保留前 10 维; +- 1 秒音频生成 49 帧 MFCC。 + +Baseline 在 4,890 条独立测试样本上的准确率为 **88.92%**,模型大小为 **44,856 bytes**。 + +Baseline 的主要问题不是模型过大,而是: + +- 4 个 DS-CNN 块串联后缺少跨层信息通道; +- 深层特征只能依赖连续卷积传播,训练稳定性和特征复用能力有限; +- 输出直接经过 Softmax,不利于后续对多个窗口的 logits 做统一融合; +- 长音频处理会重复执行归一化、FFT、mel 滤波和 DCT。 + +--- + +## 3. 模型迭代研究过程 + +### 3.1 引入残差连接和 Batch Normalization + +我们首先设计了 ResBN 模型,保持输入尺寸、通道数和 DS-CNN 主体不变,只修改每个深度可分离卷积块: + +```text +输入 identity + ├──────────────────────────────┐ + ↓ │ +DepthwiseConv 3x3 │ + ↓ │ +BatchNorm │ + ↓ │ +ReLU │ + ↓ │ +PointwiseConv 1x1 │ + ↓ │ +BatchNorm │ + ↓ │ +Add(identity) ←───────────────────┘ + ↓ +ReLU +``` + +该设计的价值: + +- 残差连接不增加卷积参数,仅增加低成本逐元素加法; +- 保留浅层信息,改善 4 层 DS-CNN 堆叠后的梯度传播; +- BN 提高训练稳定性,并在导出时折叠进卷积权重和 bias,部署时不需要单独执行 BN; +- 输出改为原始 logits,Softmax 移到多窗口融合阶段执行; +- 不改变 `49 x 10` 输入合同,不需要重新设计前端采集和窗口协议。 + +ResBN Dynamic TFLite 的测试准确率达到 **91.10%**,相比 Baseline 提升 **2.19 个百分点**。 + +### 3.2 评估 SE 通道注意力 + +在 ResBN 基础上,我们继续设计了 SE 模型: + +- 每个残差 DS-CNN 块增加通道注意力; +- SE ratio 为 8; +- 使用 `hard_sigmoid`,降低量化和 Cortex-A7 上的激活开销; +- 通过全局平均池化、降维、升维和逐通道乘法调整 64 个通道的重要性。 + +SE 模型的测试准确率为 **91.19%**,只比 ResBN Dynamic 高 **0.08 个百分点**,但模型大小从 **46,392 bytes** 增加到 **71,040 bytes**,增长约 **53.13%**。 + +SE 的最佳验证准确率较高,但在独立测试集上的提升很小,而且类别收益不均衡: + +- `go`、`right`、`no` 有改善; +- `down`、`up`、`unknown` 出现回退; +- 增加了额外的全局池化、两层通道映射、激活和逐通道乘法。 + +因此,SE 在本次数据和模型规模下没有形成足够高的精度/开销收益比,最终没有作为部署模型。 + +### 3.3 保留 1 秒模型,而不是扩展为 2 秒输入 + +研究中对比了两种长音频方案: + +1. 将模型输入扩大为 2 秒,减少推理窗口数量; +2. 保留已验证的 1 秒模型,对整段音频只计算一次频谱特征,再切分特征窗口。 + +最终选择第二种方案,原因是: + +- 1 秒模型已经完成训练、量化和精度验证; +- 不改变输入尺寸和卷积结构,部署风险更低; +- 2 秒模型会增加单次推理 MAC、激活内存和重新训练成本; +- 长音频的主要重复开销来自 FFT 和 mel,而不只是模型推理; +- 特征缓存可以直接消除相邻窗口之间的频谱重复计算。 + +--- + +## 4. 核心创新 + +### 4.1 残差 DS-CNN 与 BN 折叠 + +我们不是简单增加通道数或卷积层,而是在不明显增加推理复杂度的情况下改善网络信息流: + +- 4 个 DS-CNN 块全部增加 identity 残差; +- 每个 Depthwise 和 Pointwise 卷积后加入 BN; +- BN 在 TFLite 转换时折叠进卷积参数; +- 保持 64 通道和原始空间尺寸,残差无需投影层。 + +这种设计将提升重点放在训练稳定性和特征复用,而不是通过扩大模型换取精度。 + +### 4.2 从“重复 MFCC”改为“整段 mel cache” + +传统滑窗实现会对每个 1 秒窗口重复执行: + +```text +窗口归一化 → Hann window → FFT → magnitude → mel → log → DCT → inference +``` + +我们的实现改为: + +```text +整段音频 + ↓ +一次性计算所有 STFT 帧和 mel energy + ↓ +缓存每一帧的 40 维 mel energy + ↓ +按 frameStart 切出 49 帧窗口 + ↓ +按该 1 秒窗口的 maxAbs 恢复训练时的窗口归一化语义 + ↓ +log + DCT + ↓ +INT8 inference +``` + +这里没有直接缓存最终 MFCC,而是缓存**归一化之前的 mel energy**。原因是训练时每个 1 秒窗口都独立按最大绝对值归一化。如果直接缓存整段归一化后的 MFCC,不同窗口的幅度语义会与训练不一致。 + +通过“原始 mel energy cache + 每窗口线性缩放”,既复用了 FFT/mel,又保留了训练时的逐窗口归一化。 + +### 4.3 预计算 DCT 基矩阵 + +MFCC 的 DCT 原本每个窗口需要执行: + +```text +49 frames x 10 coefficients x 40 mel bins = 19,600 次 cos() +``` + +我们将 `10 x 40` DCT 基矩阵首次初始化后缓存: + +- 只需计算 400 次 `cos()`; +- 后续窗口仅执行矩阵乘加; +- 5 秒、21 个窗口时,三角函数计算量从 411,600 次降到 400 次; +- `cos()` 调用减少约 **99.90%**。 + +### 4.4 多窗口 logits 融合 + +模型输出改为 logits,并在识别器中增加四层判定: + +1. **EMA logits 平滑**:默认 `alpha = 0.60`; +2. **Top-1 confidence**:默认阈值 `0.75`; +3. **Top-1/Top-2 margin**:默认阈值 `0.15`; +4. **连续命中**:多窗口时默认至少连续命中 2 次。 + +与单次 argmax 相比,该策略能够: + +- 抑制瞬时噪声造成的高置信误判; +- 避免 Top-1 和 Top-2 非常接近时过早输出; +- 利用相邻窗口的一致性; +- 保留窗口步长、阈值和平滑系数的板端可调能力。 + +### 4.5 从 `.tflite` 自动导出无运行时 C++ 模型 + +为了避免在 IMX6ULL 上集成完整 TensorFlow Lite/LiteRT,我们实现了 `export_cpp.py`: + +- 解析 INT8 TFLite 图; +- 提取 Stem、4 个 Depthwise、4 个 Pointwise、4 个 Add、Global Mean 和 Dense; +- 导出 INT8 weights、INT32 bias; +- 导出 per-channel multiplier 和 shift; +- 将 BN 融合后的参数直接写入 `ResBnKwsModelData.cpp`; +- 生成手写 INT8 Conv、Depthwise、Add、GAP 和 Dense 推理路径。 + +最终板端只需要: + +- `ResBnKwsRecognizer.h/.cpp` +- `ResBnKwsModelData.h/.cpp` + +不需要在运行时加载 `.tflite`,也不依赖 TFLite Interpreter。 + +### 4.6 逐层复刻 TFLite INT8 舍入语义 + +量化部署中最容易被忽略的问题不是权重,而是整数乘法、舍入、饱和和零点处理。 + +我们针对以下细节进行了逐层校正: + +- Saturating Rounding Doubling High Mul; +- Rounding Divide by Power-of-Two; +- per-channel Conv multiplier/shift; +- Add 算子的输入缩放与 left shift; +- Global Mean 的 TFLite reference multiplier 调整方式; +- INT8 activation clamp; +- 输出零点和 scale 的还原。 + +Global Mean 最初只有少量 1 LSB 偏差,但仍继续对照 TensorFlow Lite 2.21 内核源码,最终做到全部中间张量完全一致。 + +### 4.7 训练与 C++ 特征前端严格一致 + +部署验证中还发现并修正了两个容易造成精度损失的前端差异: + +1. TensorFlow mel 滤波器是在 **mel 域**做三角插值,而不是在 Hz 域; +2. `tf.signal.mfccs_from_log_mel_spectrograms()` 使用 HTK 缩放,`C0` 同样采用 `sqrt(2/N)`,不是正交 DCT 的 `sqrt(1/N)`。 + +修正后,C++ 特征经过 INT8 量化,与 TensorFlow 训练侧输入完全一致。 + +--- + +## 5. 模型结果 + +测试集包含 4,890 条样本。 + +| 模型 | 测试准确率 | TFLite 大小 | 结论 | +|---|---:|---:|---| +| Baseline DS-CNN Dynamic | 88.92% | 44,856 B | 初始基线 | +| ResBN Dynamic | 91.10% | 46,392 B | 残差和 BN 带来明显提升 | +| ResBN + SE Dynamic | 91.19% | 71,040 B | 仅提升 0.08pp,体积增加 53.13% | +| **ResBN INT8** | **91.68%** | **48,368 B** | **最终部署模型** | + +最终 ResBN INT8 相比 Baseline: + +- 准确率提升 **2.76 个百分点**; +- 错误率从 11.08% 降至 8.32%; +- 相对错误率下降约 **24.91%**; +- 相比 ResBN Dynamic 还提升 **0.57 个百分点**; +- 模型保持约 48 KB,适合嵌入式固化。 + +INT8 文件不一定比 Dynamic TFLite 更小,因为文件中还包含量化参数和算子元数据。选择 INT8 的主要价值是: + +- 权重和激活均为 INT8; +- 卷积累加使用 INT32; +- 适合 Cortex-A7 的整数执行; +- 更容易实现无 TFLite 运行时的静态 C++ 推理。 + +### 关键类别提升 + +| 类别 | Baseline | ResBN INT8 | 提升 | +|---|---:|---:|---:| +| right | 78.54% | 87.88% | +9.34pp | +| no | 84.69% | 93.58% | +8.89pp | +| go | 76.37% | 84.08% | +7.71pp | +| down | 84.98% | 88.67% | +3.69pp | +| left | 91.02% | 93.69% | +2.67pp | +| yes | 92.36% | 94.75% | +2.39pp | + +`on` 和 `_unknown_` 相比 Baseline 有小幅回退,说明后续仍需使用真实麦克风噪声和板端场景数据校准类别阈值。 + +--- + +## 6. 长音频优化效果 + +以 5 秒音频、1 秒窗口、200 ms 步长为例: + +- 推理窗口数:21; +- 每个窗口包含 49 个 STFT/MFCC 帧; +- 传统逐窗口方式需要计算 `21 x 49 = 1,029` 个 FFT/mel 帧; +- 整段 5 秒缓存只需要计算 249 个连续 STFT/mel 帧; +- FFT/mel 帧计算减少约 **75.80%**; +- 相当于频谱前端计算次数降低约 **4.13 倍**。 + +该优化不减少 21 次模型推理,但消除了相邻窗口 80% 重叠区域的 FFT 和 mel 重复计算。 + +如果后续板端实测表明模型推理仍是主要瓶颈,可以通过接口将窗口步长从 10 帧调整为 25 帧或 50 帧,在响应速度和计算量之间继续权衡,而不需要重新训练模型。 + +--- + +## 7. 部署一致性验证 + +为了保证桌面测试结果能够迁移到 IMX6ULL,我们没有只验证最终分类结果,而是验证了完整链路。 + +### 7.1 INT8 算子逐层验证 + +在 `down`、`go`、`right`、`stop`、`yes` 五类样本上,对以下 15 个算子输出逐层比较: + +- Stem Conv; +- 4 个 Depthwise Conv; +- 4 个 Pointwise Conv; +- 4 个 Residual Add; +- Global Mean; +- Dense。 + +结果: + +> 所有层的 INT8 输出与 TensorFlow Lite reference kernel 完全一致,mismatch 为 0。 + +### 7.2 C++ MFCC 验证 + +使用真实 C++ FFT、mel cache、窗口归一化和 DCT,与 TensorFlow 训练前端比较: + +- 单窗口 490 个 INT8 MFCC 输入:`0/490` mismatch; +- 5 秒音频选取 7 个不同 frameStart:每个窗口均为 `0/490` mismatch; +- 浮点 MFCC 最大误差不超过约 `2.83e-4`; +- C++ logits 与 TFLite reference 输出最大误差约 `9.09e-7`。 + +这说明缓存切窗没有改变模型实际接收到的 INT8 特征。 + +### 7.3 编译验证 + +最终 C++ 模型数据和识别器通过 MSVC `/W4` 语法编译检查。 + +--- + +## 8. 最终交付 + +### 训练与测试 + +- `ResBNKWS/model.py` +- `ResBNKWS/train.py` +- `ResBNKWS/test.py` +- `ResBNKWS/artifacts/resbn_kws_1s_int8.tflite` + +### C++ 导出与验证 + +- `ResBNKWS/export_cpp.py` +- `ResBNKWS/validate_cpp_export.py` +- `ResBNKWS/DEPLOYMENT.md` + +### IMX6ULL 项目需要迁移 + +- `ResBnKwsRecognizer.h` +- `ResBnKwsRecognizer.cpp` +- `ResBnKwsModelData.h` +- `ResBnKwsModelData.cpp` + +运行时不需要迁移 `.tflite`,因为模型参数已经转换为 C++ 常量数组。 + +--- + +## 9. 当前限制与后续工作 + +目前已经完成模型、量化、特征缓存、融合和 C++ 数值一致性验证,但仍有以下板端工作: + +1. 在 IMX6ULL 上测量单窗口推理时间、5 秒总处理时间和峰值内存; +2. 使用实际麦克风录音重新标定 confidence、margin 和连续命中阈值; +3. 根据实时性要求评估 200 ms、500 ms 和 1 s 三种窗口步长; +4. 如 FFT 成为瓶颈,可替换为 NEON 优化 FFT 或固定点 FFT; +5. 当前 `off` 类需要目标项目的 `KeywordCommand` 增加 `Off`,并定义 `RESBN_KWS_HAS_OFF_COMMAND`; +6. 如果需要连续输出多个命令,而不是从整段音频返回一个最佳命令,需要在上层增加事件时间戳、冷却时间和去重状态机。 + +--- + +## 10. 结论 + +本项目的主要创新不是单纯扩大网络,而是联合优化了模型结构、量化实现、特征前端和长音频决策逻辑: + +- 使用残差和 BN 提升 DS-CNN 精度,同时保持轻量结构; +- 通过实验否决收益不足的 SE 部署方案; +- 保留 1 秒模型,利用 mel cache 消除长音频重复 FFT; +- 使用 logits EMA、margin 和连续命中提高输出稳定性; +- 将 INT8 TFLite 自动转换为无运行时依赖的 C++ 推理代码; +- 对齐 TFLite 整数舍入和 TensorFlow MFCC 细节; +- 最终达到 **91.68%** 测试准确率、约 **48 KB** 模型大小,并完成逐层零误差验证。 + +最终方案在精度、体积、可解释性、长音频计算量和 IMX6ULL 部署可控性之间取得了更好的综合平衡。 diff --git a/src/Apps/Game/src/recognition/ResBnKwsCnModelData.cpp b/src/Apps/Game/src/recognition/ResBnKwsCnModelData.cpp new file mode 100644 index 0000000..43f1e2e --- /dev/null +++ b/src/Apps/Game/src/recognition/ResBnKwsCnModelData.cpp @@ -0,0 +1,1812 @@ +#include "ResBnKwsCnModelData.h" + +namespace Game +{ +namespace ResBnKwsCnModelData +{ +const int8_t StemWeights[2560] = +{ + 23, 6, 3, -22, 13, -23, -9, 21, 24, -22, -10, -25, 6, -24, -6, 6, + -15, -6, -2, -18, -28, -18, 4, -17, -5, -127, 25, -18, -12, -85, 25, 43, + -10, -18, -19, 3, 21, 98, 17, 15, 53, 127, 64, 48, 31, 122, 67, -45, + 71, 60, 53, -58, 67, 42, 16, -66, 43, -31, -57, -42, -40, 6, -87, -34, + -37, -40, -70, -67, 19, -50, -29, -63, -12, -43, -65, 47, 16, 0, -81, 36, + 12, 6, 86, 44, 19, -63, 34, 70, -3, -24, -7, -57, 64, 42, -58, -127, + 77, 32, -24, -11, 25, 14, -36, 16, -50, 27, -6, -13, -17, 22, 18, 6, + -12, -6, -10, -6, -25, 9, -31, 19, -22, -28, -87, 22, -10, -5, -83, -6, + 1, -3, -19, -6, 40, 70, -26, -12, 107, 102, -27, 0, 84, 46, 12, 2, + 66, 76, 127, 69, 36, 6, 48, 22, -67, -35, 70, 19, -64, 0, 58, -40, + -52, 74, -8, -88, -18, 21, 76, -67, 42, 84, 99, -22, 61, 6, 88, -26, + 79, 42, 64, 18, 7, -3, 10, 18, -26, -75, 43, 82, -36, -41, -37, 58, + -12, -25, -109, 26, 11, -12, -127, 0, 59, 11, -85, 63, 25, -21, -88, 27, + -44, -24, -51, 67, -48, -17, -60, -8, -57, 14, 44, -6, -49, 40, 80, 16, + -28, 78, 100, -44, 8, 40, 127, -4, 44, 62, 104, -6, 41, -4, 63, -10, + 62, 5, -53, 46, 41, -23, -12, -37, 36, -48, -10, -91, -12, -49, -76, -69, + -83, 99, -39, -17, -39, 76, 71, -86, 80, 60, 29, -67, 127, -1, -3, -32, + 41, -3, 87, 23, -59, -9, 10, 48, 17, 78, -34, 4, -3, 127, -10, -42, + -34, 49, -10, -19, -61, 35, 10, 29, -38, 57, 50, -19, -25, -12, 44, -20, + -27, -65, 13, -22, -8, -24, 44, 12, 10, -28, -18, -51, 43, -39, 22, -63, + -1, 82, -42, -127, -21, 32, -56, -82, -59, -11, -43, 9, -90, -5, 4, 50, + -44, 15, 27, 91, 1, 5, 64, 69, 14, 16, 49, 9, 32, -25, 86, 19, + 52, 18, 32, -52, 118, -20, 0, -95, -11, -16, -8, -61, 4, 1, -32, -118, + -62, -41, -38, -126, -28, -65, -46, -21, -26, -44, 17, 55, -10, 5, 63, 127, + 8, 2, 30, 88, 13, 7, 31, 74, 33, 40, 14, 43, 1, 49, 23, 42, + -20, 49, 66, 85, 39, 114, 29, -23, -40, 48, -21, 42, -93, -6, -123, 57, + 15, 19, -77, -126, 69, -4, -36, -58, 77, 45, 8, -127, 126, -8, -39, -100, + 79, -6, 8, -56, -17, 0, -15, -19, 0, -7, 44, -15, 23, -20, -1, 35, + 23, -16, -3, 75, -13, 33, 15, 127, -21, 42, 12, 88, 17, 32, -31, -13, + 5, -1, -50, -73, 0, -12, -26, -56, -2, -13, 12, -30, -8, 12, 3, -16, + 64, 93, 1, -113, 114, 57, -41, 24, 42, 115, -125, 35, 20, 67, -68, 125, + -10, 12, -44, 86, -20, -76, 36, 127, -117, -86, 57, 34, -1, -52, 70, 40, + 29, 19, 93, -55, -11, 6, 107, -11, 93, -54, -127, -68, 95, -57, -107, -87, + 9, -33, -39, -24, 14, -17, -32, 11, 13, -49, -23, 58, 36, -16, 47, 99, + -38, -10, -39, 18, -31, 19, -18, 12, -12, -2, 3, -29, 1, -12, -20, -74, + -26, 3, 109, -43, -78, -9, 127, -62, -61, 15, 39, -16, -60, 10, 15, 2, + 3, 10, -1, 34, 7, 10, 20, -22, -15, 6, 15, -28, 21, -2, 28, -9, + 14, -15, 23, -29, -6, -18, 13, -28, 21, -38, 13, 31, 28, -13, -40, 29, + -4, -56, -31, 63, 15, -29, -61, 46, -12, 17, -127, 35, 41, -8, -101, -15, + 6, 14, -40, 4, 69, -3, -48, -11, 46, 41, 49, 42, -21, 3, 34, 36, + -79, 8, -43, 52, -13, 28, -114, 22, -9, 20, -99, 74, -28, 40, -55, 23, + -23, -13, -29, -51, -9, -1, 40, -23, -61, -61, 50, -52, -15, -36, 50, -127, + -3, -54, 50, -99, -32, -23, 61, -94, -55, 47, 13, 8, -2, -23, -4, 12, + -27, 45, -13, 10, -15, -34, -52, -2, -19, 6, -49, -22, -21, -12, 9, 119, + 42, -127, 75, 95, -3, -81, 15, 1, -7, 53, -16, -69, 14, 75, 5, -67, + 60, -51, -8, -6, 7, 13, -54, 58, 57, 51, -55, 100, -30, 74, -24, -5, + 11, 36, -38, 70, -94, -33, 47, 75, -4, -127, 18, 30, -33, -63, 11, 44, + -52, -26, 43, 66, 6, -10, 21, 57, -77, -8, 127, -114, -35, 70, 87, -67, + -41, 75, -11, -35, -14, 29, -4, -9, 32, -10, 36, 18, 47, -10, 9, 13, + 30, 13, 10, 1, 24, 32, 52, 2, 16, 56, 54, 35, 26, -10, 4, 29, + -5, -110, 94, 17, -23, -84, 86, -53, -23, -21, 14, -78, -47, 70, -3, -88, + -4, 127, -38, -51, -33, 49, -99, -1, -15, 32, -87, 72, 43, -4, 5, 53, + 7, -36, 76, 63, -59, 5, 5, 79, 4, -29, -89, -22, -17, -61, -2, -15, + 18, -31, 61, 4, 38, -21, 33, 30, 30, -33, 90, 47, 1, 4, 38, 74, + 14, 34, -11, 43, -17, 52, 81, 12, -4, 127, 89, 7, -49, 72, 34, -15, + -6, 25, 48, -72, -1, 33, -31, -44, 80, 12, -60, -117, 49, -16, -90, -16, + -28, -46, -37, 56, -43, -77, -68, 127, 35, -50, -10, 106, 39, -4, 66, 121, + 118, -13, 30, 7, 84, 3, 93, -10, -40, 66, 86, -21, -47, 26, 9, 27, + -26, 66, -41, -68, -124, 86, 55, 97, -69, 57, 110, 41, 127, 46, 49, 42, + 7, -104, -16, -47, -19, -76, -15, -67, -20, -26, -116, -116, 37, -24, -90, -72, + -1, -20, -45, -23, -24, -42, 59, 51, -84, 20, 91, 95, -111, 38, 111, 25, + -87, 65, 30, 2, -6, -27, 51, -38, 21, 33, -66, -94, 47, 16, 23, -93, + 46, 70, -62, -105, 97, 110, -72, -127, -15, 63, -22, 7, -16, 71, -50, -41, + 0, 72, -75, -82, 5, 52, -59, -127, 32, 7, -63, -98, 47, -62, -36, -28, + 31, -53, -44, -15, -19, -74, 66, -10, -54, 16, 95, 43, 0, 26, 79, 64, + -54, 21, 40, -19, -20, 12, 59, -97, 15, 26, 70, -53, 20, 6, 55, -12, + -16, -42, -25, 54, -7, -77, -127, 73, 15, -37, -82, 100, 8, -17, -42, 40, + 29, 23, 3, 3, 44, 48, 38, 9, 70, 73, -67, 53, 31, 78, 49, 23, + -22, 39, -24, 56, -5, -68, 90, 53, -50, -51, 8, 72, 30, 1, 31, -12, + -6, -127, 36, -11, -26, -79, -1, 28, 24, -6, -64, -32, -28, 29, -100, -19, + 44, -32, 58, -17, 33, -23, -17, -63, -23, -24, -113, 14, -109, 39, 12, 122, + -105, 65, 127, 101, -50, 107, 102, 22, 3, 56, 59, -42, 42, -27, -45, -73, + -3, 13, -101, 12, 21, 3, -40, -29, -13, -80, 13, -4, 43, -22, -73, -42, + 19, 31, -43, 49, -20, 45, -103, 58, -44, -34, -27, 52, -34, -54, 44, -9, + -48, -13, 107, -17, -55, 0, 91, -5, 24, 127, 29, -71, 29, 116, -29, -46, + -26, -6, -49, 49, 15, 53, 21, 86, -30, 69, 50, 78, -127, 25, 73, 37, + -78, -15, 93, 20, -54, -5, -6, -52, 20, 18, -24, -86, 28, 68, -42, -107, + 97, 8, -21, -105, 88, -30, -8, -20, 31, -37, 30, -35, -20, -21, 33, 12, + -84, -7, 26, 36, -29, 77, -2, 12, 1, 100, -44, 21, 9, 38, -10, 18, + 48, -36, -20, -61, -14, -14, 8, -127, 7, 49, 5, -64, 8, 78, -1, -58, + -38, 111, 47, 11, -1, 84, -39, -6, 30, 46, -104, -60, 127, 7, 1, -20, + 61, -34, 52, -47, -26, -60, 90, 12, -97, -56, 62, -53, -97, 13, -4, -15, + -47, 7, -15, 14, -9, -30, 22, 9, 1, -21, 65, 73, -16, 24, -98, 69, + -9, -19, -105, 13, -8, -21, -98, -58, 14, -107, -127, -35, -8, -47, -94, -59, + 42, -32, -50, -3, 56, -22, 106, 61, -12, -63, 16, 29, 10, -21, -18, 85, + 15, 25, 45, -47, -4, 30, 53, -19, -29, -32, 105, 8, -65, -59, 121, 25, + -62, -127, 30, 49, -22, -39, -35, 55, 15, -35, -89, -17, 39, -12, -77, 20, + 47, 62, -73, 7, 53, 66, -57, -28, -54, 7, -57, 60, -47, -31, -112, 75, + 18, -27, -28, 70, -6, -18, 4, 39, 36, -7, -20, 20, 51, -27, -23, 16, + 33, 0, 8, 66, 83, -4, 48, 0, 127, -16, 54, 18, 69, 15, 106, -29, + 62, 11, 78, 19, 84, -10, 28, 34, 43, -6, -28, 95, -46, 20, -84, 127, + -71, 46, -78, 39, -70, 15, -27, -43, -38, -49, 68, -104, -23, -27, 43, -49, + 43, -7, 10, -60, 45, -6, 3, -58, -30, 15, 10, -127, -54, 61, 25, -91, + -55, 96, 79, -99, -30, 59, 13, -21, 9, 58, -21, -2, 63, -53, -56, 30, + 55, -45, -80, 64, 12, -35, -21, 39, 19, -1, -43, 64, 24, 21, -16, 55, + 0, -1, 91, -77, -36, 4, 37, -96, -90, 50, -4, -95, -127, 58, 58, -7, + -40, -12, -12, 65, 92, -55, -15, 106, 103, 5, -94, 28, 70, -6, -41, 28, + 43, -3, -44, 10, -9, -4, 42, -23, -38, -74, -70, 68, -52, -127, -7, 82, + -38, -85, -14, 80, 10, -45, -22, 51, 15, 67, -31, 62, 23, 0, 10, 23, + 30, 26, 63, 2, 74, 29, 61, 17, 39, 25, 104, -93, 32, 58, 92, -82, + -57, -79, -72, -35, 94, -3, -76, -58, 50, 102, -1, -127, 20, 119, 99, -119, + -32, 104, 85, -42, -38, 88, -29, -59, 12, -28, 25, -34, 72, -69, 50, 15, + 46, -26, 8, 35, -10, -2, -79, 29, -57, 63, -80, -62, -17, 50, -9, 55, + -33, 23, -24, 52, 18, -52, -20, 127, -10, -34, -68, 49, -28, -41, -28, 49, + -48, -3, -2, -34, 30, -58, 52, 18, -45, -36, 14, -11, -54, -14, 22, -37, + -32, -86, 64, 127, -46, -62, 82, 71, -14, -1, 5, 40, -9, -11, -46, -1, + 31, 21, -52, 1, 21, 33, -31, -56, 16, 41, -45, -53, -8, 29, -5, -121, + -1, 38, -25, -77, 35, 36, 13, -88, -68, -68, 40, -5, 11, -64, -70, -32, + 28, -48, -94, 21, 80, -40, -64, 34, 69, -13, -112, -59, 85, 25, -108, -10, + 84, 60, -53, -71, 47, 42, -26, -48, 32, 49, -19, -68, 52, 42, 12, -127, + 24, 36, -44, -17, 26, -16, -83, 7, 43, 48, -94, -30, 38, -20, -127, 44, + -26, -54, -125, 60, -16, -38, -87, 84, -3, 15, 43, 53, -14, 46, 69, -41, + -14, 19, 96, -18, -22, -12, 97, -29, -78, 27, 62, -33, 102, 76, -7, 17, + 61, 2, 19, -50, -7, -1, 43, -57, -63, -10, 54, -88, -31, 6, 27, -50, + -78, 64, -127, 0, 30, 51, -117, 51, 105, -26, -108, 74, 110, -40, 21, 81, + -44, -37, 20, 41, -16, -20, -1, 32, -29, -4, 61, 55, -17, -20, 17, 104, + 38, -10, -14, 127, -17, -26, -14, 124, -28, -23, -35, 100, -81, 13, -11, 33, + -69, -23, -35, 8, -36, -80, 17, 59, -72, 26, 127, 6, -28, 22, 86, 60, + -33, -31, 21, 107, 3, -4, -73, 49, -1, -27, -93, 27, 50, -28, -94, -52, + 44, -45, -79, -46, 26, 5, -56, -74, 3, -19, -31, -26, 37, -24, 24, 7, + -48, 13, -49, -27, -28, 16, 22, 37, -24, -27, 50, 109, -34, -60, -73, -2, + -14, 12, -127, 32, 4, 84, -34, 33, -8, 113, 81, 29, 28, 22, 91, -38, + 33, -24, 62, -69, 34, -9, 13, -53, -5, 65, 7, 23, -54, -10, 40, 43, + -36, -11, 8, 52, -62, -33, 7, 74, -42, -23, -3, 67, -4, -51, -28, 20, + 30, -28, -8, -64, 63, 14, 42, -90, 21, -1, 68, -127, 60, 59, 13, -26, + 15, -29, 0, -20, 74, -34, 2, -21, -18, 28, -34, 110, -13, -22, -40, 127, + -46, 1, -21, 75, -74, 26, -32, 70, -34, 65, -6, 19, 23, 21, -5, 8, + -39, 3, 18, 14, -42, -6, 23, -12, 21, 11, -68, 97, 102, 18, -103, 53, + 87, -79, -127, 78, -15, -62, -44, -113, -31, -26, 34, -127, -101, 8, -25, -83, + -45, -9, 21, -26, -17, 6, 95, 71, -35, 70, 77, -4, -15, 63, 68, 14, + -93, 71, -119, -127, 33, 80, -20, -118, 30, 28, 47, 24, -31, -41, 103, 31, + -45, -3, 55, 118, -9, 2, -12, 122, 52, 59, -10, 45, -3, 10, 46, -53, + -11, -89, -64, 34, -2, -37, -15, 24, -77, -51, -15, 104, -6, -8, -80, 13, + 30, 10, -91, -81, 45, 23, -7, -110, 18, 100, 25, -75, -25, 53, 38, -127, + -21, 17, 118, -107, -16, 24, 99, -52, -56, 38, 116, 30, -1, -4, 40, 116, + -12, -46, -114, 51, 10, 14, -58, 68, 26, -61, 11, 127, 18, -41, 27, 85, + 57, -5, 2, 76, 4, -47, 60, 44, 39, -49, -11, 46, -32, -60, -2, 11, + 0, -44, 14, 50, -33, -42, 20, 58, 95, 70, -25, 10, 127, 30, -2, 15, + 23, -58, 25, 1, 2, -116, 1, 52, -4, -98, -31, 68, -35, -68, -6, 45, + -57, -65, 33, 39, -38, -22, -14, 3, -68, -17, -6, -12, -35, -5, 50, 8, + 11, -10, 28, -8, 33, 58, -1, 37, -23, 99, 26, 100, -4, 115, 68, 100, + 15, 79, 61, 103, -17, 19, 24, -15, -12, -33, 13, -107, -4, -63, -57, -36, + -4, -60, -75, -127, -5, -38, -56, -104, 6, -3, -34, 42, 28, 9, -15, 26, + -43, 38, -31, 64, -81, -25, 6, 33, 10, 13, 16, -70, 124, 51, 17, -97, + 127, 70, -7, -98, 45, 1, -13, -61, -17, -7, -30, -14, -4, -33, -31, 24, + -29, -64, -48, 1, -95, -65, -47, -90, -127, -71, -82, 34, -121, 20, 28, 37, + -110, 4, 59, 109, -64, 50, 14, 57, 5, 36, -35, 16, 46, 29, -59, -12, + 91, 66, -22, -4, 88, 29, -38, 14, -41, 79, -100, -98, 14, 68, -70, -127, + 73, 16, -62, -17, 23, 60, -116, 2, 18, 65, -33, 38, -35, -50, 3, 7, + -16, -42, 42, 9, 6, -53, 50, 9, -18, -59, 22, 57, -55, -26, -6, 85, + 88, 82, 36, -3, 127, -23, 2, 25, 12, -18, -29, 60, 24, -93, 5, 28, + -48, -72, -55, -89, -16, -7, -39, -50, -30, 31, -72, -27, 11, 7, 14, 87, + 44, -4, 66, 32, -33, -10, 46, 30, 113, 58, 13, -60, 17, 83, -79, 41, + -77, 10, -33, -6, -21, -62, 30, 2, 60, -56, 90, 29, 29, -58, 94, 111, + 28, 43, 27, 127, -107, 98, -24, 4, -124, 82, -77, -125, -87, 94, -34, -35, + 24, -19, 47, 65, -35, -56, 44, 63, -40, -8, 6, 71, -63, -6, 44, 58, + -46, -10, 7, 60, 15, 9, -63, 18, 45, -85, -127, -1, 70, 25, -91, 17, + 26, -27, -84, 17, 36, -29, -21, -34, 50, -49, -95, 64, -45, 18, -69, 79, + -127, 26, -44, 83, -75, -20, 18, 99, -63, -59, -3, 36, -66, 37, -11, 53, + -9, 60, -12, 56, 30, -18, 2, 46, 26, 23, 32, 35, 40, 92, 38, -42 +}; + +const int32_t StemBias[64] = +{ + -1128, 2299, 301, 1924, -227, 1462, 71, 193, + 860, -321, 335, 722, 981, -1200, 86, -176, + -108, -715, -856, 841, 368, 548, -795, 827, + 1543, 0, -63, -1011, 1052, 1117, 1752, 2177, + 422, -1610, -509, -271, 230, 2625, 147, -696, + 1002, -800, 129, -406, -55, 1173, -273, -233, + 948, 82, 401, -365, 805, 961, -1086, -2095, + 1785, 838, 458, 1395, -34, 2058, -1007, 1391 +}; + +const int32_t StemMultipliers[64] = +{ + 1622362162, 1163558067, 1736597674, 1341094969, 1080057896, 1439004594, 1955130479, 1365870065, + 1252265288, 1310520143, 1206676741, 1370711973, 1263739653, 1460480382, 1223107969, 1347469733, + 1227195235, 1401935573, 1634949465, 1508056467, 1333399875, 1633079910, 1769959714, 1402419092, + 1301090201, 1473580831, 1569111705, 1590818587, 1656896131, 1658978082, 1856049321, 1173740151, + 1973581111, 1957036641, 1518372203, 1112160837, 1483652701, 1300997500, 2121479941, 1488218350, + 1302791999, 1938631964, 1877132052, 1301835231, 1754497236, 2044673061, 1224479916, 1317196186, + 2026702788, 1863698590, 1462011000, 1147749146, 1083055201, 1363222165, 1642487599, 1867363698, + 1115152020, 1153115262, 1654521066, 1527796484, 1382789289, 1104297130, 1638833157, 1793308744 +}; + +const int32_t StemShifts[64] = +{ + -8, -8, -7, -8, -7, -8, -8, -8, + -7, -7, -8, -6, -8, -8, -6, -7, + -7, -7, -8, -8, -7, -8, -8, -8, + -8, -8, -7, -8, -8, -8, -8, -8, + -8, -9, -8, -7, -6, -8, -7, -8, + -8, -8, -8, -8, -7, -8, -8, -7, + -8, -7, -7, -7, -7, -8, -9, -9, + -8, -7, -8, -8, -7, -8, -8, -8 +}; + +const int8_t Dw0Weights[576] = +{ + 93, 54, 127, 7, 84, 0, -6, 48, -42, -7, -15, 6, 105, -6, -17, -56, + 46, 127, -112, 43, 41, 58, 34, 18, 27, 79, -28, -69, 37, -53, -49, -72, + 18, 48, -110, -19, -127, -95, -55, -75, -108, 127, 43, 1, 14, 95, -48, -43, + -62, -99, 22, 20, 93, -18, 44, 35, 27, 127, 5, -92, 42, -83, -72, -81, + 33, 127, -6, -57, -21, -61, 127, 70, -39, -51, 66, 127, 35, 3, -63, -47, + -33, -24, 86, 116, 53, -20, -59, 65, -37, -46, -25, 91, -127, 105, -34, -64, + -5, -46, 127, 9, 20, 66, 4, -94, 127, -48, -23, -127, 2, -127, -62, 20, + -59, 86, -91, 12, -100, 90, -8, -101, -37, -27, 2, -47, 23, -126, -3, -45, + -76, -53, 53, -19, 20, 32, 5, 33, -55, -12, -30, -86, 19, 127, -100, -61, + 5, -56, -53, -127, 46, 6, 87, -125, 22, 11, 126, -11, 21, 76, 127, 120, + -91, 25, 0, 105, 48, 66, -77, 127, 38, 79, 127, -67, 127, -79, 28, -25, + -9, 74, -68, -19, 86, -78, 23, -27, 50, -117, 63, 56, -127, 114, -127, 88, + -43, 30, -95, 110, 127, -57, -65, 127, 45, 9, 9, 6, -37, -38, -17, 7, + 32, 35, -21, -64, -1, 127, -102, -60, -46, -35, -8, -77, 7, 29, 76, 17, + -127, -33, -84, -24, -86, 31, -8, 13, -6, -12, -17, 67, -74, 22, 10, -102, + 84, -38, 46, 37, -127, 39, 127, -118, -58, -57, -100, -75, 62, -74, 79, 67, + 127, -52, 31, -16, -14, -20, -52, 48, -86, 54, -59, -84, 16, -19, -127, 62, + -127, 61, 119, 78, -17, 25, -27, 90, 29, -50, -82, 63, -28, 127, -66, -127, + 25, -27, -5, -4, 94, -127, 21, -88, -28, -102, 19, -45, 28, 30, 127, -27, + -60, -6, -4, -30, 60, -17, -37, 75, 11, 2, 8, 64, 1, -30, 57, 10, + 66, -88, 47, -127, 55, 127, -45, -38, 37, 127, 26, 13, 21, -17, -41, -127, + 124, -8, 77, 16, 65, -83, -33, -127, 59, 127, 35, 17, 25, -125, 57, 50, + -27, -4, 61, -18, -20, 121, -69, 112, -94, 77, 54, -3, 72, -28, 57, 17, + 33, 85, -21, 10, 32, 104, 14, -127, -73, 75, 127, -33, -11, 127, -4, 72, + 55, 37, -43, 33, -20, -11, -33, -29, 37, 69, 30, -33, -127, 26, 23, -35, + 3, -84, 126, -68, 127, -24, 32, 18, -29, -42, -127, -63, -35, 15, -2, 119, + -50, -26, -24, -61, 61, 76, 52, -63, 88, 34, -75, 53, -20, -13, 35, 42, + -48, -10, 41, -11, 7, 115, 11, -124, 14, -66, -123, -74, -67, 87, 33, -127, + 16, -72, -57, 59, -2, -2, -21, -78, -59, -67, -18, -21, 113, -14, -64, -64, + -8, 124, -88, -68, -70, 42, 127, -36, 90, 27, -17, -127, 96, -82, 104, 77, + 21, 18, -101, 22, 31, -48, 60, 74, -104, -53, -69, -84, -61, 0, 116, -127, + 127, -127, 116, -39, -41, -127, 2, -2, 80, 59, 42, -21, -22, 103, -30, -70, + 67, 46, -119, -38, 99, 42, -14, -52, 127, 15, -127, 52, -48, -90, -38, 77, + 65, 45, -127, 13, -40, -34, 11, 14, -127, -9, -82, -108, 2, -113, -32, -100, + 125, -127, 28, -127, -9, -43, 127, 16, 38, -31, -68, -91, -56, -8, -11, 17, + -20, 0, 127, -127, -69, 115, -84, -83, -127, 61, 9, -127, 92, -99, -71, 25 +}; + +const int32_t Dw0Bias[64] = +{ + -1050, -82, 410, -96, -431, 190, 179, -245, + 326, -30, 236, 28, -240, 120, 1759, 545, + -43, -681, -52, 100, -70, -655, -48, 102, + -60, 0, 1076, 386, -18, -278, -925, -7, + 530, 253, 834, 268, 6, -393, -38, 868, + 69, -164, 287, 677, -83, 393, -548, 1163, + 99, 518, -449, 255, 261, -389, -182, 1212, + 285, -275, -23, 1270, -156, 13, -82, 303 +}; + +const int32_t Dw0Multipliers[64] = +{ + 1340986606, 1711380266, 2045454035, 1416984139, 1216285340, 2042080852, 1290115324, 2095211850, + 1411601029, 1537762406, 1574602099, 1957832355, 1400017111, 1731302315, 1207505951, 1715366379, + 1401975631, 1777968665, 2106415282, 1776927782, 1234828379, 1273180013, 1465064727, 1545243138, + 1186940854, 1408912721, 2141169485, 1517445609, 1151327657, 1466017805, 1757128979, 1967351855, + 1364356127, 1147604213, 1649583653, 1502233501, 1817470911, 1911734020, 1509036489, 1973447512, + 1117202773, 1308982548, 1972313184, 1755108411, 1775992052, 1511025874, 1910041574, 1821428394, + 1234109127, 1549994800, 1703787676, 1181112762, 2017124570, 1395101122, 1866950518, 1597557289, + 1441027573, 2129312148, 1807469496, 1208979258, 1801744003, 1833628008, 1168714230, 1227509206 +}; + +const int32_t Dw0Shifts[64] = +{ + -7, -6, -6, -6, -6, -6, -5, -6, + -6, -6, -5, -6, -6, -5, -7, -6, + -7, -7, -7, -6, -6, -6, -6, -6, + -5, -5, -7, -7, -5, -6, -6, -6, + -6, -5, -7, -6, -7, -6, -6, -7, + -5, -7, -6, -7, -6, -6, -7, -7, + -5, -7, -6, -5, -6, -6, -7, -7, + -5, -6, -7, -6, -6, -6, -5, -6 +}; + +const int8_t Dw1Weights[576] = +{ + 127, 50, -3, -54, 127, -87, -24, 2, -57, 114, -98, 19, -14, -65, -7, -92, + -109, 70, 65, -63, 3, 5, 74, 127, -81, 15, 76, 10, 127, 17, 127, 44, + -97, -86, 54, 72, -65, -127, 26, 86, 26, -86, 36, 127, 29, 102, -95, -127, + 127, -36, -76, -64, 73, 110, 64, 59, 41, 83, 127, 23, 48, 127, 71, -20, + -26, 46, -41, -7, 0, -28, -28, -20, 24, -45, -90, 83, 73, 2, -72, 82, + 45, -57, -80, 127, -127, 127, -52, -24, -91, -14, -47, 5, 43, -11, 18, 114, + -3, 55, 61, 127, -84, -20, 71, -118, -74, 37, 9, 83, 103, 35, -37, -36, + -14, 67, -1, 30, -32, 65, 43, 122, 82, -75, 27, -111, 42, 7, -63, 28, + 112, 86, 46, -14, 46, 17, -68, 124, -4, 61, -12, -54, -46, 127, -16, -50, + 84, 63, 58, -95, -22, -22, 53, -68, 48, -9, -62, -127, -53, 50, 93, -127, + 36, 16, -39, 74, 127, 97, 3, -61, -9, -72, 102, -4, -17, -85, -127, 57, + 127, 94, -82, -62, -25, -127, 88, 48, -7, 13, 5, 1, -114, 57, 49, 63, + 9, -118, -53, -60, 8, 7, 9, 13, -71, 69, -69, -3, 9, 32, -127, 45, + -127, 78, -127, -41, -94, -82, -2, -56, -17, 11, -18, -55, 54, -22, -82, -67, + -17, 70, -68, 62, -92, -38, 32, 15, -127, 22, 8, 62, 52, 15, 3, 3, + 55, 37, -75, -58, 97, 28, 127, 54, 55, -71, -14, -33, -25, 32, 77, -33, + 28, -127, -34, 8, 46, -54, -42, -12, 127, -48, -78, -127, 6, 71, 47, 39, + 25, -34, -12, -64, -31, 0, -127, 97, -2, -9, -35, 54, 43, -30, -67, -35, + -23, -45, 22, 4, -51, 54, 38, -30, -79, -63, -65, 27, 39, 56, -11, -23, + 3, -57, -6, -42, -100, 23, -94, 24, 48, 41, 83, -90, -36, 40, -92, -27, + 8, 43, 127, -63, 48, -71, -41, 121, 46, 35, 60, -95, 7, 44, 61, 12, + 56, -67, -81, -4, -36, -79, 96, -36, 127, 36, -127, 29, -42, 127, 44, -12, + 127, 50, -127, -43, -10, 91, -94, -58, 26, -39, 96, -9, 60, 127, -12, 53, + 111, 127, -127, 30, 33, -74, -122, -3, 45, -66, 36, -127, -67, -4, 40, -61, + -74, -46, -67, -127, 52, -127, -83, -127, 15, 127, 127, -74, 127, 88, 7, 48, + -86, -35, 36, -108, -96, 10, -28, -42, -77, 19, 32, -25, 26, 30, -14, -103, + -34, 39, -121, 76, -3, 39, -3, 21, -50, 72, 61, 82, -127, 94, -14, 44, + 8, -49, -24, -127, 78, 51, -56, 127, -17, -127, 17, 51, -127, 65, -16, -59, + 9, 50, 67, -112, 3, -81, -127, -112, 84, -20, -55, 49, -33, 34, -33, 95, + 22, 103, 86, 113, -83, -43, 26, -106, 20, 15, -51, -16, -50, 7, 111, -30, + -61, -87, -88, 15, 8, 66, -88, -8, 66, -47, -127, 38, 9, -109, -10, -95, + 95, -70, -8, 46, -127, 7, -97, 75, 31, 40, 59, 86, -18, -86, -75, -107, + -48, -98, -14, -69, 116, -65, 61, 111, -22, 110, 49, 113, 29, -12, 40, -127, + -108, 127, -43, -105, 95, 14, 62, 71, 99, 127, -60, 42, -20, 39, 14, -127, + 28, 127, 1, -94, -83, -10, -127, -127, 51, 127, 12, 99, -32, 117, -30, 12, + -40, -67, -64, 55, 83, -37, 47, 121, 127, -1, 64, -34, 18, -33, -127, -127 +}; + +const int32_t Dw1Bias[64] = +{ + -536, 429, -135, 1867, -972, 1704, 1139, -235, + -672, -954, 257, 662, -385, -1210, 793, -641, + 1660, -614, 167, 567, 1949, 114, -343, 10, + 142, -364, 634, 89, -445, -329, -1228, 1107, + 244, -249, 606, -1174, 1202, -935, 3, 793, + 389, 111, -591, -2413, -452, -1464, 686, 823, + -2042, 100, 1655, 696, -518, -153, -81, -1272, + -1475, 613, -1044, 825, 1264, -744, 353, 1548 +}; + +const int32_t Dw1Multipliers[64] = +{ + 1186680738, 1338923133, 1730243392, 1454873767, 2066410704, 2073393160, 1103103505, 2100107774, + 1321511162, 1964590519, 1906210217, 1825885397, 1434461107, 1321334867, 1671309779, 1153704289, + 1581876455, 1807127102, 1667553665, 1393036787, 1202132453, 1231110809, 1986796000, 1381147426, + 1884391118, 1959401390, 1803436419, 1822467825, 2047898817, 2018135422, 1095284501, 1759718109, + 1219579860, 1899936359, 1125642086, 1245884680, 1893778428, 1885157806, 1476155122, 2113776160, + 1485377654, 1180434850, 1584564926, 1721544304, 1657262351, 1809690280, 1187514755, 1484686014, + 1500150891, 2102306465, 2068558772, 1093502691, 1507541117, 1941340176, 1652774606, 1157114900, + 1519666762, 1225989641, 1646042481, 1781232963, 1671875366, 2041061017, 1920060214, 2084595311 +}; + +const int32_t Dw1Shifts[64] = +{ + -5, -5, -5, -6, -6, -6, -5, -6, + -5, -6, -6, -6, -5, -6, -6, -6, + -7, -5, -5, -6, -6, -5, -6, -5, + -6, -5, -6, -5, -6, -5, -5, -6, + -5, -6, -5, -6, -6, -6, -6, -7, + -5, -5, -6, -7, -6, -6, -4, -6, + -6, -6, -6, -5, -6, -6, -5, -5, + -6, -5, -6, -6, -6, -6, -6, -6 +}; + +const int8_t Dw2Weights[576] = +{ + 114, -127, 57, 53, 87, -53, 127, 97, 58, 39, 58, 59, 53, -81, -127, -127, + 72, 80, -78, -25, -28, -41, -105, 85, -37, 127, -71, 59, 127, 127, -67, -91, + 127, 68, 96, 35, -116, -127, -110, 85, 104, -127, 109, 3, 127, 7, 58, 55, + 127, 25, 85, 54, -15, 127, 89, -100, 75, 127, 9, 127, 45, 84, 65, 51, + 127, -22, 32, -127, -89, 3, 66, 111, 28, 11, -91, -14, 53, 0, -45, -42, + -2, 95, 5, -127, 88, -50, -127, -31, -47, 92, 35, -127, 39, 68, -15, 69, + 13, -1, 32, 43, 40, -47, -127, -8, 26, 78, 14, -127, 30, 30, 58, 127, + 67, 12, 127, 54, 22, -81, 24, 16, -22, 82, -50, 107, -85, -35, -59, -90, + 125, -42, -68, 79, -8, 49, 17, 127, 87, -80, 56, -24, 91, -80, -55, -41, + 97, 75, 10, 14, -127, 19, -17, 36, -59, 40, -94, -3, 2, 78, 3, -118, + 4, 22, 19, -81, -18, 88, -15, 110, -14, 87, -76, -81, 40, 41, -37, 68, + 50, 48, 72, 36, -47, 16, 127, -60, -53, 83, -127, 38, -127, -33, 60, -17, + 44, -61, 71, 26, -14, 16, 12, 47, 71, 36, 58, 32, 45, -127, -42, -76, + -79, -2, -70, 28, 42, -108, -13, 13, -21, 38, -13, -52, 86, 36, -17, -127, + 36, -71, 34, 17, -103, -53, -104, -63, 127, 73, 127, 15, 74, -111, -5, 57, + 59, 24, 98, 55, 22, 88, 44, -104, 0, 47, -46, 21, 14, 6, -84, -86, + -61, -6, 127, -21, -127, 54, -4, 45, 52, -13, -127, -69, -44, 21, -28, 14, + -86, -24, -107, -103, -5, -60, 18, 12, -52, 50, 43, 30, 51, 31, -29, 43, + 62, -79, 127, 26, 81, -126, -50, -86, 57, 73, -9, -39, -14, -107, 21, 21, + 84, 21, 63, 44, 4, -28, 19, -49, -38, -31, -49, -23, -86, -95, -15, -27, + 83, -16, 27, 73, 61, 58, 71, 61, 40, -127, 52, -127, -7, -63, -59, -28, + -4, 15, 55, -6, -77, 20, 76, 38, -80, -1, -7, -97, 124, 68, 33, 36, + 54, -9, 35, -13, -56, 5, 72, -32, -18, 8, -126, -64, -23, -50, -44, 102, + 16, 37, 73, 103, 60, 8, 59, -91, -127, 29, -82, 45, -118, -14, 13, -18, + 28, -61, 112, 37, 0, 99, 102, 38, 35, -9, 22, 13, 47, -32, -5, 48, + -127, 127, -60, 49, 63, -95, -82, 12, -7, 21, -96, -42, 3, 55, -11, -42, + 52, -127, 39, 20, -63, -26, 42, -127, 112, 59, 92, -9, 117, -103, -127, 48, + 109, 64, 95, 71, 127, 76, 57, -109, 10, 55, -46, 98, 85, 62, -89, -91, + 36, -52, 75, -87, -57, 127, 71, 51, 44, -2, -81, -50, 127, -5, -31, -22, + 11, 68, -127, -57, -58, -127, 7, 127, -104, 0, -63, 48, 55, 108, -127, 127, + 116, -49, 96, -127, 127, -66, 0, -118, 56, 21, 54, -71, 71, -127, 15, -43, + 56, 20, 127, 127, 114, 67, 38, -127, -37, 32, -47, 106, -51, -63, -26, -106, + 117, -82, 77, -19, 90, 33, 83, -19, 127, -94, 52, -44, 111, -58, -68, -91, + 12, -1, -83, 27, -119, 29, 23, 69, -127, 41, -127, -54, 59, 120, 86, 32, + 76, 10, 127, -1, -78, -57, 108, 8, 102, -48, 3, -67, 3, -87, -67, 111, + 13, 127, 70, 79, 123, 90, 68, -36, -49, 73, 24, 30, -106, 127, -127, -127 +}; + +const int32_t Dw2Bias[64] = +{ + -3753, 2533, -2123, -507, 433, -2322, -2935, -2604, + -2617, 602, -52, 1422, -2567, 1908, 3165, 2772, + 1168, -1799, 1335, 1375, -151, 2982, 1672, -2161, + 3160, -1812, 1940, 1361, -1741, -2473, 1027, -165, + -3294, 1461, -2159, 192, 504, 2895, 963, 753, + -1783, -1549, -2347, 1877, -1639, 3433, 323, -3276, + -3269, -1973, -4792, -2669, -2507, -1051, -1416, 2065, + 1016, -2957, 1305, -2890, 2660, -176, 1093, 2895 +}; + +const int32_t Dw2Multipliers[64] = +{ + 2002898548, 1208038225, 1385530954, 1457347334, 1226147771, 1595756522, 1450349994, 1639522820, + 1809258698, 1747214946, 1657615383, 2077512807, 1292059254, 1306579207, 1934745061, 1219280287, + 2044332560, 2091552507, 2109099209, 1174743423, 1281356243, 1574599478, 1435743444, 1292372720, + 1523297669, 1544207657, 1206043357, 1920169754, 1854823117, 1541237866, 1835735443, 1238140452, + 1197905989, 1442738700, 1664008863, 1765239736, 1124332023, 1791975233, 2031600875, 1858444033, + 1984938966, 1547534574, 1523607370, 1349877910, 1755842979, 1419693333, 1510093503, 2058119278, + 1172301468, 1847131235, 1112490026, 1303841650, 1327202245, 1091247427, 1211345915, 1716035976, + 1646138201, 1158597903, 1809660043, 1276861101, 1538448639, 1584665464, 1165774527, 1356202657 +}; + +const int32_t Dw2Shifts[64] = +{ + -7, -6, -6, -6, -6, -6, -6, -6, + -6, -6, -6, -7, -6, -6, -7, -6, + -7, -6, -6, -6, -6, -7, -6, -6, + -6, -6, -6, -6, -6, -6, -6, -6, + -6, -6, -6, -6, -6, -6, -7, -7, + -6, -6, -7, -6, -6, -6, -5, -7, + -6, -6, -6, -6, -6, -5, -5, -6, + -6, -6, -5, -6, -7, -6, -5, -6 +}; + +const int8_t Dw3Weights[576] = +{ + 88, 31, 107, -65, -46, 101, -127, 91, 95, 56, 127, -107, -41, 103, 71, 9, + -1, 113, 92, -127, -86, 17, 62, -24, 60, 101, 127, -60, 87, 64, -94, -53, + -46, 116, 99, 92, 14, -95, 127, 43, 117, 111, -105, 123, -114, 2, 108, 45, + -80, 18, 127, 69, -83, 114, 86, 98, -89, 127, 79, 52, -112, -113, 104, 54, + 78, 50, 98, -75, -127, 122, -46, 127, 90, 82, 111, -55, 10, 93, 40, -29, + 9, 110, 95, -90, -20, -127, 89, -28, 45, 57, 89, -93, 110, -10, -14, -96, + 36, 127, 113, 76, 41, -72, 72, 60, 114, 93, -71, 97, -100, -105, 98, -72, + -127, 59, 59, 81, 105, 127, 83, 123, 27, 100, 51, 109, -11, -44, 65, 60, + 127, 127, 102, -73, 3, 118, -100, 88, 53, 85, 94, -127, 53, 116, -35, -127, + -78, 76, 104, -40, -78, 4, 71, -127, 40, 77, 55, -40, 118, 11, -69, -75, + 127, 124, 127, 127, -47, -68, 123, -12, 104, 49, -127, 106, -60, -104, 84, -127, + 27, 112, 90, 112, -127, 117, 99, 127, -73, 100, 87, 127, -121, 35, 119, 87, + 34, 77, 41, -76, -50, 9, -34, 81, 27, -10, 29, -63, -127, 29, 51, 41, + 6, 21, 20, -34, -45, 40, 55, 55, -51, 10, 48, -74, 59, -11, -16, -20, + -83, 64, 94, 40, 5, -9, 25, 127, 40, 53, 51, 34, -116, -43, 38, 33, + -41, 14, -16, 49, -52, 101, 34, 69, -27, -3, 36, 40, 67, -127, 20, 44, + -2, 125, -43, -74, -50, -16, 14, 83, -1, 12, 26, 22, -59, -17, -60, -26, + -101, -20, -16, 2, -105, -60, 70, -12, -23, -29, 4, -104, 42, -46, 27, -15, + 15, -22, 71, 1, 30, 0, 1, 108, 34, 49, 69, 44, -80, -126, 50, -115, + -127, 32, -78, 5, 45, 43, -40, 55, -127, -53, 44, 46, -44, -64, -23, 33, + 59, 91, -4, -97, 19, 13, -13, 80, -20, 14, 29, -64, 15, 4, -55, -126, + -26, -8, 7, -36, 18, -45, 67, -97, -31, -7, 8, -88, 57, 12, -41, -25, + 13, 40, 38, 18, -63, -11, 43, 88, 40, 49, -57, 72, -59, -35, 48, -24, + 5, 74, 10, 78, -69, 85, -2, 67, -6, 21, 42, 43, -127, -23, -14, 80, + 57, 98, 127, -71, -30, 91, -98, 116, 101, 127, 37, -127, -83, 122, 1, 33, + -3, 121, 127, -64, -127, 44, 33, 108, -60, 127, 82, -43, 127, -127, -36, -96, + -16, 93, 124, 54, 40, -99, 69, 110, 114, 105, -122, 127, -110, 2, 90, 13, + -36, 127, 62, 58, -56, 110, 97, 127, -102, 114, 70, 90, -62, -99, 127, 127, + 98, 106, 115, -92, -98, 127, -42, 106, 127, 62, 63, -59, 31, 121, -122, 34, + -127, 127, 116, -83, -71, -4, 94, 47, -127, 73, 72, -111, 125, 38, -31, -127, + 88, 100, 121, 18, 27, -127, 54, 58, 127, 118, -99, 100, -127, -127, 127, -73, + -106, 26, 3, 47, 88, 115, 93, 104, -47, 84, 82, 101, -48, -113, 84, 94, + 113, 104, 96, -127, 15, 83, -106, 93, 96, 76, 41, -56, 5, 127, -127, -81, + 14, 63, 86, -41, -55, -103, 127, -19, -125, 97, 73, -127, 87, 90, -127, -100, + 92, 105, 126, 41, -127, -97, 114, 83, 117, 127, -18, 96, -45, -105, 102, -16, + 22, 119, 85, 127, -60, 121, 127, 89, -8, 104, 127, 43, 62, -3, 106, 66 +}; + +const int32_t Dw3Bias[64] = +{ + -6605, -10153, -4598, 7893, 3091, -5195, 5672, -5569, + -4578, -5070, -3056, 8496, 1190, -6996, 1456, 971, + 1787, -5185, -3529, 3965, 5168, 1175, -7278, -613, + 2584, -4569, -5410, 8071, -5858, -788, 4954, 5033, + -3274, -5374, -5386, -4670, -660, 7344, -6727, -11941, + -4527, -7273, 7939, -4839, 6550, 5463, -5822, 912, + 4199, -7446, -3793, -5085, 721, -6710, -3237, -6000, + 4585, -6500, -4194, -6658, 3641, 5860, -3560, -6452 +}; + +const int32_t Dw3Multipliers[64] = +{ + 1776335214, 1132088019, 1876116268, 1611111811, 1184162688, 1996932430, 1096943076, 1371394448, + 1506820650, 1731728546, 1106214929, 1239016708, 1990525105, 1919794050, 2142694573, 1957124159, + 1855338871, 1483904430, 2133083176, 1527741099, 1533422652, 1953426638, 1124872612, 1179795360, + 1302702435, 1950357155, 1910917571, 1575504336, 1274588009, 1359394295, 1228120160, 1316414711, + 1088914946, 1508004927, 1074869678, 1692389563, 1424279587, 2067557288, 1878788944, 1771571080, + 1668810634, 1407563894, 1422985700, 1848594936, 1698389844, 1928775772, 1138964291, 1377972944, + 1288925184, 2027049589, 1485478961, 1478211274, 1617068685, 1226948987, 2062963370, 1287466405, + 1870546235, 1945623461, 1525298562, 1612228929, 1332574166, 1431874465, 1141159353, 2072159396 +}; + +const int32_t Dw3Shifts[64] = +{ + -8, -8, -7, -8, -7, -8, -7, -7, + -7, -8, -7, -8, -8, -8, -8, -8, + -8, -7, -7, -8, -8, -8, -7, -7, + -7, -8, -8, -8, -7, -6, -7, -7, + -7, -8, -7, -8, -7, -8, -8, -9, + -7, -8, -8, -7, -7, -8, -7, -8, + -8, -9, -7, -7, -7, -7, -7, -7, + -8, -8, -7, -7, -8, -8, -7, -8 +}; + +const int8_t Pw0Weights[4096] = +{ + -9, -36, -96, -15, 26, -18, 78, 81, -80, -49, 66, 83, 42, 56, -41, 62, + -110, -73, -13, 26, 57, -34, 69, -53, -99, 127, -9, 14, 17, -30, -40, 14, + -46, -10, -28, 36, -72, 40, 55, -1, 33, -58, 23, -10, 94, -30, -40, 80, + -123, 60, -86, -91, -62, 91, -63, 6, 57, -50, -42, -30, 39, -37, 81, 39, + -10, -21, 7, -87, -32, 40, 127, -28, -37, -27, -7, -84, 5, 1, 47, 52, + -33, -13, 44, -13, -12, 21, -28, -30, 46, 29, 26, -31, -12, 49, 46, 25, + -5, -36, 2, -33, -13, 27, -35, 60, -76, 36, -58, 39, -70, -30, -18, -56, + 32, 49, 25, -71, -11, -26, -34, -21, 51, -34, 53, 19, -70, -33, -27, 49, + -31, -20, 29, -4, 21, 10, 7, 26, 0, -36, 2, 55, -70, -87, -8, -74, + 6, -87, 24, -66, -46, -77, -10, 54, 40, 36, 66, -41, -28, 18, 66, -45, + -31, 39, -4, -61, 18, -78, 35, -53, 127, 5, 35, 51, 17, -9, 17, -16, + -22, 23, 31, -49, -46, -9, -72, 0, -70, -51, -17, 26, -1, -20, -19, -46, + -73, -1, 54, -43, -71, -31, 127, -97, 6, -20, -40, 26, 76, 85, -105, -46, + -6, -5, -67, 46, -48, -85, 70, 42, -44, -33, -46, -2, 13, 22, 1, 61, + -78, -78, 75, 7, -17, 120, -3, -45, -114, -44, -69, 41, 123, 27, -50, 47, + 12, 46, 10, -1, -57, 20, -14, -55, -39, -18, -124, -24, 42, -101, 98, -87, + -53, 88, -14, -12, 13, 46, -2, -27, -26, -43, 66, -1, 57, -16, -31, 50, + 0, -77, -49, -14, 13, 56, -66, 14, 8, 27, -55, -32, 6, -127, -83, 54, + 44, 24, 45, 47, 43, 1, 19, -125, 47, 1, 35, 21, 11, 4, -94, -54, + -64, -60, 27, -39, 8, 62, -70, 27, 78, 43, -7, -109, 24, 30, 15, -7, + 36, 25, 33, -37, 9, -8, -61, -84, -97, -11, 7, -37, -1, -27, 30, -28, + 60, 17, -98, -43, -73, -81, -27, -81, 17, -127, 53, -23, 5, -24, -118, -56, + -67, -83, 42, -8, 35, -35, -28, 67, 43, -45, 43, -14, 13, 105, 37, -109, + 23, -39, -21, 50, 12, 38, -28, -72, -51, -40, -3, -49, -36, -115, -64, 104, + -66, 61, 56, -23, 41, -29, -13, 27, 13, 18, 37, 61, 22, 76, -1, -89, + -114, 1, -89, -75, -25, -127, -64, 96, 84, 51, -124, 8, 85, -50, -19, -3, + -32, -10, 18, -43, -61, 77, 83, 19, 57, -22, 0, -85, 64, -27, -19, 67, + 85, 19, 6, -43, -87, 9, -107, -60, 29, -122, 100, -93, -59, 3, -73, -45, + -2, -92, -10, 34, -35, 8, -41, -113, 50, -40, -43, -3, -5, -47, -14, -84, + -108, -51, -46, -15, -34, -106, 22, 30, 1, -51, -80, 57, 27, -127, 58, 99, + 58, 2, -59, 49, 78, 19, 78, -49, -79, -24, -101, 60, -72, -17, -87, -81, + -32, -95, -27, 80, -18, 12, 27, 121, -49, 15, -64, 41, -63, 68, 72, -95, + -87, -45, -10, 23, -76, -43, -21, -35, -98, 39, -65, -127, -61, -12, 63, -29, + -42, -112, 33, 37, -58, -74, 62, -43, -26, -81, -22, 53, 45, -115, -111, -65, + 10, -40, -10, -20, -13, -8, 0, -74, -96, 81, 60, 8, -83, -23, 21, -13, + -37, -28, 43, 37, -29, -112, -43, -37, 49, 28, 65, 4, -23, -44, 28, 10, + -54, -32, -45, 14, 18, -15, 41, 15, 2, 16, -10, 7, -19, 12, -71, 62, + 12, -55, -9, 20, -127, -31, -51, -14, 13, -58, 48, 9, 50, -61, -18, 19, + 6, -36, 5, -28, -117, -43, -59, -4, -21, 51, 30, -32, 5, -34, 18, 36, + 39, -41, -45, 78, -49, -12, 26, 12, -59, 6, -12, 43, -4, -19, -2, -20, + -19, -24, 127, 16, -13, -45, -105, -79, 49, -18, 63, 94, -75, 12, 5, -93, + -44, 37, -75, -45, 71, 58, -26, 8, -34, -14, 89, 31, 30, -27, -22, -20, + 12, 18, 28, -32, -7, 15, 28, -9, -21, -27, 39, -33, 10, 7, 1, -63, + -3, 25, -48, 67, -108, 57, -64, -81, -21, 22, 27, 25, -18, 41, -22, -43, + 21, 53, 9, 62, 62, -51, -15, -127, 21, 6, -33, 69, -41, 77, -25, 43, + 2, -3, 30, 4, 37, 6, -2, -75, -36, 68, 56, 102, -36, 14, 15, 74, + -37, 112, 31, 90, 95, -28, 21, -41, 2, -54, -28, 22, 57, 47, -26, -14, + 8, 3, 74, 70, 108, -62, 32, -77, 33, -22, -36, 25, -1, -17, -85, 7, + -43, -32, -18, -43, -43, -12, -26, -86, 27, -36, 44, -17, 5, 57, -29, -4, + -23, 4, 32, 49, 65, -27, -103, -27, -78, 0, 14, 14, 57, -126, -29, -13, + 126, 10, 16, -5, 26, 75, 34, -21, -6, -35, -3, 39, 27, 47, -58, -31, + -51, -21, -60, 30, -30, 67, -73, -15, 28, 23, 52, -46, 24, -127, 74, -22, + 62, 0, 88, 60, -35, 110, -2, 50, 61, 23, 55, 62, 5, -40, -82, -40, + -65, -20, -67, -41, 68, -33, 6, 34, -47, 60, -34, -37, 55, -81, 88, -50, + 77, -70, -91, -33, 66, 20, -36, -13, 59, -108, -6, -47, 47, -19, -35, -42, + 20, -21, -32, -127, 67, 33, 40, 89, -58, 28, 54, 44, 10, -47, -12, -12, + -9, 13, 75, 22, -1, -58, 24, -127, -3, -45, 14, 35, 22, 75, -36, 10, + -14, 11, 3, 34, -59, 43, 46, 16, 9, 50, 57, -18, 19, -32, 34, 77, + 7, -15, -43, 39, 59, -13, -70, 6, 85, -18, 60, 30, 42, 75, 30, 60, + 50, 36, -75, -75, 25, -67, -23, -17, -26, 88, 33, 18, 21, -25, 31, 7, + -57, 45, 71, 93, -25, 50, 101, 73, -2, -23, 52, -53, 26, -13, -127, -55, + -62, -42, -36, 61, 80, -1, -54, -21, -24, 45, -32, -69, 24, -41, 28, -121, + 64, 56, 65, 25, 4, -6, 14, 72, 15, -56, 14, -121, 1, 19, -5, -90, + 59, 17, 0, 44, 69, -12, 2, 27, -104, 0, -36, 4, -40, -21, -12, 24, + -8, 67, -30, -30, 22, 42, 9, 69, 51, 85, 44, -8, 5, -111, -93, -113, + -56, 20, 42, 27, -14, 33, -5, 92, -7, -46, 99, -62, 6, 72, -111, -68, + 44, 126, -53, -69, 7, 39, 58, -82, 100, -41, 93, 58, -36, 53, -45, -31, + 126, 57, 6, -7, 9, -4, -15, 13, 0, 65, 26, 127, -20, -49, -9, -15, + -39, -16, 43, -8, 81, -51, 55, -92, -53, -44, -16, -4, -21, -25, -16, -7, + 27, 15, -127, 32, -30, -25, -26, 12, 20, -1, 5, -22, -37, 18, -10, -28, + 14, 54, 10, 26, 9, -71, 16, -59, 6, -59, -46, -29, -18, -55, -68, 28, + -32, -46, 13, 2, -33, -3, -31, -31, 75, 1, 17, -27, -95, 22, -110, -28, + -90, -28, 16, 8, -60, -10, 23, -4, 27, 52, -82, -3, -69, -115, 76, -29, + -48, -76, 8, -39, 24, 117, -48, -75, -57, 61, 20, -28, 77, -23, -61, 74, + -48, 40, 69, 61, 37, -20, -40, 81, 6, -19, 31, 80, -10, -68, -62, -7, + 12, -127, 98, -31, -19, 107, 86, -37, -12, -11, -64, -26, 24, -22, -19, -40, + -31, -44, 79, -8, -70, -5, -67, 74, 105, 65, 45, 127, 67, 101, -71, 42, + -81, 37, -96, -103, 50, -23, -41, 90, -68, -57, -24, -83, -29, 89, -9, 50, + 73, -38, 60, 52, 13, 57, 70, 90, 36, 67, 27, 24, -6, 49, 16, -81, + 107, -24, 37, -44, 54, -20, -71, -61, 10, -2, 114, -44, -17, 79, -74, 80, + -7, 20, -28, 9, 16, -9, 4, -19, 15, -41, 14, -21, 23, -3, -33, 22, + -16, -32, 23, -20, -8, 37, -18, 25, -10, -75, -10, 31, 3, -13, 20, 3, + -30, -70, 32, -12, -40, 60, 32, -10, -55, -14, -25, -31, -33, -42, -21, -21, + 7, 35, -38, 127, 34, 40, -5, -25, 41, -73, 35, -14, 7, 33, 1, 18, + 45, 35, 49, -83, -63, -80, 9, 63, 59, -87, 43, -20, -29, 11, -77, 127, + 100, 13, -91, 17, 80, 30, 32, 72, -56, -54, -121, -29, -21, -80, 22, 23, + -86, -41, -2, -68, -52, -34, 71, -62, 8, -79, 34, -8, -6, 83, -80, 80, + -10, 63, 82, 2, 7, -47, 63, 83, 84, -79, -28, 64, 36, -22, -49, -18, + -78, 3, 49, 77, 35, 85, -62, 21, 50, -54, -47, -34, 35, 4, -19, -85, + 57, -59, -40, 34, -18, -72, -99, 23, -62, -24, -71, -24, 48, -20, 96, -50, + -21, 18, -87, 10, -52, 35, 61, 36, -16, 21, 37, -61, 118, -55, 28, -67, + 28, -1, -78, 37, -127, -47, -94, -83, 4, -47, 29, -52, 25, -55, 25, -122, + -58, -100, 63, -48, -87, -44, 26, 51, 20, -54, 119, -20, 38, -46, 7, -99, + 16, -27, -41, -54, 0, 69, 19, 52, -34, 58, 5, 100, -3, -24, -76, 62, + 16, 16, -6, -49, 24, -76, -10, -86, 67, -37, 38, 50, 19, 70, -9, -63, + -16, -77, -84, -109, -48, -29, -120, -36, -127, -9, -30, 59, -33, -24, -49, 11, + -127, -69, -26, -11, 50, -75, -42, 99, -69, -121, 67, 29, 68, 92, 37, 23, + -40, -120, -103, 125, -28, -60, 5, -17, 101, 3, 78, -82, -7, -73, 35, -2, + -36, 28, -11, 58, -17, 6, -8, 73, -40, -16, -1, 123, -1, 18, 43, -109, + 56, -27, 33, 16, 40, -3, -113, 47, -36, 42, 76, 13, -66, 57, 31, 59, + -12, 65, -3, -50, -27, -99, -118, 57, 74, 74, 24, -12, -112, 127, -25, -20, + -88, -59, 47, -33, 27, 66, 2, -71, 66, -18, 58, -57, 72, 116, -2, -20, + -13, -11, 37, -26, -31, 74, -61, -31, -50, -20, 79, -64, 55, -19, 7, 39, + -50, -81, 5, 72, 75, -46, -64, 37, 44, -75, -104, -117, -3, 91, -47, -24, + -29, -67, -56, 14, -28, -42, -16, -36, 7, -61, -63, 77, 60, 15, -25, 6, + -20, 8, -2, -69, 64, -18, -75, -127, -109, 17, -25, -16, 38, 62, -72, 66, + 27, -15, 79, -34, 47, 73, -53, -63, 6, 32, 43, -30, 13, 32, -3, 9, + 37, -21, 8, 1, 3, -8, -42, -72, 73, 74, -11, -18, -17, 37, 1, -44, + -11, -36, -35, 3, -18, -75, -95, 21, -69, 11, -46, 127, 12, -38, 5, 75, + 34, -68, -37, -44, 67, 47, -96, -59, 2, 34, -39, -43, -108, 77, -106, -3, + 9, -80, -67, 19, 78, -26, -83, -22, 83, -85, 50, -98, 69, 16, -92, -7, + 108, -30, 40, 5, -23, 3, -20, 53, -71, -70, -35, 12, -76, -83, -50, 64, + 28, -26, -101, -4, 8, 53, -29, -55, 30, 40, -105, -61, -10, 13, -60, 20, + -14, 27, -127, -64, -70, 78, -23, 2, -17, -118, 44, 64, -16, 112, -85, 55, + 28, 16, 47, 66, -81, 30, 59, 16, -109, -6, -59, -111, 8, 12, -66, 53, + -45, 76, -39, 42, 43, 66, 21, -48, 55, 3, -73, 5, 18, 79, -35, 24, + -77, -25, -2, 60, -72, 89, -55, -86, 46, -72, 8, 102, 27, -61, -32, -56, + -2, -109, 19, 68, -18, 20, 67, 73, -125, -70, 49, -19, 39, -40, -6, 20, + 53, -21, 28, -19, 48, 28, 94, -44, 28, -72, -72, -32, 84, 4, -64, -33, + 14, 31, 86, -1, -57, 16, 32, 125, -7, -23, 127, 9, -75, -4, -103, 21, + 34, -13, 43, -108, 114, -40, 95, 38, -17, -65, -59, 7, 9, 11, -63, -19, + -43, -74, -34, 82, -34, -81, -25, -57, 7, 2, -108, -25, 77, -39, 53, 6, + 10, 33, -13, 104, -103, 73, -61, 25, -100, 0, -5, 11, 35, -10, 104, 29, + -80, -2, -23, 15, -24, -72, -125, -106, -127, 26, 43, -79, 33, 13, -70, -45, + 39, -102, -29, 77, 114, 7, -68, -23, -50, -26, 4, -38, 0, 90, -12, -32, + -19, -54, 46, -54, 63, 72, 71, -90, 101, -34, -30, 127, 34, 20, -1, 32, + -3, -27, 29, 30, 7, 38, 5, 11, 96, -51, -29, -13, 32, 11, -14, -5, + -22, 13, 36, -84, -10, 63, -28, 33, 9, 42, 49, -62, -30, 29, 78, -41, + -75, 7, -15, -77, 11, -6, 74, 52, -29, -45, 19, 54, 45, 11, -68, -1, + 29, -68, 38, 17, 31, -43, -84, -23, 46, -13, -6, 11, -22, -118, -16, 50, + 3, -20, 16, 5, 106, 31, -33, 82, -13, 33, 94, 72, -3, 63, -59, 44, + 34, -66, -4, 23, -56, -52, -42, 22, -10, -21, 7, 56, 50, 26, -127, 74, + -14, 82, -14, 17, -14, 54, -58, 34, -75, -46, -86, 77, -31, -79, 12, 3, + -69, -4, 53, 45, 67, -47, 42, -36, -37, 24, 46, -81, -21, 33, 35, 82, + -39, -24, -26, 14, 80, -49, 20, -19, -127, 49, 75, 26, -18, 20, -78, 32, + 31, 66, -18, 30, -66, 18, 16, -80, 109, 31, 0, -42, 67, 13, 24, 89, + 16, 81, 46, -3, -6, 60, 33, 38, 111, 32, 9, 46, 28, -37, -22, -90, + 21, -96, 19, -1, 25, 10, -19, -32, 38, 4, -23, -38, -80, 14, -32, 43, + 17, 43, -2, 5, -102, -25, 73, 1, -28, -31, -8, -127, 45, 10, -43, -99, + -41, -54, -5, -30, -14, 9, 39, 49, 19, -100, -8, 59, -5, -31, -7, 11, + 22, -26, 75, 68, 33, -6, -29, -38, 1, -20, 7, -9, -47, -16, 13, 79, + -29, -45, 15, -30, 45, -35, -21, -36, 46, -111, 18, 5, -9, 13, -1, 47, + 18, -22, -70, -12, 0, 127, -14, 29, 1, 49, -40, -29, -27, 53, 32, -18, + 53, 23, -32, 23, -19, 7, 66, 18, 47, 11, 0, 11, -16, -6, -61, 20, + 29, 22, -44, -23, 17, -4, -115, -88, 26, -44, -102, 4, -83, 61, 127, 103, + -1, 28, 48, 32, 6, 100, -75, -81, -53, 79, 34, 117, -115, 13, 4, 34, + 20, -27, 23, -9, -25, -66, -25, 29, -47, -81, 75, -31, -94, -4, 54, 36, + 119, -96, 52, 39, 76, 20, -4, -47, -120, 52, -123, -31, -36, -45, -109, -24, + 65, -90, 70, 93, 4, -31, -20, 29, 23, -64, -15, 60, -70, 48, -2, -54, + 25, -38, 0, 36, -12, -16, 39, -26, 34, 16, 42, -41, 0, -63, -17, 21, + 14, 25, 40, -58, 39, 80, -35, -23, -66, 5, 27, -30, 65, -31, -66, 7, + -84, 28, 20, 47, -42, -63, -102, -10, -33, 2, -127, -88, -20, -26, -32, 64, + -53, -66, 59, -10, -59, 127, -88, 80, 2, -19, 46, 88, -18, 31, -8, 2, + 125, 35, -5, 107, 28, 78, -6, 54, -19, -38, 79, -67, 6, 12, -114, 79, + -79, -127, 88, -2, 47, 51, 82, -76, -75, -70, -54, -68, 33, -60, 91, 93, + -83, 3, -35, 12, -26, 57, 64, 16, 22, -53, 54, -6, 25, -83, 18, 112, + -93, 58, 49, 62, 2, -29, -112, 127, 50, 11, 34, -32, -20, 6, 27, 110, + 97, 39, -40, 115, 90, 89, 21, 43, -79, 37, -57, -123, 22, -3, -106, 22, + -23, 11, 20, 39, -79, 53, 26, -37, 97, 39, 16, -91, -19, 45, 10, -72, + 97, 69, -68, -9, -52, 17, -27, 73, 10, 99, 31, -40, -23, 44, -84, 76, + 26, 13, 72, -13, 40, 37, -34, 16, 35, 19, 11, -23, -7, 3, -12, 2, + -7, 61, 15, -25, -23, 21, -59, -45, -9, 3, -127, -7, 42, -12, 33, -16, + -29, 18, -61, 20, 10, -4, 42, 12, -10, -34, 44, 21, 2, 36, -29, -37, + -34, 12, -36, 39, -13, -14, -22, -7, -27, -46, -22, -22, -3, 50, 15, -34, + -3, 32, 14, -78, 50, 41, 31, 53, 88, -65, 11, -51, 39, 15, 17, -14, + 9, 11, -69, 62, -4, 14, -11, -29, -35, -8, -25, -12, 108, -55, 6, 52, + 29, -41, -127, -106, -3, 17, -2, -60, -20, -26, -39, 37, -2, 35, -18, 58, + -42, 15, 20, 22, -109, 36, -105, -26, -25, -43, -25, 87, 19, -70, -23, -60, + -52, -65, 4, -2, -71, -19, -4, -42, 35, -27, -16, 1, 73, 29, 54, 0, + -84, -67, -102, -8, 5, -61, -8, -84, -6, -22, -23, 19, -15, -27, -5, -58, + -35, 28, 43, -84, 68, 79, 12, -51, 45, -17, -11, 42, 11, -13, -14, 41, + 44, -32, -5, 66, 127, -43, -40, 98, 4, 32, 36, -5, -71, 25, 24, 75, + -67, -29, 32, 54, 29, -67, 0, 12, 18, -127, 9, -26, -4, -56, 77, -2, + 42, -5, 18, -23, 35, -10, 12, -60, 38, 78, -65, -78, 6, 74, 47, 47, + -76, 9, -70, -15, -5, -54, 69, -126, 12, -35, 86, -62, -15, -37, 58, 9, + 6, -50, 18, 13, 17, 28, -38, 43, -35, 71, -36, 36, 0, -12, 14, -47, + 20, -23, 36, 29, 62, 43, 73, -22, 71, 63, 112, 88, 59, -2, -15, -24, + -10, -22, -28, 2, -31, 13, 20, 47, 38, -10, 11, -2, -23, 98, -49, 13, + 47, 106, -21, 38, 31, 82, -11, 19, -29, -63, -115, -86, -25, 10, -30, 72, + 14, -85, 42, -5, -80, -101, 59, -59, 21, -74, -65, -127, 20, 27, -30, 15, + 8, 3, 25, -45, -13, 71, -58, -42, 22, -60, -29, -61, 30, 73, 19, -69, + -18, -76, -58, -15, 5, -79, -19, -56, -21, -51, 81, -34, -1, 22, -36, 113, + 11, -44, 60, 39, 20, 25, -33, 59, -64, -66, -70, -6, -63, 60, 38, -80, + -56, 33, 57, -9, 29, -46, -36, -89, -125, 113, -1, 95, 127, 8, 3, -1, + -6, -72, 12, 15, 3, -19, 22, -24, -1, -52, 30, -48, 1, 7, 11, -15, + 53, -45, -79, 56, -92, -52, 32, 67, -16, -42, -56, 72, 39, -81, 47, -19, + -127, -20, 82, -37, 16, -57, 33, 2, -46, 58, -29, -85, -16, -112, -71, -11, + 2, -15, 3, 12, 20, 62, -38, 67, 15, 36, -30, -45, -34, -10, 3, 23, + 12, 19, 65, 14, 29, 58, -30, 49, 4, -7, -39, 19, 31, -54, -57, -14, + -47, -80, -29, -2, 29, 32, -30, -14, 17, 34, -42, -13, 5, -49, 22, -24, + -52, -26, -28, 7, 36, 21, 82, -127, -6, -53, 60, -48, -65, 10, 21, 37, + 14, -7, -11, -18, 12, 46, 10, -52, -10, -62, -6, 56, 30, 27, -20, 47, + 23, -2, 33, -71, -19, 22, 29, -18, 78, 7, 5, 52, -58, -4, -7, 2, + 51, 32, -1, -20, 14, -2, -10, -101, 27, 7, -74, 4, 38, 38, 83, -67, + -3, 63, -61, -44, 52, -21, -29, -31, -29, -73, -127, 63, 7, -14, -106, 25, + 30, -75, -35, -62, 30, -48, -19, -27, 32, 75, 15, -100, -49, 43, -68, -62, + 53, -74, 26, 75, 19, -83, 42, 61, -36, -127, -25, 31, 8, 43, 0, -7, + -23, -17, -40, -39, 40, -6, 61, 52, 55, 39, 92, -49, 17, -75, 88, -37, + 39, 13, -12, 26, 79, 53, -14, -84, -60, -35, 13, 61, 13, -11, 15, -15, + -47, 21, -2, -44, 36, 9, -1, 42, -22, 29, 78, -61, -31, -54, 38, -36, + -127, -43, 65, 19, 68, -101, 68, -44, -115, -57, -55, 30, 74, 69, 51, 36, + -27, -98, -47, 29, -44, -46, -58, 23, 0, 29, -63, -94, -55, -3, -71, 60, + -2, -48, -100, -61, 56, -19, -41, 102, 16, 71, 17, 47, 26, 40, -24, -59, + 13, 19, -31, 59, 6, 17, -91, -46, -69, -20, 18, 40, -82, -27, -21, 46, + -23, 91, 27, -3, -34, 9, 46, -37, -38, 59, 20, 57, 11, 72, -23, -21, + -54, 59, 56, 127, 33, 0, 4, 98, 38, -75, 23, -52, 66, 15, 12, -68, + -42, 60, -38, -9, 2, 25, -97, 40, 35, 23, 61, 83, -13, 8, -36, -21, + -112, -9, -66, -58, -15, 8, -61, 55, -40, 81, -59, -27, -6, 96, -121, -54, + -27, 13, 8, 60, -76, 46, 24, 1, -63, -36, -3, -54, -92, -30, -45, -33, + -19, -71, -9, 33, 36, -18, -16, -4, -33, 58, 54, -127, -29, -20, -62, -1, + -60, 69, -45, 105, -90, -31, 23, 21, -17, 14, 31, -16, 68, 34, -31, -39, + -40, -17, 19, 58, -7, 39, 27, 0, -14, 2, 66, -8, -53, -83, -23, 22, + -14, 40, -35, 7, -18, -76, 16, 9, -8, -50, 39, 107, 41, -60, 52, -77, + -47, 18, -75, -24, 35, -3, 23, -55, -54, 34, -41, 69, 58, 2, -21, -22, + -46, -59, 113, 14, 23, -22, -5, 44, 37, -62, 43, -3, 11, -9, -20, 123, + -12, -9, 59, 40, -66, 13, 4, 46, 8, 47, -9, 15, -127, 3, 35, -3, + -127, -80, -65, 65, 65, -4, -7, 88, 46, 53, 1, 111, 32, -47, 17, 50, + 28, -110, -58, 81, -82, -8, -69, -79, 15, -110, 82, 83, -107, 83, 3, 3, + -35, -83, 58, 63, -44, 8, 25, -16, 80, -7, -68, 86, 12, -38, -124, -31, + 25, -10, -12, -40, -96, -69, 24, 52, -89, 103, -16, 16, -3, 6, -112, 46, + -96, -29, 4, 74, 62, 82, 0, 18, -50, -58, -76, 24, 20, -4, 3, -9, + 25, 0, -67, 0, -100, -26, 28, 49, 24, 75, 46, 16, -37, -78, -6, -84, + 8, -39, -48, -127, -64, -49, -102, -44, -15, -16, 75, 26, 54, -68, -121, -4, + -9, 8, -85, 33, -14, -17, -2, 50, 64, -29, 21, 57, 26, -8, 13, 11, + 24, -16, 23, -54, -59, 23, -52, -58, 20, 33, 75, 88, -56, 1, 0, -50, + -22, -56, -38, -20, 18, 53, 10, 22, 24, -8, 81, -37, 127, 50, -34, 66, + 60, 59, -59, 35, -8, -57, -15, 38, -29, -62, 12, -38, 18, -43, 41, 83, + -17, -24, 12, -62, 6, 76, -18, 29, 76, 42, -9, 48, 65, 45, -35, -41, + -61, -7, 124, 42, 26, -37, -71, -25, -51, -97, 24, 15, 30, 45, -26, -49, + -43, -14, -45, 48, 68, -52, 34, -49, -18, -48, 107, 2, 15, 48, 103, -48, + -103, -127, -10, -77, 50, 51, -33, -44, -23, 19, 76, -36, 0, -5, -19, 52, + -77, 46, 26, -39, -29, 33, -122, 30, 34, 19, 20, 1, -22, -47, 13, -12, + 43, 33, -38, 33, 36, -26, 59, 26, 33, 6, -54, -48, 27, -36, -65, 34, + 36, -12, 34, 50, -14, -22, 41, -6, -3, -24, -50, -8, -13, -3, 72, 49, + 59, -3, -52, 57, -18, -127, 1, 10, -56, -17, -38, 35, 57, -61, 14, 24, + -27, -74, -14, 65, 25, 95, 67, -19, -8, -34, -29, 116, 30, -24, 42, -28, + -36, 59, 16, 6, -32, 52, 94, -2, -74, 17, -45, -17, -5, 127, 3, 59, + 24, -73, -61, 83, -43, -52, -6, 31, 40, 10, -64, -56, 41, -29, -54, -77, + 62, 16, -2, -30, -33, 0, -31, 53, 34, 18, 16, -87, 91, 53, -32, 121, + -34, -12, 79, 31, 43, 96, -32, 39, -11, 33, 35, 1, 30, 33, 2, 42, + 22, 14, 127, -44, 53, -10, 56, -10, 46, -4, 23, 25, -37, 40, -9, -9, + 20, 30, 24, -39, 49, -7, -1, 28, -53, 6, 12, -33, -27, 50, 45, 36, + 99, -43, -20, -29, 35, -18, -23, -46, 79, -35, 35, 1, -16, -28, -55, -7, + -19, 4, 20, 24, 84, -6, 2, 9, 65, -60, -17, -43, 35, -5, -60, -6, + -11, -70, 31, -127, -96, -9, 20, 35, 63, 64, 43, 68, 40, 42, 22, -42, + -61, -79, 56, 1, -64, -17, 37, -3, -106, -60, 56, 45, 30, 14, -24, 42, + 14, -40, -7, 0, 33, -36, -65, 19, -5, -54, 12, -22, -12, 56, -93, 83, + 70, -81, 62, 80, 27, -65, -39, -23, -27, 40, 31, 1, -47, -80, 1, 3, + -67, -7, 55, -89, -39, -99, -20, -87, -94, 18, 22, 24, -43, -2, 27, -15, + -32, -78, -41, 29, 20, 12, -46, 47, 50, 127, -24, 38, 54, 14, 64, -24, + -4, 18, 75, 4, -5, -56, 89, 59, 105, -18, -51, 45, 97, 46, -61, 29, + 35, -14, 39, -17, -56, -95, -30, -77, 25, 66, -55, -35, 23, 17, -44, -33, + -39, 102, 13, -77, 12, -101, -13, -105, -57, -24, 18, -51, -117, 27, 48, -5, + -76, 69, -3, -27, -22, 56, -24, -35, 68, 34, -5, -58, 57, 34, -80, 11, + -3, 47, -68, -63, -80, -105, -10, 19, 83, -5, -9, 16, 36, -26, 50, -48, + -59, -91, -16, 79, 6, -39, -23, -127, 45, 9, -26, 46, -24, -112, -15, -79 +}; + +const int32_t Pw0Bias[64] = +{ + 1143, 452, 1101, 2233, 214, 3465, 1760, 3019, + 4610, 1860, 111, -2073, 603, -622, -1663, -656, + -1677, 3002, 264, -2663, 618, 320, 3080, 3058, + -636, -285, 1305, 3331, 393, -1291, 3566, -1084, + -1065, -1492, -314, -989, 1222, 1734, -1998, -1669, + -140, 1365, 28, 1192, -1193, 1304, 1842, 359, + 1878, -902, 2904, -1307, 1194, -1266, 352, 1398, + -2176, 345, -1295, -2321, -1495, 373, 190, 3725 +}; + +const int32_t Pw0Multipliers[64] = +{ + 1403908813, 1147044899, 1914447236, 1926772856, 1687803205, 1673841027, 1492452004, 1290407731, + 1298236758, 1880739243, 1723970011, 1242081502, 1079875800, 1959882319, 1399908236, 1336519798, + 1948419577, 1249741087, 1565001614, 1924750296, 1087565155, 1084226677, 1964816282, 1560196803, + 1282563819, 1837982553, 1099698054, 1772763268, 1457264247, 1082381953, 1565779436, 1749273278, + 1696659517, 1207695517, 2025275298, 1665799470, 1599810309, 1123716740, 2032488783, 1531056021, + 2037097292, 1166725690, 1078253567, 1241569483, 1393760423, 1803354715, 1787003449, 1442331413, + 2142442103, 1103496734, 1260059815, 1601519167, 1979987274, 2131947218, 2128923898, 1421265428, + 1214775932, 1400681133, 1979328308, 1956790058, 1272051923, 1117645030, 1645413267, 2024995954 +}; + +const int32_t Pw0Shifts[64] = +{ + -9, -8, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -8, -8, -9, -8, -8, + -9, -9, -9, -9, -7, -8, -9, -9, + -9, -9, -8, -9, -9, -9, -9, -9, + -9, -8, -9, -8, -9, -8, -9, -9, + -9, -8, -8, -8, -8, -9, -9, -8, + -9, -8, -9, -9, -9, -9, -10, -9, + -8, -9, -9, -9, -8, -8, -9, -9 +}; + +const int8_t Pw1Weights[4096] = +{ + 16, 32, 26, -20, -13, -3, 22, 3, -59, -60, -100, -22, 34, -3, 31, -20, + -12, -47, 0, -72, -98, 16, -17, -8, -14, 70, 42, 28, -7, 42, 85, 25, + -28, 34, 62, -32, 28, -75, -47, 57, -59, -23, -71, 23, -59, 8, 127, 54, + 91, 62, 9, 2, -1, 63, -55, -19, -59, -47, -66, 15, 34, -40, 46, 41, + -62, 76, -30, -7, 18, -15, -83, -83, 17, -95, 57, -37, 51, -35, 19, 33, + 8, -16, -91, 9, 50, -12, -54, -30, -43, 24, 127, 17, 8, 70, -63, 43, + -36, 53, 30, -43, 12, 35, 4, 127, -62, -2, -8, 43, 20, 37, -38, -18, + 28, 47, 11, -19, 24, -1, -23, 16, 75, -8, 23, -80, -52, 39, 13, 37, + 60, -53, -91, 27, -127, 2, -64, 84, -6, -32, 40, -14, -1, -44, 33, -37, + 25, 97, -57, -32, -79, 70, 1, 43, -77, -6, -102, -26, -3, 101, 43, -64, + -54, 4, 106, -75, 62, 16, -6, -33, 67, -51, 5, 57, -54, 5, -34, -47, + 11, 77, 24, -40, 96, 50, -45, -71, -14, 28, -90, 38, -42, 52, 99, 90, + -47, -29, 31, 11, -22, 108, -33, 22, 51, 39, -14, 56, 20, 4, 51, -11, + -46, 16, -25, 25, -71, 8, -2, 0, 27, 8, -46, 27, -71, 24, 55, 5, + -49, 22, 1, -30, -59, -57, -62, 86, -24, 51, -28, 27, 8, 51, 21, 60, + -7, -51, -33, -38, 17, 40, 70, -10, -27, -37, 25, -10, -127, -26, 17, -7, + 16, 108, -29, -15, 38, 37, 19, -16, -9, -26, -4, 62, 127, -29, -64, -58, + -28, -97, 71, 47, -36, 25, 64, 7, -22, 68, 63, 67, 70, -56, 61, 12, + -41, 48, 80, 72, -2, -57, 64, 21, 13, -6, -23, 57, -48, -60, -6, -20, + -49, 66, 10, 37, 32, -36, -37, -50, -39, 23, -46, -12, 87, -98, -121, -1, + -19, -22, 27, -51, 49, 84, 16, 32, 0, 5, -24, 8, -42, -22, 32, -21, + 20, 8, -33, -52, -52, 23, -8, 67, -40, 54, -50, -5, 65, -3, 55, -1, + -38, -19, 1, 14, 28, 31, -2, 10, -14, -55, -43, 51, 55, 61, -30, -67, + 78, -34, 35, 43, -64, 72, 17, -62, -11, -68, -24, -12, 127, -58, -91, 6, + -28, -79, -122, -80, 29, -3, -13, -36, -62, -3, -47, 45, 63, 54, 36, 2, + -86, -23, 8, -45, -23, -25, 80, 60, 6, 16, -127, -14, 19, -9, 72, 44, + 107, -38, 14, -42, -68, 28, 14, 1, 93, -84, -106, -112, -35, -67, 11, 18, + -38, 53, -47, 0, 34, 37, 15, 16, -38, 61, -42, 17, -77, -40, 83, -49, + -32, -106, -9, -8, -1, -69, -16, 57, -103, 57, 30, 6, 9, 47, -23, -80, + -103, -59, -52, 55, -107, -4, -51, -93, -77, 80, 81, -49, 34, 72, -78, 41, + 67, 105, -88, -73, -47, -6, 77, 17, -36, -103, -79, 34, -31, 19, 2, 127, + -3, -25, 68, 16, -86, -36, -9, -53, 41, -61, -34, 20, 1, 29, -12, -121, + 17, 20, -92, 73, -5, 29, 41, -11, -4, -70, -44, 25, -31, 33, -108, 2, + -17, -43, 70, 127, -58, 68, -45, 20, -1, 22, -71, -56, -8, 35, 57, 78, + -5, 22, -36, -51, 8, -68, -23, 42, -71, 58, 22, 56, -33, -58, 1, -39, + 57, 20, -18, -40, 50, -28, 11, -86, -4, -127, -19, -63, -98, 54, 115, -86, + -71, -29, 40, 22, 74, -38, -53, -44, 7, 10, -62, 59, -73, 49, 13, -6, + 100, -12, -45, -74, -86, -6, 90, 27, -54, -42, 13, -6, -61, 33, -45, -3, + -40, 72, 3, 35, 93, 28, -42, -22, 3, -54, -11, 34, -14, -8, -63, -38, + 71, 71, 69, -8, 96, 80, 32, 8, 99, 40, -43, 127, -93, 69, 4, -10, + 22, 42, -17, -70, 54, 40, -16, -54, -127, -109, 35, -20, 14, -57, -33, -13, + -66, 11, 96, -107, -29, -21, -57, 84, 27, 83, -49, -8, 23, -98, 17, -90, + 48, 41, 94, 47, -79, 36, -3, 41, 12, 26, 62, 6, -3, 12, 1, -69, + 5, 97, -5, 7, 49, -97, 30, -101, -76, -71, -84, -18, -13, 53, -14, -7, + 38, 24, -25, -38, 12, 49, -8, 54, 20, -29, -49, 70, -69, 93, 6, 49, + -16, -33, -14, 82, 23, -37, 42, 93, 37, 115, -54, 18, -124, 94, 99, 28, + 25, 30, -8, -113, 1, 86, 23, 22, -36, 37, -40, 29, -51, -76, -62, 9, + 96, -84, -50, 11, -21, -13, 5, -76, 37, -127, -100, -54, 26, 72, 105, 17, + 20, -4, -17, 27, 52, 82, -5, 85, -31, -78, 9, -26, 73, 8, -16, 48, + -78, 44, -11, -60, -43, -100, -63, 127, -10, 3, -116, 79, 37, 0, 50, 38, + -36, 94, 33, -2, 23, -28, 19, 122, -78, -70, -59, -26, -68, 48, -13, -87, + -88, 13, 1, 19, 18, -29, -40, -51, 61, -38, -41, -34, 0, 32, -44, 57, + 27, -96, 44, -93, -28, 53, -24, 26, 11, -29, -12, -10, -43, -11, 34, 13, + -127, 13, 40, 35, -92, 68, 68, 39, 18, -14, 0, 14, -72, 36, 11, 39, + 43, -88, -46, -101, 31, -48, -28, 70, 66, 56, -6, 96, 22, 63, 0, 23, + -14, 15, -8, -10, 21, -31, 85, -63, -86, -46, -66, 34, -55, 12, 26, 51, + -39, -46, 67, 52, 50, -1, -65, -35, -2, -17, 45, 9, -19, -64, -31, 70, + -48, -2, -7, -18, -19, 40, 56, -127, 0, 14, -4, -52, 1, -79, 23, -54, + -3, -45, 54, -50, 33, 44, -6, 4, -26, -31, 91, 61, -11, 5, -54, 1, + -50, 12, -59, 37, 17, 26, -64, 109, -59, 5, 36, 17, 33, -40, 4, 35, + 23, -114, 71, 30, -4, -86, -2, 52, 68, -63, 59, -26, -23, -8, -8, 48, + 29, -38, -35, -28, -85, -1, -32, 73, -35, 43, -95, 60, 5, 90, 69, -60, + 5, -48, -27, -58, 50, 127, 55, 40, -67, -57, 15, 34, -72, 55, 22, -7, + 45, -2, -71, -43, 31, -23, -1, -27, -45, -14, 32, 2, -8, -36, -36, 10, + 12, 28, -70, -80, -46, 97, 41, 2, -37, 20, 8, -30, -16, 31, -62, -1, + -62, 105, -35, -54, -96, -61, 69, 37, -4, -2, 34, 63, 1, 127, 106, 29, + -36, -71, 25, -97, 52, -24, -26, 86, 9, -21, -123, 91, -1, -27, 45, 98, + 45, 3, -16, 41, 55, 29, 8, 66, 7, 38, -45, 0, 44, -59, 81, 110, + -78, 17, -76, 8, -36, -18, -79, 10, -32, -98, 107, 71, 31, -23, -53, 58, + -77, 78, 107, 85, -127, -113, 64, -74, 64, 25, -86, 126, -30, 62, 86, -62, + -6, 44, -75, 32, 22, 8, 48, 93, 86, 93, -2, 19, -24, -31, -44, 64, + 53, -51, -76, -104, -35, -75, -13, -71, -11, 3, -102, 4, 24, 19, -31, -60, + -27, -52, -62, -84, -12, -41, -14, 5, -2, -119, -47, -39, 57, -24, -82, 47, + 23, 40, 70, 35, 40, 56, -2, -41, -3, -74, -32, -19, 10, 27, -58, 125, + 14, 3, -32, 9, 22, -120, -64, -20, 53, 5, 21, 11, 46, 36, 83, 76, + 76, 30, -47, 29, -43, 4, 15, -39, -51, -8, -117, -37, 54, 66, -127, 60, + 77, -34, 99, 47, -21, 35, -31, 84, -36, 6, -32, 47, 23, 41, -90, 16, + -45, -35, -14, -44, 78, 20, 41, 7, 7, -127, 36, 20, -27, 6, 37, -30, + -72, -27, -19, -52, 23, 20, -6, -26, -65, 29, 44, 83, 65, -46, 19, -55, + 112, 11, 5, -6, 23, 125, -49, 62, -5, 27, 4, 13, -92, 69, -51, 62, + -42, 24, -31, -69, -37, 69, -26, -5, -1, -12, -15, 33, -12, 54, -88, -63, + 50, -76, 24, -30, -127, 11, 66, -30, -50, 6, 8, -24, -47, -16, 94, -6, + 22, 109, -3, -75, 21, 64, 84, 94, 77, -23, -11, -22, 22, 107, 57, -17, + 89, 64, 63, 4, -89, 74, 23, -77, -27, 13, -25, 48, -68, -76, -11, -66, + 116, -6, 82, -20, 59, 73, 19, 14, 25, -13, -1, 76, 27, 2, -26, -14, + -17, 21, 25, -69, -46, -45, -58, 6, -103, 14, 24, 5, 81, -52, 52, -8, + 122, -51, -2, 27, 2, -28, -5, 87, 10, -15, 39, 29, -35, 69, 47, 16, + -21, 55, -13, -63, 42, 29, -42, -40, 97, -61, -7, 15, 127, -6, 71, -35, + -10, 61, -127, -103, 100, 19, -8, 100, 78, 31, -3, 113, 90, 68, -29, -23, + 34, -49, -15, -24, 66, 1, -22, -49, 69, -78, -78, 104, -55, -106, -52, 30, + 38, 6, -8, -10, -106, -8, -36, 35, 23, -121, 21, 53, 7, -73, 33, 11, + 103, 43, -39, -17, 33, -34, 95, -98, -50, 25, 19, -47, 17, 23, 81, 5, + -60, 113, -84, -2, 41, -9, -5, -88, -84, -14, 0, -40, 23, -35, -72, 12, + -16, -45, 113, -80, -40, -86, 60, -1, 82, -28, -10, -101, -26, 9, -25, 19, + -71, 3, -74, 47, 57, -36, -106, 18, 65, -28, -3, 28, 14, -14, 33, 78, + 115, 19, -21, 0, 28, -5, 79, 32, 54, -127, -8, -54, -21, 67, 31, -32, + -6, -23, 20, -11, -91, -23, -3, -92, -127, -66, -2, -35, 74, 42, 7, -5, + -32, 73, 9, 28, -5, -27, -70, -50, -5, 24, -35, 12, 37, 18, -10, 8, + -20, 71, -43, 71, -9, -8, 42, 10, -8, -12, -33, 3, -2, 7, -19, 25, + -85, 49, -14, -47, -63, -23, 62, 22, 28, -11, -10, 73, 25, -64, -18, 21, + -33, 86, 0, 79, 75, -80, 80, 90, -119, 39, -20, 109, 48, -2, -9, -80, + -31, 2, -38, -35, -3, -19, 27, -12, -79, 25, 35, -42, 0, 36, -78, -79, + 29, 48, -61, -48, -117, 11, 9, 3, 127, 68, 57, -31, -112, -52, 109, -19, + -12, 21, 113, -64, 22, 24, 45, -36, 51, 108, -7, 27, 5, 52, 75, 14, + 5, 39, 11, -77, -28, 124, -66, 6, -80, 33, 33, 103, 104, -2, 44, 92, + 17, -79, -41, -16, -127, 40, 37, 104, 52, -75, -51, 35, -47, 40, 15, 29, + -93, -19, -39, 30, 109, 76, -36, 62, -30, -81, -72, 40, -28, 24, 4, -39, + -41, -32, 65, -40, -22, -18, -11, -35, -123, 19, 3, 24, 6, -42, -16, 47, + 25, -42, -51, -56, -100, 81, 55, -16, 77, 105, -21, -55, 48, 14, -46, -25, + 71, -22, 3, -34, -36, -46, 69, -1, -28, 73, 97, 12, -6, 38, -3, 96, + 6, 7, 6, -55, -19, 60, -36, 70, -54, 30, 2, 22, 33, 15, -51, 10, + 12, 30, 43, -16, -45, 20, -10, 14, -18, 8, -43, -127, -9, 44, 77, -57, + 10, -5, -35, -11, 26, -13, -83, -35, 17, -52, 56, 36, -39, 33, -33, 26, + -29, -60, 55, -32, 43, 0, 15, 25, 35, 21, -52, -119, -65, 35, -29, 65, + 41, 39, -52, -43, -34, -25, -17, -127, -24, -55, 54, -8, -5, -1, -23, -36, + 59, 41, 23, -14, -39, 15, 22, 20, -61, 6, 28, 31, 7, 33, 14, -46, + 55, 8, -122, 35, 23, -52, -21, 5, -66, -34, -39, -125, 8, -92, 104, -48, + -44, -85, -110, -104, -20, 49, 52, -108, 24, -49, -25, -18, 99, -120, -11, -80, + 4, 106, 52, -55, 8, 16, 55, -12, 88, -13, 50, 127, -37, -79, 25, -55, + -49, 90, 18, -65, -35, -32, -66, -2, -28, 29, 13, 71, -53, 42, 0, 30, + 22, 9, 105, 58, 69, 56, -16, -49, 49, 6, -35, -116, 86, 53, -17, 0, + 31, 10, 69, 100, -24, -32, -8, 24, -25, 6, 17, 29, -21, 11, 7, 25, + 29, 48, 53, -43, -40, 14, 119, 63, 13, -16, 13, 2, -58, 84, -44, -8, + 33, -2, -22, -47, -13, 9, -33, -19, 35, 1, -38, -127, 36, -44, 51, -78, + -101, -23, -55, -69, -52, 37, 38, -75, -30, 60, 63, -60, 9, 29, -67, -39, + -46, 58, -74, -12, -24, -58, -49, -42, 86, -1, 60, 20, -77, 127, 27, -80, + -102, -73, 55, -23, -36, -66, 34, 114, -6, -19, 47, 83, 11, -52, -66, 75, + 6, -40, -100, -41, 62, -25, 106, 70, -74, -15, -32, -87, 87, 31, 21, 33, + 53, -8, -7, 53, 43, 57, -70, 67, 19, -92, -14, -71, 109, -62, 59, 16, + -83, 7, -78, -8, -119, -49, -127, 60, -76, -84, 45, 82, 16, -10, -27, -61, + -45, -86, 76, 63, 111, -16, -25, 68, 9, -103, -50, -2, -127, 53, 27, 34, + -45, -58, -38, -55, -30, -65, -9, -29, 65, 38, 59, 76, 28, 62, -28, 126, + -46, -17, 29, -24, -60, 90, 49, -114, -31, 9, -78, 123, -22, 31, -54, 42, + -79, -22, 75, 18, -16, 7, -26, -71, -96, 82, 4, -31, 104, 98, 23, 36, + 114, 127, 63, -88, -70, 38, -39, -10, 26, 57, 78, -22, 35, -81, -15, 6, + 37, 22, -20, -25, -108, -35, -30, -27, 77, 121, -19, -64, 37, -48, 28, -29, + 24, 16, -3, 73, 45, -65, -47, -17, 56, 3, -30, -59, -37, 6, -58, -3, + -127, -97, 25, -13, -67, -42, -22, -37, -49, 13, -1, -99, -2, -67, 67, 104, + -32, -23, -31, 3, -52, -27, -92, -51, -40, 47, 2, 27, 45, -41, -21, 89, + 31, 63, 21, -60, 55, -62, -2, 61, -4, -13, -53, 10, -68, 70, 69, -46, + -88, 22, 38, 15, -34, 68, -47, 61, -50, -43, 20, 5, 50, -53, -40, -16, + -90, -30, -63, 4, -57, -15, 37, -89, 19, -64, 9, 4, -4, -76, 81, -66, + -21, -35, -85, -33, -53, -45, 20, 16, -49, -13, 116, 41, -78, -9, -52, 5, + 104, 127, 39, 55, 73, 6, 7, -81, 45, 15, -60, 91, 104, -33, -7, 30, + 92, -22, -53, -34, 0, -37, 37, 20, 37, 25, -50, 49, 17, -52, 52, 72, + -65, -13, -34, -40, -17, -100, 15, 13, -7, 64, -62, 30, -2, 29, -54, 38, + 13, 12, -12, 21, 66, 127, -52, 34, -123, -81, 12, -36, 69, 14, -22, 20, + 24, 26, -17, -67, 2, 40, 49, 40, -57, 58, -57, 78, -33, -10, -20, 6, + 42, 37, 69, 65, 39, 87, 112, -24, -66, 27, 84, 21, -66, -57, 127, -106, + 84, -95, -60, 33, -12, -86, -75, -104, -12, 29, 6, 4, 69, 40, 19, -5, + 16, -5, -17, 84, -1, -53, -71, 14, -114, -39, 15, -83, 78, 114, -70, -77, + -11, -16, -34, 22, 2, -71, -105, -33, 30, -7, -25, 62, 40, -69, 20, -110, + -20, -93, -69, 38, 42, 68, 12, -107, -8, -61, 20, -58, 94, -43, 45, 46, + -62, 0, 121, 49, 22, 24, -64, 64, -49, -51, -6, -39, 23, 22, -127, 62, + 23, -19, -40, -12, 103, -2, 110, -29, -24, -36, 61, 14, 53, 21, -76, 21, + -52, 8, 84, 17, 79, -16, 41, 28, 58, -61, -52, -80, 43, -29, -30, -87, + 125, 88, 27, 66, 66, 51, -62, -52, -62, 45, 49, 90, 74, 10, -56, -67, + -5, -29, 22, 5, 82, -1, -58, 59, -74, 26, 38, -94, 21, -26, -19, -88, + -50, 65, 16, 28, -4, -34, 1, -76, 122, 12, -16, 11, -71, 3, -31, -7, + 28, 96, 28, -13, -2, 127, -9, -77, 58, 76, 17, -61, -42, 14, 52, -36, + -62, -56, 52, -66, -101, -22, 4, 12, -20, 11, 22, -26, 30, -27, 11, -32, + 45, 13, -46, -17, 0, -17, -43, 32, -40, -56, -54, -120, 6, 44, 7, 1, + 62, 47, -82, -63, 99, -9, -47, -7, -54, -34, 56, 33, -78, 38, 81, 35, + -7, 64, 19, 41, 37, 69, -127, 21, -3, -27, -2, -38, 38, 26, -64, -2, + -1, -64, -23, -62, -76, -19, 42, 48, -104, 18, 88, -29, -16, -72, -2, 31, + 39, -113, -22, -35, 62, 101, 52, -2, 39, 57, -89, 84, -17, -60, 89, 56, + -64, 33, 3, -17, 64, -115, -71, -8, 82, -42, -71, 36, 39, 10, 32, 39, + 1, 46, 44, -4, 14, 33, -6, -60, -88, 6, -58, -43, -127, -24, 88, 45, + -64, 40, 28, -44, 92, -71, -120, -39, -43, 14, 93, 6, -2, 33, 19, -40, + -74, 57, -2, 29, -111, -46, -6, 15, -20, -8, -69, 127, 70, -10, -36, -101, + -99, 75, 46, 96, 44, 2, -16, 56, -17, -76, -19, 48, 46, -64, 51, -35, + 23, -68, 60, 9, 64, 9, 22, -98, 44, -79, -67, -41, 33, -20, -39, 40, + 96, 15, 16, 15, -61, 5, 30, -71, 44, 45, -24, 113, 32, -58, -45, 55, + -127, -77, 89, 40, 18, -60, -46, 63, 2, 33, -70, -31, -47, 54, 0, 29, + 10, 9, 21, -39, -103, -63, -38, 5, -4, 27, -39, -11, -5, -26, 87, 19, + 30, 2, 113, -44, 1, -13, -5, -62, 77, -55, -35, -87, 62, 10, 37, 112, + -40, 14, 7, -29, -40, -13, 96, -34, 61, 19, -31, -50, -35, 38, 10, 28, + 27, -18, -40, -107, 44, 68, -62, 20, 20, -30, -62, -48, -54, -23, -11, -3, + -17, 23, 90, -11, -11, -21, -26, -69, -35, 30, 65, -11, -103, 81, -32, 9, + -17, -14, 48, 69, -34, 13, -76, -61, 0, 18, 45, -127, -65, 0, -11, 5, + 73, -24, -31, 0, -23, -48, -6, -52, 18, 74, -14, 89, 26, 10, 58, -23, + -37, -11, -76, -37, -81, 127, 32, 78, 50, 61, 17, 22, -88, -73, 40, -41, + -3, -81, 43, 49, 100, 52, -18, 34, -66, -22, 0, -44, -76, -67, -7, -38, + -44, 24, 53, 112, -85, -63, 35, 52, -63, -58, 1, -2, 75, 50, 12, 17, + 23, 35, 5, -60, -3, -41, -3, -77, -23, -61, -28, -74, -81, -53, -79, -42, + -127, -24, 79, 37, 36, -14, 44, 20, 26, -53, 25, -22, -27, 35, -14, -47, + -23, 38, -14, -36, 39, -8, -36, -32, 7, 5, -6, 25, 9, -14, 57, -1, + 87, -16, -47, -63, 27, 16, 38, -46, 65, 9, -42, -73, -70, 6, 38, -37, + -40, 80, 42, -26, -70, 54, -98, 49, -33, -7, -21, 14, -79, -9, -2, 34, + -85, 29, -87, 5, 14, -64, 33, -12, -2, -12, 57, -90, 22, 18, 34, -70, + -16, -105, -49, -60, -19, -66, -58, 51, -3, 19, 71, -26, -2, -127, 43, 87, + 27, 62, 9, -24, 33, 49, -3, -65, -10, -20, -31, 30, -6, -41, 31, 94, + -2, 29, -6, 50, 17, 43, 0, 48, -63, -1, -67, -28, 9, -23, -7, -5, + 19, -127, 28, 55, 4, 61, -47, 47, 43, -30, 52, -65, -25, 28, 7, -75, + 0, -5, 45, 5, -90, 80, -19, -15, 34, 36, 17, 21, -17, 9, 51, 50, + -14, -46, 21, -3, -35, 4, 6, -53, 11, -19, -31, 56, -19, 25, -45, 29, + 38, -41, 8, 11, -30, 61, -77, -32, 9, -12, 42, 32, 26, 18, -1, -15, + 60, 46, -117, -43, -15, 48, -21, -102, -72, 14, 27, -53, -25, 21, -26, -117, + 34, 20, 127, 9, 26, -60, -38, 62, -17, -43, -15, 56, 13, -36, -24, -21, + 28, 50, -9, -116, 31, -16, -43, 47, -47, -58, -17, 61, 53, -52, -58, 45, + -30, -110, -46, -48, -64, -46, -11, -4, 1, -31, 42, 45, 18, 41, -46, 65, + -69, 46, -20, -13, -53, 28, -16, -31, 20, -14, 30, 51, 46, -23, 38, -18, + 78, -36, 54, 50, -43, 9, -30, -38, 1, 69, 0, 40, -34, -17, 76, 127, + -68, 68, -3, -25, 31, -36, 1, -61, -10, 31, -64, 56, 26, -53, 48, -5, + 67, 25, 24, -30, -7, -33, 31, -37, -2, 8, -66, -65, -13, -45, -51, -27, + -11, 8, 84, -19, -61, -4, 70, 73, 44, -39, 78, 101, -12, 14, 26, 3, + -90, -24, 22, 32, -18, -77, 97, 80, 54, -33, -3, -75, -32, 73, -20, 127, + 80, -58, 72, 70, 71, 61, -80, -53, 93, -8, -86, 7, 16, 35, -76, -28, + 4, -12, -44, 46, 45, 1, -26, -24, 59, -46, 6, 34, -18, -58, -16, 110, + -2, 3, 78, 15, 51, -14, 21, -127, -99, -91, 79, 7, -25, -4, 30, 7, + 40, 65, -67, 12, -54, 64, 11, -23, -53, 81, -14, -38, 13, -15, -45, 22, + -33, 46, -76, -100, 49, 56, -23, -39, 37, -7, -12, 25, 33, 61, -5, -60, + -15, 47, 30, 5, 63, -57, -48, -49, 15, -32, 49, 127, -19, -8, 41, 33, + -60, -48, -4, -46, 60, -39, -15, -19, 39, -93, 112, -36, 71, -69, -37, -4, + -62, -13, -9, 53, -55, -12, -1, -14, 76, 36, -113, -62, -120, -100, 96, 16, + 33, -13, 53, -86, -14, 59, 29, -10, 28, -17, -69, 95, 83, 29, -49, -31, + -76, 8, -15, -17, 70, -13, -56, -15, -89, 64, -86, -35, 63, -30, -35, 28, + 16, -74, 50, -35, 25, -6, 72, -20, -127, -85, -78, -33, 24, 9, -107, -79, + 95, 45, 43, 4, 3, -110, 24, -14, 68, 21, 64, 19, -84, 10, -26, -62, + -20, 31, 84, -93, 65, 22, 27, -25, 45, -68, -66, -39, 17, -12, -103, -87, + 33, 25, 25, 28, 28, 48, -74, -31, 25, -107, 36, 37, 53, -8, -35, 27, + -36, -19, 87, 22, 79, -9, 6, -8, -14, -41, -40, 8, 65, -2, -55, -75, + -43, 35, -20, 47, -95, -122, 64, -6, -1, 2, 68, 19, -19, 14, 21, -88, + 52, 39, -8, -127, 33, -75, -59, -58, -16, -81, -59, 34, 23, -55, 47, 4, + -21, -1, -38, 24, -48, 42, 33, 36, -23, -25, 56, 47, -46, -65, -86, 61, + -30, 26, 10, 13, -4, -1, 20, 10, -57, 26, 64, -9, -70, -30, 45, 23, + 5, -19, -19, 0, 52, 29, 25, 77, 4, -27, -16, 24, 33, -10, 27, 16, + 7, 48, -127, -43, 32, 3, -45, -40, 38, 47, -56, 31, -85, 18, -4, 53, + -108, -78, -111, -78, 35, 74, -20, 108, -19, -21, -4, 80, 12, -37, 24, 102, + 85, -18, -127, 11, -8, -84, 14, 123, -3, 36, -8, -24, 13, 38, -57, -56, + -16, 28, -19, -75, 73, 98, -63, 96, -11, 24, -64, 122, -65, 54, 30, -58, + 87, -90, -20, 31, -64, 24, 62, 62, -100, 24, -26, -42, -66, -81, 68, 97, + -48, -20, -2, 33, 17, 38, -50, -117, 10, -89, 8, -38, 29, -89, 7, 36, + 7, 5, -27, -69, -31, -45, -31, 10, 51, -52, 11, -63, 52, -87, 30, 10, + -18, 48, -83, 21, 63, -48, 39, -30, 25, 2, -34, -35, -15, -14, 77, 61, + -32, 3, -35, 1, 38, -127, -45, -35, -12, -23, -53, 49, 8, 29, -23, 39, + 61, 3, -13, -8, -8, 34, -59, 5, -17, -97, -6, 25, 14, -28, 62, 25, + -34, 4, -47, 3, -58, -83, -111, 23, -64, 10, 57, -127, -24, -12, 123, 4, + 20, -47, 56, 28, 11, -40, -21, 33, -10, 82, -74, -3, -70, 61, 26, -89, + -51, 11, 93, 27, 2, -2, -28, 60, -38, 5, -71, -24, -9, 54, 99, 7, + -7, -126, -4, 62, 6, -32, 25, 35, 3, 18, 47, 27, 32, 18, -41, 13, + -74, 20, 40, -45, 27, 11, 127, 11, 88, -52, 25, 68, 59, 31, -4, 60, + -27, 38, -8, -13, 0, 8, 15, 0, 28, 109, 66, -26, -42, -1, -20, 48, + -28, 93, 28, 53, -10, -33, 78, -67, -26, 10, -88, -16, -94, -21, 12, -11, + 58, -30, 23, 54, -116, 55, 12, 30, -13, 32, 1, -26, 88, -14, -81, 101, + -1, 92, 9, 76, 40, 42, 14, -69, -47, -95, -41, -61, -17, 38, 38, -103, + 75, -73, 101, -97, -67, -68, 65, -124, -30, -10, -93, 99, -107, 45, -13, 19, + 14, -62, -2, 41, 127, -6, -96, 44, 30, -49, -43, 31, -12, -52, -10, 72, + -6, -24, -3, 31, -24, 60, -87, 26, 8, -126, 2, -44, -37, -37, -59, 59, + 6, 59, -19, 116, 22, 53, -59, -58, -13, 61, -65, -29, -1, -19, 43, -127, + 2, 53, 92, -64, -43, 32, -44, 86, 26, 19, -83, 11, -61, 25, 15, 78, + 69, -21, 18, 16, -39, 33, -16, -7, -19, -121, -65, 63, -14, -60, 32, 5, + -17, -21, -42, -68, -2, 15, -49, -36, -10, -10, 127, 6, 26, 4, 59, 14, + -26, -40, -65, 32, 80, -63, -93, 40, 76, -30, 31, 123, -58, 33, -110, 31, + 45, 70, -118, -46, -18, 52, 17, 13, -76, 60, 51, -32, 47, -35, -57, 7, + 29, 70, -9, -71, 22, -29, -74, -20, 92, -18, -48, -31, -116, -34, -63, -50 +}; + +const int32_t Pw1Bias[64] = +{ + 186, -670, 650, 701, -1870, -1022, 2606, 4392, + 2405, -990, 3610, -1117, 866, 1025, 1467, 1919, + -2760, 1825, 184, -1481, -232, -3216, -1497, 1519, + 2631, -2139, -846, -1501, 1545, 3471, -1713, 2908, + 1172, -1139, 3504, 2184, -333, 1698, 564, -1691, + 1025, 1462, 2673, -1145, 2543, -1201, 4829, 3078, + -762, 2140, 178, -2541, 671, -655, 4277, 2889, + -109, -937, 3473, 1043, -2180, 1217, 1525, 2996 +}; + +const int32_t Pw1Multipliers[64] = +{ + 1957573144, 1832652321, 1497381859, 1426089422, 1973120055, 2146985354, 1685186185, 1310780464, + 1593957194, 1483319885, 1551620948, 1640579787, 1945994967, 1076837775, 1284499294, 1881185799, + 1764605718, 1363264957, 1974200879, 1470545663, 1493724355, 1624284781, 1635459349, 1798684362, + 1326601403, 1828923266, 1961645678, 1701523853, 1166126824, 1554340226, 1821715249, 1621789563, + 1778165322, 1378304543, 1803784813, 1716762557, 1922656713, 1295261015, 1862453017, 1499930825, + 1737832507, 1922006875, 1489441703, 1600909576, 1626944601, 1506751357, 1871749116, 1938093442, + 1372711801, 1373011694, 1102907316, 1978986069, 1717719931, 1351060708, 1419599099, 1625244338, + 1317407591, 1446546821, 1379275186, 1366591075, 1084938081, 1658028009, 1414865138, 1659057270 +}; + +const int32_t Pw1Shifts[64] = +{ + -9, -9, -9, -8, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -8, -8, -9, + -9, -9, -10, -9, -9, -9, -9, -9, + -8, -9, -9, -9, -8, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, + -8, -8, -8, -9, -9, -9, -9, -9, + -8, -9, -9, -9, -8, -9, -9, -9 +}; + +const int8_t Pw2Weights[4096] = +{ + 18, 29, 15, -16, -23, -47, 48, -32, -52, -31, 100, -49, 26, -64, -21, -27, + 38, 51, -127, 61, 108, -8, -60, 79, 4, -45, -94, -45, 28, 19, -55, 34, + 30, 39, -9, 16, 51, 70, -32, -49, -10, -16, -16, -35, -74, -22, 11, -34, + -24, -15, -39, 10, -60, -10, -58, 69, -15, 29, -18, -36, -33, 28, -17, -43, + -65, -25, 85, 35, -12, -25, 34, -93, 111, -13, 85, -46, -11, -127, 29, -85, + -31, 44, 58, -56, 67, -38, 12, 79, -34, -11, -73, -43, -9, -14, 46, 12, + -18, -33, 4, -29, -15, -17, -50, 19, 17, -24, 42, 54, -41, -3, -48, -65, + -30, -106, -1, -10, 14, -53, -19, -53, 12, -46, -112, -11, 34, 14, 2, -56, + 34, 0, 13, -25, -1, 44, -45, -8, 23, 42, -102, -24, 6, -13, 103, -55, + 3, -33, 91, -52, 23, 65, -118, -43, -1, -17, 37, 41, -78, -57, -18, -43, + -62, 0, -28, 39, -82, -65, -107, 13, 29, 34, 95, 33, -44, 127, -77, 66, + -38, 45, -85, 9, 22, 65, 25, -90, 34, -47, 27, -23, -16, -66, 40, 31, + -48, 31, -6, -39, -13, 28, 54, 9, 127, 30, -13, 50, -11, -1, 24, 55, + 0, 14, -7, -44, 1, -64, 23, 9, -22, -12, -37, 13, -44, -17, 40, 3, + -34, 11, 71, -47, 34, 9, 2, -53, 14, 21, -43, -55, -20, -13, 36, -15, + 82, -64, 27, 27, 18, -80, -49, -24, -88, 42, 62, -8, 66, 2, 3, 87, + 34, 45, -17, -35, 73, -51, 22, 15, -127, -27, 41, 48, 99, 52, -57, 75, + 90, 85, -107, 105, 93, -56, -5, -56, -49, -41, -27, 32, 75, 34, -15, 18, + 32, 31, -49, 101, 55, 30, 32, -6, -19, 20, 43, -79, -23, 54, -15, -111, + -85, -27, 10, 96, -61, -77, 7, -99, 23, 76, 1, 36, 115, -27, 10, 54, + 59, -77, 49, -18, -118, -46, 45, -30, 51, -78, 61, -74, 78, 34, -14, -57, + 71, -50, 63, 2, -3, -43, -86, -13, 49, 14, 24, 2, -54, -66, 21, 8, + 59, 68, 44, -127, 119, 20, 63, -10, -25, -50, 34, 10, -64, -2, 99, 37, + -48, 10, 28, 43, -25, -57, -12, -13, 16, 41, -35, 45, 12, 19, 15, -26, + 85, -46, 30, -116, 22, -13, 93, 82, -37, -9, -30, 11, 92, 22, -32, 1, + -51, -127, -23, 36, -36, -59, -48, 30, 26, 18, 35, -45, -30, 9, -39, 12, + -32, -71, -62, -55, 97, -63, 77, -33, -47, -61, 23, -77, -64, -1, 22, 48, + 101, 42, -2, -83, -45, -21, 73, -54, -18, 67, -46, 40, 53, -43, 45, -6, + 51, -96, 45, 127, -74, -83, -81, -32, 32, 39, 6, 10, -76, 21, 20, -60, + 13, 42, -16, -86, -69, 29, -53, 15, -55, -49, 40, -2, -36, 12, 26, -100, + 23, 50, -35, 21, -32, 49, -40, -56, 27, -1, 23, -83, -70, 30, 38, 2, + 26, 33, -10, 18, 61, -2, -51, -56, 121, -53, -15, 31, -62, 101, -34, 26, + -25, -49, -41, -44, 92, -79, 76, 84, 15, -5, -40, 112, 13, 60, 26, -127, + 48, 19, 45, -122, -86, -14, 82, -81, 19, 103, -44, -36, -93, -41, 90, -38, + -83, -103, -7, -83, -2, -10, -3, 85, -76, 25, -53, -94, -22, 94, 45, 10, + -1, -77, 38, -16, 52, 64, 17, -67, -7, -6, 4, 87, 29, 12, -25, -57, + -61, -36, -14, -22, 51, 48, -6, 9, 55, 81, -88, -6, -37, -27, 51, 16, + -53, -23, 62, -69, -28, -1, -1, -10, 92, 105, 76, 25, -54, -47, 33, 45, + -53, -8, 20, -9, 10, -49, -72, 127, -19, 8, -14, -40, -28, 83, -123, 34, + -54, 42, -20, 19, 93, 115, 60, -75, -12, 48, -29, 42, 1, -26, -4, -26, + 69, -127, -12, -24, 97, 35, -52, -43, 76, -27, 91, -25, 52, -40, 99, -81, + -56, -33, -67, 4, -54, 0, 32, 47, -125, -16, -32, -48, -43, 31, -11, -24, + -27, -12, -48, 59, -63, -11, -14, -79, 19, -58, 51, -80, -80, -21, -13, -68, + 76, -28, -62, -7, 20, 44, 35, 42, 47, -42, -24, -61, -3, 5, -109, -69, + 15, -70, -14, 2, -43, -4, -8, 10, 52, 39, 48, 71, 11, -2, 8, -45, + 42, 29, -57, -89, 5, 2, 32, 10, 8, 5, 4, -61, -56, -18, 57, -2, + -23, -30, -22, -25, 21, 27, -29, 55, -44, 26, -14, 20, 13, -6, 40, 2, + 127, -53, -49, -27, 48, 32, 7, -23, 45, 18, 7, 35, 57, 3, -76, -42, + 36, -93, -19, -14, -20, -10, -32, 13, 75, -14, 102, -48, 66, -77, 76, -95, + 13, 33, -97, -33, -60, -7, 45, -10, -100, -30, -50, -31, -28, 18, 1, -78, + 32, -15, -74, 17, -42, 34, -34, -50, -60, 26, 22, 7, -1, -29, 20, -24, + 56, 24, -24, 21, 110, -12, -106, 46, 121, -72, -14, 7, -38, 127, -74, -23, + -31, -35, 86, -28, -52, -9, 31, -70, 124, 77, -13, 26, -52, 16, 7, 12, + 91, -4, -61, -18, -73, -95, 43, 45, 127, -80, 5, -122, -52, -13, -70, 28, + 27, -85, 39, -95, 48, 93, 77, -67, -76, 35, -22, -67, -71, -94, 115, 106, + 90, -83, 40, 40, -2, -84, 32, -14, 9, -14, 26, 21, 92, -17, -4, -80, + -50, -62, 24, -15, -35, -14, 1, -29, -20, -41, -9, -6, -32, -70, 36, 69, + -55, -10, 50, -55, -62, -25, -32, -19, 27, -12, 58, 12, -41, -17, -30, 20, + -49, -6, 28, -51, -32, 5, 35, -13, 29, -40, 56, -4, -22, 11, -3, 13, + -49, 86, 50, -44, 14, 9, 127, -77, -14, -19, 21, 6, -15, 49, 30, -50, + -5, -67, 9, -61, -82, 49, -16, -45, -50, -17, 23, 27, 1, -44, -58, -41, + 3, -37, 9, -22, 37, -12, 15, 43, 127, 39, -3, -45, -17, -26, 64, 11, + -30, 16, -55, -61, 8, -18, 16, 0, 25, -34, 57, 2, -50, 2, -20, -17, + 27, 33, 6, -92, 24, 44, -20, 12, 11, 29, -30, 102, -10, 34, 7, -36, + -44, 39, 1, 4, 41, 18, 47, -44, 46, 106, 54, 47, -1, 57, -28, -23, + 6, 83, -32, -23, -12, -36, -3, 76, 36, -47, -60, -57, -27, -42, -58, -16, + -2, -14, 43, 1, 50, 127, 48, -64, 62, 17, 9, -34, -23, 5, 6, -4, + -15, -40, 18, -24, 13, -50, -15, 5, 21, 81, -37, -75, 10, -11, 6, -30, + -25, 66, -25, 0, 90, -29, 61, 2, -34, 32, 25, -20, 1, -6, -66, -60, + 32, 16, -31, 32, -9, 8, 42, 3, -50, 22, -70, 5, 16, 52, -29, -30, + -57, -82, -11, 7, -34, 31, -27, -25, -19, 39, 11, -70, -55, 40, -23, -16, + -31, -60, 23, -18, 20, -4, -68, -76, 28, 27, 15, -2, 127, -48, 25, 45, + 73, -47, -106, 110, 0, -127, 26, -10, 40, -44, 7, -31, -20, 29, 68, -16, + -77, 31, -32, -7, 18, 53, 40, 92, -49, -22, 30, -49, 0, 61, -110, -110, + 5, 89, 25, 77, -47, 125, -32, 0, -39, 51, -44, -52, -9, 43, -99, 19, + 73, -32, -63, -46, -7, -45, 10, -10, 96, -10, 64, -3, -49, 39, -4, 60, + 23, -56, 67, 28, 3, 21, -40, -47, 16, 103, -7, -16, -35, 34, 54, 22, + 19, 52, 111, 28, 23, 34, -91, 88, -24, -83, -12, -24, -34, 2, -91, 10, + -35, -55, 57, -4, -57, 43, -80, -10, 69, 16, 69, -52, -28, 42, -41, 68, + -25, 30, -127, 21, -2, 7, 4, 4, 91, -10, 52, -72, -20, 2, -19, 33, + -9, -68, -28, 61, 90, -5, 90, -31, 75, 18, -12, -37, 50, -76, -40, -63, + 14, 97, -27, -27, -26, 21, 48, -18, -69, 19, -72, -39, 20, 56, -95, -47, + -3, -10, -37, 41, -13, 86, -1, 78, -105, 38, -27, -113, -34, -14, -77, -18, + -33, -88, -76, 102, 5, 10, 99, -38, -20, -49, 127, -68, 81, -84, -60, 6, + 46, -5, 37, -64, 40, -42, 32, 32, 102, 55, -103, 44, 111, 69, 91, -72, + 52, -54, -6, -90, -50, 78, -16, -57, -10, 65, -59, -40, -76, 18, 2, -48, + 92, -7, 65, -108, -14, -21, -5, -2, -55, 58, -20, 24, -3, -9, 13, 77, + 42, -127, -9, 30, 72, 40, 59, -84, 93, -84, -10, -9, -19, 81, -48, -20, + -2, 78, -43, -42, 93, -31, 29, 34, 81, 62, 116, -41, 30, -42, -21, 127, + -92, 18, -25, 105, 107, 45, 98, 85, 36, 18, -73, -19, -36, -81, -28, -49, + -90, -1, -53, 51, -9, 42, 19, -13, 100, 22, 13, -33, -49, 60, -6, -108, + 23, -51, 53, -74, 6, -95, 31, -23, -44, 107, -114, -89, 79, 12, 71, -21, + -11, 16, 22, 81, 28, -41, -24, -18, 11, 35, 111, -86, -31, -57, 27, -50, + -6, 127, -33, 2, 110, -16, -40, 48, 38, -99, -38, -10, 69, -8, -44, -45, + 11, 75, -37, 68, -17, 109, -48, 22, 32, -37, 81, -61, -125, 88, -48, 44, + -18, -21, -85, 72, -71, -43, 10, -38, 33, 25, 22, -8, -8, 20, 46, 15, + 47, -14, 101, 26, -21, -24, -91, 22, 28, 45, 34, 32, 45, -22, -9, 16, + 9, 4, 127, 10, -67, -9, -37, -33, -3, -25, 34, 49, -99, 78, 25, -32, + 87, 97, 14, 14, -1, -17, -61, -67, 27, -29, -20, 86, -46, 1, 16, -26, + 8, 100, 14, -9, -25, -25, 26, -30, 29, 46, -1, 39, -95, 51, -30, -19, + 3, -88, 34, 3, 74, -30, 82, 4, -72, -27, -99, 65, -38, 108, -127, -39, + 10, -5, -15, 39, -16, 32, 20, 34, 40, 25, -25, 45, -41, 20, 80, -4, + -29, 9, 3, -19, -19, -21, -31, 33, -63, 77, 18, -17, 15, 84, -12, 44, + 41, -65, -5, 5, 83, 91, 26, -67, 112, -51, 67, 26, -50, -67, 27, -53, + -25, -27, 2, 13, -7, -6, 86, -26, -84, 70, 15, -3, 127, 29, 9, 25, + 70, 22, -12, 44, 44, -73, 7, -2, 9, -45, -77, -96, -31, 72, -43, 40, + 22, 19, -63, -2, 70, 76, 37, -36, -25, -23, 38, -21, -100, -43, 41, 12, + 7, -2, -39, -56, 9, 31, -65, -24, 70, -14, 92, -29, -41, -20, -15, -41, + 17, -43, 63, -38, -1, 65, 9, -20, 6, 0, -89, 127, -16, -31, -8, 76, + -1, -26, 24, 54, -17, -61, 22, 55, 11, 4, 6, 8, 2, -30, 55, 31, + 29, 12, -21, 12, 93, 53, 38, -6, 38, 59, 11, 34, -31, 29, 73, 51, + 62, -51, -79, -23, -89, 5, -34, -61, 12, 79, 27, 5, -14, -17, -14, -12, + -6, -64, 47, -45, 127, 7, 44, 25, 3, 56, 34, -60, 17, -88, 30, -66, + 14, -29, -45, 81, 14, -4, 32, -67, -47, 3, -33, -11, -28, 30, -70, -83, + 53, -60, -57, -77, -28, -22, -59, -39, -89, -36, -41, -66, -84, 50, 20, -64, + 8, -22, -37, 32, -10, -4, 24, -9, -48, -33, -12, -19, 12, 52, -60, -15, + 83, 15, -22, 91, -44, -94, -51, 67, 64, 124, 29, -75, -11, -35, 122, -66, + -2, 114, -89, 58, 12, 48, 109, 24, -61, 15, 54, 30, -8, -3, 27, -66, + -112, -7, 5, 97, -68, -39, -95, -20, -58, 35, -17, 9, -46, 9, -37, 28, + 54, 111, 21, 64, -8, -79, -52, -69, 6, -25, 45, 39, 22, 127, -44, 1, + 3, 18, 33, -83, -16, 34, 14, 7, 69, -47, 12, 66, 102, 69, 14, 64, + -3, 8, -29, -80, -68, -46, 0, 17, 70, -13, 34, 27, 28, -65, 100, -38, + 38, -79, -21, -69, 49, -73, 28, -27, 41, -31, -5, -31, -38, 54, 70, -99, + 83, -28, -94, -2, 51, 9, 29, -127, -25, 4, -88, -40, -34, 37, 57, -50, + -29, 64, -26, -66, -33, 53, -63, 55, 37, 14, -74, 42, -30, 20, -48, 50, + -55, 50, 82, 36, 41, 33, 25, -17, 27, -51, 67, 3, -16, 13, -113, 71, + -53, -9, 45, 52, 78, 48, 24, 37, 88, -11, 4, -127, -63, 66, 12, 32, + -92, 126, 57, 14, -81, -77, 1, -66, -34, 57, 126, 62, -31, 40, -26, 31, + 41, -127, 97, -14, -67, -26, -50, 18, -46, -24, 11, -16, 54, -7, -8, -23, + -4, -1, -30, 77, 1, -1, 2, -76, -6, -23, -35, -52, -35, 80, -3, 46, + 87, -35, -3, -74, 68, 35, -29, 18, -4, -10, -7, 49, -70, -67, 6, 37, + -6, -27, 5, 12, 73, -1, -43, 65, 110, -25, 19, 23, 13, 55, -75, -40, + 14, 37, -83, -19, -19, -62, 99, -6, 10, -25, 38, -28, -36, -16, 18, 2, + -68, 25, -17, 54, 19, -117, 127, 53, 30, 60, -44, -28, 1, 7, -12, -56, + -91, 11, -97, -7, -50, 35, 27, 24, 12, 4, -93, -26, 3, 55, -49, -63, + 73, -26, -22, -19, 1, 84, 51, -20, 28, -17, -62, 78, 36, -66, -40, -3, + -73, -2, 64, 16, 34, 25, 62, 4, 68, 18, 20, -31, -15, -13, 89, -51, + 52, 66, 86, 42, 53, 39, -24, 32, -14, -58, 53, -127, -102, -81, -109, -45, + -51, 74, -6, 3, 58, 72, -17, -41, 59, -18, 77, -25, -46, 115, -95, 18, + 21, -70, -80, 42, 12, 31, 57, -93, 50, -36, 43, -40, -97, -42, 3, 27, + 63, 3, 49, -61, -40, -3, 70, 80, 0, 80, -20, 91, 19, -15, -127, 13, + 76, -40, -12, 47, -120, -22, -13, 14, -15, 43, 51, -35, -110, 45, 18, -14, + 16, -33, -23, -51, 45, -40, 36, 88, -77, 46, 37, -91, -32, -29, 98, 62, + 37, -14, 118, -36, 16, -33, -45, -71, -3, 81, 41, -23, 31, 82, 0, 37, + 19, 22, -84, -26, -13, 30, 127, -24, -82, 52, 25, 66, -19, -16, -63, -100, + 24, 54, 24, 75, -36, -69, 46, 5, -93, 79, -61, -36, 24, -26, 47, -42, + -95, -117, -98, 47, 66, 56, -21, 78, -45, -12, 15, -4, 21, -58, -79, -83, + 71, -28, -26, -18, 11, 34, -26, 19, 18, 65, 11, -28, 48, -81, 0, 2, + 65, 58, -32, -43, -7, 2, 60, 62, -51, 5, -17, -14, -8, 16, -2, 28, + -119, -57, 58, 20, 14, -56, 2, 35, -1, 69, 28, -40, -32, 24, 96, 0, + -42, -55, -54, -25, -27, 36, -41, 12, -11, -2, -3, -39, -26, 22, -34, -52, + 59, 77, -56, -65, -5, -16, 127, -18, -32, 54, -46, 54, -11, 12, 48, -3, + 6, -40, -3, -65, 70, -27, -19, -14, -18, -77, 45, -48, 63, -85, 75, 76, + -80, -41, 43, -16, 69, 9, -47, 40, 24, -45, 24, 19, -3, -48, -31, -32, + 52, 56, -35, -10, 18, -40, 0, 2, 68, -99, 29, -14, -55, 36, -33, -34, + -32, -23, -22, 4, -29, 15, 104, -28, -79, 56, -127, -36, -25, 43, 67, -56, + 53, -5, -7, -42, 44, 2, 23, -59, -33, -74, -86, -13, -10, 9, 26, 72, + 3, -82, 93, 21, 65, -12, 29, 8, 15, 45, 50, 53, 58, -18, 16, -22, + 75, 34, 21, 7, -48, -30, -20, 36, 1, -40, -25, 5, -8, 8, -67, -11, + 30, -17, -59, 6, -5, 127, 91, -41, -75, -30, -13, 54, -8, -16, -19, -15, + -48, -4, -68, -20, -16, -24, -46, 5, -42, 27, 62, -127, -48, 3, -7, 50, + -79, 45, -13, 11, 100, -80, -1, -23, 16, 45, 17, 89, 38, -31, 57, -39, + -12, 44, -14, 107, 7, 27, 51, -26, -7, -12, -9, 4, -53, 115, 66, 8, + -4, -27, 84, -13, 9, 45, -13, -47, 13, 59, -12, -73, -2, 85, -16, -27, + 35, -97, -14, 48, -13, -57, 1, 65, -15, 66, 6, 9, 28, 37, -99, 16, + 50, 56, -95, -54, -73, -5, 8, -7, -34, 5, -34, -40, -66, 68, 4, 31, + 11, -29, -84, 45, -14, 24, 47, 17, 23, 25, 1, -10, -59, 24, 76, 49, + 2, 27, 57, -61, 7, 8, -100, -127, 82, 27, 2, -17, 82, 50, -20, -14, + -100, 51, 68, 43, 41, -13, -55, -76, -34, 63, -2, -11, -29, -75, 59, -14, + 46, 26, 8, 81, 16, 75, -29, 78, 95, -50, -66, -1, -55, 3, -75, 20, + 45, -41, 58, 16, -63, -11, -47, 19, 23, -11, 26, -46, -12, 23, -76, 127, + -22, -10, 29, 63, 69, 2, -22, 34, 50, 24, -25, -38, 1, -31, 7, 2, + -81, 8, -12, 19, 28, -126, 84, -27, -4, -89, 28, 20, -79, -77, -125, 21, + -25, 72, -23, -12, 28, 43, 51, -40, 84, 73, -111, 19, 24, 4, 6, -122, + -75, 127, 6, -27, -36, -71, -37, 38, -11, 96, -82, 81, -72, 41, -16, -10, + 54, -1, -35, -48, 73, 67, -45, 35, 82, -77, -72, 22, -21, -24, -102, 38, + -30, 88, 73, -6, -10, -22, -3, -31, 73, -25, -52, 27, 73, 44, 46, -16, + -98, -67, -85, -78, 15, 53, 3, 39, -42, -52, -55, 27, 116, -42, -15, 83, + 28, 127, 11, -30, 78, -37, -15, -122, 23, -31, 86, 49, -55, -51, 39, -58, + 37, -62, 4, 18, -71, -3, -36, 37, -81, -108, -55, -13, 25, -33, 19, 13, + 19, 19, 59, 31, -42, 9, -29, -31, 32, -24, -50, -55, 8, 10, -3, 52, + 97, -42, 5, 101, 8, -43, -71, -46, 15, -65, 59, 31, -4, 127, -21, 5, + 62, 23, -22, -16, 33, 56, 33, -16, -14, 14, -21, 75, -82, 43, 51, 53, + 108, 9, 92, -64, -39, 36, 15, -28, 61, 67, 101, 19, -46, 6, 52, -11, + -3, -100, -12, 65, -20, -57, -17, 27, 33, 14, 4, 56, -64, 55, -24, 3, + 78, 13, -48, 23, 45, -17, -25, -6, 93, 21, 40, -40, 38, -3, -22, -31, + 45, -1, 15, 11, -61, 0, 52, 70, -101, 72, -76, 77, 36, -5, 45, 32, + 48, -37, 11, 34, -67, 64, 32, 24, 71, -35, 57, 45, -127, 56, -64, -83, + 55, 37, 59, 25, -53, 18, 19, -71, -20, 90, -27, 18, -10, -47, -48, 0, + -8, -53, 35, -7, 11, 45, 4, 40, 72, 10, -35, -76, -16, 72, -34, 66, + -5, 6, -13, -55, -33, 104, -48, -44, 8, -26, -33, -59, -18, -16, 21, 69, + 127, -4, -60, 32, -2, -7, 114, 42, 27, 2, 58, -33, 21, -26, -14, -28, + 32, -41, 96, -16, -72, 75, -19, 29, 43, -9, 0, -47, 51, 48, 50, 19, + 40, -24, -127, -36, 10, -111, 12, 39, 0, -37, 34, -14, -26, 36, -49, -29, + 51, 67, -16, -59, 95, 47, 9, -9, -3, -64, -6, -32, -46, -93, 80, 22, + 65, -48, -83, 22, -16, -68, 33, 7, 83, 123, -46, -15, -57, -1, 15, -75, + -32, -62, 6, 53, -46, 43, -10, 16, -98, -48, -2, 8, 104, -68, -26, 49, + -70, 5, 19, 51, -1, 9, -20, -14, 64, -93, 39, -39, -49, 109, 2, -11, + 78, 1, 69, -69, 35, 73, 73, 13, 31, -100, 9, 55, -88, 2, 15, 54, + -3, 109, -14, -94, -39, 63, 79, -24, 92, 127, -48, 89, -88, 17, 8, -44, + -49, 28, 23, 53, 54, -34, 46, 39, -38, -35, 7, -10, 45, 59, -116, -17, + 14, 93, -82, -23, -39, 15, -106, -56, -14, -4, -43, 1, 43, 82, -34, -6, + 30, 40, 36, 127, -70, 43, -53, 26, -29, 40, 8, -123, -52, 89, 16, 4, + -49, 19, 108, -40, -12, 9, -38, -9, -96, 2, -54, -68, 123, 60, 48, 19, + -49, 16, -22, 59, -41, 6, 42, -27, -36, 109, -30, -44, 54, -31, 32, -73, + -25, -4, -13, 78, 18, 32, 29, -8, -79, -24, -10, -103, -24, 115, -34, 8, + -24, -79, 7, 78, -80, -21, -69, 16, -90, 10, 59, -95, -78, 58, -127, 5, + 11, 1, -32, -5, 55, 18, -11, -56, 113, -114, 71, -46, -11, 6, 10, 20, + 16, -127, -16, 39, 3, 73, -63, 16, -36, 69, 46, 16, -16, -38, 85, -36, + 16, -20, 9, -85, 33, 93, 40, -29, 43, 30, -3, -1, -29, -71, 59, -16, + -37, -40, 29, -6, 0, -28, -13, -21, 64, -10, 19, 59, -42, 41, -36, -16, + -43, 61, 34, -44, 34, 35, 21, 34, 31, 46, -93, 35, 30, 115, 13, -108, + -106, 4, -21, 19, 3, -53, 15, -28, -29, -55, -1, -3, -41, 40, 12, 6, + 18, -13, -37, 100, 24, -5, -40, 38, -25, 46, -18, -26, -41, 127, -27, 43, + 65, -28, 23, -17, -48, 10, 17, 69, -87, 48, 13, 20, -42, -3, 24, 29, + 17, -2, -32, -42, 60, 43, 2, -2, 93, -43, 26, -92, -127, -22, 20, -40, + -121, 45, -38, -28, 33, -104, 67, -29, 76, -122, 55, -66, -64, 4, -29, 92, + -80, 13, 72, -26, 7, -18, -67, -50, 18, 57, 87, -17, -18, -21, -57, 3, + 20, -11, 26, -92, 42, 63, 99, 124, 80, -62, 54, 46, -60, 86, 22, 36, + -3, -49, 46, -56, 28, -23, 111, -81, -60, -2, -111, 27, 98, -8, 127, 12, + -12, -74, -31, -89, 6, 5, 12, 63, -11, -43, 0, -87, 27, 32, 16, -59, + -1, 71, 3, 48, 50, 27, 13, -25, 35, -6, 17, 15, -7, -5, -3, -34, + 3, -3, 23, 26, -42, -25, 15, -10, 3, 12, -16, -6, -8, 8, 3, -10, + 21, -54, 39, 31, -36, -77, -65, 25, -24, -69, -53, 61, 39, 92, -127, -4, + -35, 43, 19, 75, 12, -3, -50, 41, 98, 29, 40, -83, -103, -29, -78, -11, + -29, 50, 78, -17, 49, -3, 35, 6, 54, 7, -78, 98, 57, -81, 49, 24, + 17, 53, -39, 73, 23, 77, 77, -66, 24, 62, 47, -52, -53, 49, -39, 30, + -106, 28, 127, 26, 14, 87, -56, -37, 21, 19, -25, 46, 78, 20, -36, 5, + 39, -75, -24, -21, -102, -39, 7, -24, 20, 37, 42, -7, 23, 51, -32, 60, + 104, -44, -86, -33, 10, -107, -49, -10, 28, 42, -90, -105, -38, 15, 26, 48, + -28, -64, -43, -87, 107, 81, -64, -11, -29, 17, 24, 63, -95, -46, 127, 69, + 77, -22, 7, -64, -72, 19, -39, 24, 22, 46, 74, 52, 32, -60, -32, -49, + -18, -15, 7, -28, 9, -14, -83, 66, -18, -10, 19, -51, 14, -21, 31, 39, + -32, -43, -69, 44, 92, 39, 10, -22, 127, 72, 28, 5, 44, -21, -3, -30, + 23, 78, -46, 46, 77, 0, -13, -1, -59, -29, -16, 41, 26, 6, 49, 25, + 33, 59, 59, 44, 34, 95, -5, 42, 1, 4, -46, 72, 105, 1, -9, -54, + 19, -14, 41, 66, -16, 15, -81, 36, -79, -92, 84, -97, -12, -24, 13, -7, + -45, 110, -53, 17, 2, 21, -78, -70, 9, -36, -30, -39, -79, 58, 100, -30, + 64, 29, -90, 65, -4, -10, -44, 17, -67, 11, -13, 26, -43, 37, -22, -48, + -78, 80, -2, -113, -61, 58, -103, 6, 127, 79, -21, -12, -73, 89, -123, -58, + -83, -43, 32, 15, 38, -47, 55, -50, 22, -64, 103, -63, 36, -60, -5, -78, + 44, 85, 10, 56, 2, 30, -59, 14, 46, 4, 12, -23, 12, -49, -3, -25, + 118, 72, 59, 22, 2, -17, 5, -20, 55, -6, 127, 58, -77, 62, -24, 68, + -117, -85, 61, 1, 21, -77, 55, 59, -67, 2, -106, -63, 83, 7, 61, -68, + 48, -51, -17, -42, -1, -5, -6, 88, 25, 113, -122, 5, 44, -10, -2, 96, + -54, 15, 9, 53, -80, -70, 2, -107, -69, 17, 59, 40, -21, 78, -22, -7, + -58, -37, -41, 36, -16, -40, 30, 41, 45, 46, 15, -51, -67, 31, 33, 45, + -28, 127, 89, 35, 41, -65, 49, -38, -52, 54, 36, -27, 72, 21, 13, 54, + 10, -61, 70, -38, -48, 18, -7, 54, 34, 127, -13, 69, 21, 1, 46, 5, + 45, -26, 61, -19, -50, 14, -16, 34, 30, -48, 42, -36, -122, 70, -27, 79, + 22, 29, 4, -11, 42, -15, -70, -76, 50, -13, 69, -29, -90, -31, 14, 48, + 16, 80, -37, 17, -3, 15, 67, -5, 117, 40, 76, 30, -89, -21, -38, -35, + -21, -35, 90, -83, -30, -16, 33, 15, -10, -46, -105, -8, 60, 81, -98, 86, + 9, -40, 16, -10, -12, -52, -43, -21, 10, -44, -2, -23, -45, 21, -28, 4, + 99, 26, -51, 4, 127, -15, 5, -55, -3, -72, 65, 6, -52, 87, 34, 64, + 5, -35, -87, -22, -56, -2, -10, -125, 2, 51, -77, 22, -60, -40, 81, -93 +}; + +const int32_t Pw2Bias[64] = +{ + 3761, 5207, 1356, -601, -2246, -261, 1593, 3146, + 2184, -880, 7234, -192, 3418, 1687, 1330, 1490, + -37, 1935, -640, -424, 4489, 166, -1576, 321, + -2113, -1546, 1281, -3334, 6576, -1307, 303, -3378, + 1438, 882, -3, -1055, 4150, -543, 2775, -1168, + -1797, 1384, -2660, 2333, 2654, -5403, -1892, -2402, + -144, -1943, 831, 4185, -178, 1293, -1654, 1931, + -4606, 2009, -5889, 4480, -830, -2512, -1319, 3903 +}; + +const int32_t Pw2Multipliers[64] = +{ + 1597508273, 1150297914, 1171513636, 1718373001, 2037991589, 2108126782, 1984092950, 1089894132, + 2080217152, 1932369341, 1579078316, 1497613969, 1374886725, 1152608863, 1861057433, 1351489849, + 1390177574, 1765477068, 2063859047, 1319910309, 1594059478, 1156407940, 2073570199, 1858445671, + 1305993498, 1366359040, 1584436583, 1325961130, 1373804707, 1811761377, 1286575197, 1940096898, + 1386250900, 1225586513, 1971060520, 2065990262, 1081191895, 1143866659, 1510407067, 1264220765, + 2126560979, 1269077126, 1402071607, 1568467000, 1885351961, 2056170279, 1209591097, 1746694709, + 1913874839, 1550900772, 1141495720, 1235645199, 1430607363, 1251607951, 1974841302, 1182084907, + 1938861293, 1433071192, 1079589597, 2016415613, 2078267163, 1096976169, 1958279514, 1157267823 +}; + +const int32_t Pw2Shifts[64] = +{ + -8, -8, -8, -8, -9, -9, -9, -8, + -9, -9, -9, -8, -8, -8, -8, -8, + -8, -8, -9, -8, -9, -8, -9, -9, + -8, -8, -8, -8, -8, -9, -8, -9, + -8, -8, -9, -9, -8, -8, -8, -8, + -9, -8, -8, -9, -9, -9, -8, -9, + -9, -9, -8, -8, -8, -8, -9, -8, + -9, -8, -8, -9, -9, -8, -9, -8 +}; + +const int8_t Pw3Weights[4096] = +{ + 67, 8, -91, 13, 65, 6, 54, 49, -27, -1, 19, -95, -18, 50, -119, -31, + 2, 14, 54, -12, -25, 47, -41, 47, 33, 6, 106, 51, 14, 24, -11, 41, + 90, -23, 57, -55, -15, 28, -51, -95, -15, -11, -70, -93, -18, 43, 48, 27, + 11, -23, -5, 32, 25, 4, -32, -44, -43, 127, -6, 18, 26, 5, 52, 10, + -15, 7, -1, 67, -15, -14, 65, 1, 27, -18, 35, -127, 20, 15, 27, -40, + -18, 72, -12, 5, -6, 5, 18, -33, -21, -28, -48, 47, -7, 8, -10, 47, + 6, 43, 16, 1, 15, 2, 57, 30, -29, -26, -84, -9, 2, -64, -90, 25, + -10, -39, -14, -10, -10, 6, 25, -20, -49, -2, -28, 46, -34, 20, -6, -20, + -79, -5, 95, -1, -99, 94, -99, 47, 20, 109, -62, -15, -64, 14, 98, -50, + 31, 17, -112, -30, 4, -16, -4, 25, 0, 28, -37, 28, -83, -43, -104, -40, + 19, -74, 127, 21, -32, -118, 2, -60, 27, -75, -32, -63, 48, 36, -81, -12, + 58, -38, 21, 16, 47, 1, 61, 6, 7, 60, -85, -96, -16, -3, 62, 33, + -43, 48, -52, 78, 20, -28, 91, 84, 103, -1, 58, 45, 67, 127, 15, -36, + -24, 20, 42, 80, -16, 75, -60, -22, -45, -11, 12, -61, -16, 15, -57, -93, + 11, 25, 10, 7, 13, 37, -6, -69, -108, 85, 27, -59, -22, 22, 4, 40, + 120, 13, -24, 57, 127, -64, 17, -15, 100, 68, -39, -79, 44, -24, -17, -37, + 69, 17, -11, -56, -35, -10, -127, -97, -53, 30, 17, -10, 19, -88, -60, 35, + 6, 31, -9, 58, 25, 42, 13, 81, 36, -39, 46, 72, 98, 46, 34, -19, + -24, -29, 22, 42, -23, -21, -9, -9, 109, -55, 98, 31, 32, -44, -22, 51, + -38, 34, 72, -17, -102, -43, -25, 6, 18, -67, 10, -38, 24, -90, 35, 28, + -9, -7, -31, -48, -42, -72, -13, -52, -9, -20, 121, -40, 47, -14, -26, -38, + 6, -15, -38, 42, -11, 32, 39, 43, -61, -72, -24, 26, 89, 12, -83, 24, + 34, 20, 9, 68, 94, -40, 30, -22, 3, -31, 29, -61, -1, 1, -24, -6, + -80, -8, -88, -35, 1, -127, 73, 28, 26, 12, 38, 24, -42, -60, 55, 11, + 20, -50, -83, 46, -1, 11, -64, 22, 59, 27, -31, -9, 7, 43, -87, -21, + -23, -50, -87, 40, 37, -7, -55, -5, 54, 22, 66, -1, 40, -2, -127, -4, + 31, -30, -41, 22, -24, -63, -25, -63, -9, 39, 38, -68, 7, 49, 90, -20, + -44, -6, -7, -20, 34, 41, -34, -23, 11, 120, -5, 8, 51, -109, 29, -13, + -2, -50, 81, 56, -29, -40, -46, 104, 1, 52, 22, 32, 57, -74, 3, -26, + 13, -44, 15, -1, -37, 19, -4, 51, 82, 4, 0, -27, 54, 45, 79, -30, + 2, -51, 26, 34, 5, -71, 16, -24, 18, -11, 20, 73, 11, -28, 4, -38, + 49, 28, -24, 17, -59, -70, -127, 87, -18, -26, -19, -74, -1, -12, 66, -21, + -58, -11, 15, 5, 35, -59, -43, 22, 85, 29, 12, 21, 82, -10, -1, -39, + 3, -8, -31, -19, -29, -33, 12, -36, -11, 38, -53, -127, 111, 37, 8, 46, + -18, 3, -72, 6, -37, 10, 43, -13, -37, 25, 62, 17, 16, -60, 51, 15, + -69, 8, 57, -16, 15, 22, 24, 110, 23, 5, -90, 8, -18, -12, -30, -18, + -77, 16, 41, -6, 80, 2, -127, 18, 25, -23, -43, 40, -5, -116, 79, 87, + -34, -108, -18, -72, -20, -37, 10, -58, -21, 81, -9, -23, -52, -73, -22, -3, + -48, 15, -25, -39, -9, -67, 5, 53, 1, -79, 26, 46, 90, -45, 43, -23, + -55, 59, -2, -67, 3, 30, 74, 21, -29, -62, -4, 68, 23, -46, -14, -27, + -9, -104, 47, 45, 43, -24, -78, -40, -28, -1, 39, -82, -32, -61, -2, -62, + -32, 9, -11, 16, -31, 24, 41, 3, -39, -67, -62, -96, 90, 77, 53, 58, + 6, 25, -21, 61, -31, 17, 49, -34, -43, -120, 50, 40, 45, -113, -72, -60, + -40, 4, -15, 27, -52, 13, 62, 127, -15, -73, -60, -68, 10, -52, 28, -1, + 24, 23, -62, -39, -17, 27, 5, 69, 92, -35, 38, -123, -38, 115, -72, 66, + -48, -52, 45, -10, 55, 65, -103, -56, -53, 46, 38, 12, 10, -18, -58, 26, + 58, -30, -51, 19, 24, -9, -64, -88, -104, 7, 70, -49, 23, 49, 86, 14, + 39, -10, -127, -54, 30, 49, -5, -37, -56, 90, 3, -3, 18, -40, 4, -36, + -62, 23, -10, 46, -85, -32, 98, 111, 111, 1, 127, -56, 81, 22, -31, -2, + 25, 4, 8, -45, -52, -15, -17, -16, -8, -75, -9, -11, 46, 62, -23, 102, + 28, -3, -24, 20, 38, 47, -12, 5, -6, 10, -9, -32, 9, 14, 3, 10, + -37, 11, -27, 9, -3, -58, -32, 13, 16, 4, -30, 57, -4, 55, -44, -38, + 10, 41, -47, -28, -13, 20, -41, 8, 32, -31, -14, -38, -25, 86, 38, -19, + 29, -36, -43, -27, 34, 11, -6, 11, -17, 1, 16, 30, -38, -49, -43, 1, + 35, -24, 52, -49, 42, 6, 12, 28, 23, 9, 8, -1, -18, 16, -4, -15, + 75, -6, 5, -15, 127, -9, 26, -40, -41, 60, 27, -18, -38, 10, -26, 29, + -88, 28, 4, 49, 4, 59, -80, 22, 7, -70, -25, 68, -20, -13, 111, -26, + -21, -43, -47, -25, 20, -32, -9, -46, 46, -38, -62, 43, -68, -10, -127, -67, + -53, -54, 10, 30, -23, -4, 46, 16, -12, 24, -38, 1, 99, -22, 47, -58, + -4, 20, 36, 17, 62, 87, 117, -17, -9, -49, -28, 71, 20, -5, 20, 32, + 4, 52, -29, 49, -3, 48, -9, -15, 44, -26, 2, -53, 48, -1, -38, 50, + 45, -7, -64, -21, 59, 7, -11, -7, 86, 40, 5, 115, -95, -48, -68, 127, + 5, 17, 10, -37, 73, -48, 32, 7, 50, 38, -7, -39, -31, 83, 86, 28, + 39, 17, -35, -95, -32, 17, -48, -76, -59, 108, 19, 87, 20, 73, -45, 48, + 64, 34, -41, -27, -110, 45, -71, -13, -43, -91, 45, -90, 16, 114, 53, -80, + 84, 14, -35, 48, 2, -81, 103, 97, 27, -127, -18, 73, -97, -41, -126, -48, + -2, 47, 57, -50, 73, 64, 5, 32, 50, -17, -75, -7, 32, 38, -80, 82, + 43, -48, 37, -12, 101, -44, 72, -101, 22, 54, 62, -70, -7, -37, -72, 71, + 46, -1, 36, 25, -127, 11, 9, -20, 53, 10, -59, -49, 3, 30, -60, -40, + 6, 70, -8, 9, -52, 18, -10, -4, -33, -27, -47, -82, 77, 17, 25, 80, + -47, 8, -54, -19, -82, 49, -2, -48, 105, -12, -70, 106, -27, -43, -18, -57, + 28, 20, 64, 4, -17, 18, -42, 55, -22, 4, -2, -15, 42, 19, -89, -13, + 71, 34, -28, 35, 72, -36, -44, -17, 27, -32, 68, -80, -16, -28, 19, 21, + -25, -75, 67, 103, -67, -41, 5, -10, -95, 113, -19, -107, 39, 49, -12, 111, + 55, 44, -51, -84, 31, 65, 71, -59, -78, -63, -12, 78, 47, -74, -28, 24, + -79, -8, -39, -34, -101, -3, 74, 127, -127, -62, 19, 77, 25, 52, 2, -14, + -3, -16, 127, -26, 34, -9, -97, 51, -64, 39, -4, 71, -50, -73, 66, 46, + 68, -32, 97, 106, 17, 9, 22, 24, 34, -11, 22, 45, -15, 2, 3, -118, + 22, 15, 84, 8, 7, -22, -66, -56, -10, -29, -25, 8, 47, 5, -55, 11, + 23, -18, -50, 86, -11, -40, -27, -4, -57, 18, -26, -55, -37, 32, 10, 6, + 30, -15, 38, 3, 46, -125, 20, -2, 12, -44, -1, -73, 37, -13, -19, -13, + -8, 103, 112, 62, 22, 27, 23, 7, -22, 16, 26, -14, 127, 94, -54, -59, + 11, -12, -32, -2, -52, 12, 23, -27, -59, 12, 5, -46, 56, -6, -36, 22, + -80, 42, 10, 82, -55, 4, -10, 9, 57, 0, -35, -47, 29, -7, -22, -62, + -79, -15, 53, 77, -7, 60, -42, 19, 98, 59, -16, -75, 7, 108, -10, 1, + -2, -44, -77, -42, 12, 88, -82, -1, -12, 75, 10, -83, 127, -36, -1, 103, + 78, -76, 4, 68, -34, 52, 98, -91, -33, -40, -28, 50, -1, -71, 67, 67, + 95, 2, 80, 35, 66, 4, -84, 47, -2, -34, -99, -83, -94, -41, -15, 58, + -47, -6, -57, -26, 53, 33, 47, -97, 69, 16, -19, -62, 18, -9, 57, -28, + 41, 127, -30, 36, -1, -30, 32, 53, 21, -69, 8, -36, 8, 2, -63, -67, + -33, -23, 61, -16, -30, 9, -15, -41, 17, 22, 15, -22, 42, -39, -38, 42, + -8, 40, 52, 60, 56, -17, 80, -50, 91, 27, -57, -64, 9, -69, 34, -21, + 2, -20, 83, 38, 29, 26, -83, 4, -54, -57, 36, 3, -8, -92, 28, -84, + -12, 52, 127, 69, -91, -72, 84, 33, -10, -42, -55, -35, 58, 39, 76, -18, + 2, -54, 51, 27, 33, 2, -10, 11, 68, -91, -18, 58, 14, -78, -66, 15, + 9, 32, -15, 26, -24, -100, -35, 10, -79, -42, 13, -60, -65, -18, 28, 31, + -13, -77, 29, 49, -38, 58, -78, 97, -34, 13, 40, 74, 10, 28, 41, -40, + -18, -68, -79, -50, 87, -5, -50, -20, 66, 8, 17, 63, 39, 41, 29, 15, + 13, -60, -34, 20, 47, -42, -33, -33, -16, -23, -43, -59, 99, 15, 23, 34, + 43, 35, 9, 34, -8, 44, -40, 53, -21, -22, -29, 45, -3, -42, 127, 82, + -56, -38, 31, 37, 4, -36, -53, 16, 127, 28, -15, -58, 11, 11, -24, -8, + 45, 50, -24, -16, -31, -17, -24, -27, -2, 49, -16, -55, 10, -13, -4, 28, + 1, 45, -1, -38, -23, -19, -21, -9, -47, 41, 29, 21, 37, -8, -29, -35, + -29, 11, -11, 37, -28, 38, 8, 15, 7, 25, -71, 8, -30, 0, -24, -11, + 37, 4, -37, 28, 51, -54, 23, 3, -37, -43, 6, -53, 54, -26, -73, -30, + 38, 63, 25, -4, 18, -25, -18, 3, 70, -19, 89, 2, 127, 77, -21, -6, + 27, 29, 2, 47, -4, -3, -12, -84, -37, 16, 6, -39, 44, 39, 25, -23, + 0, -5, -23, 11, -5, 32, -29, -9, 62, -12, -3, -10, -22, -49, 76, -30, + 27, 52, 76, -127, -38, 91, -70, 14, -27, -7, -70, 93, 46, 23, -70, 93, + 7, -74, -92, 70, -29, -16, -39, 73, 74, 65, 40, 57, -113, -89, 23, 9, + -38, 73, 105, 4, 92, -98, 50, 22, 65, -15, 75, -22, 32, -7, 12, -33, + 17, -81, -42, -17, -50, -84, -41, -60, 13, 94, 0, -6, 51, -13, 15, 84, + 56, -18, -36, 127, 75, -19, 83, -58, -101, 9, -7, 13, -10, -4, 30, 70, + 27, 21, 64, 88, -29, 7, 49, -41, -31, 4, 36, 17, 68, -14, 65, 65, + 85, -14, -44, 4, -22, 49, 43, -19, -56, -86, -94, -42, 44, 21, 34, -29, + -2, -62, 31, -18, -59, 127, 93, 58, -69, -23, 53, 20, -13, 77, -25, 56, + 1, -9, 62, 2, -26, -41, 47, 115, 27, -52, 42, 54, 59, 5, -44, 37, + 20, -51, 110, 65, 44, -82, 0, 4, -5, -52, -44, -17, 18, 127, 17, 9, + -60, 33, 22, 33, 18, -29, -25, 37, 36, 37, 22, 8, 50, -9, -30, -16, + -19, 65, -38, -14, -34, -9, -43, 89, 80, -69, -31, 58, 38, -24, -7, -34, + -43, -13, 62, -55, -54, -21, -79, -64, 43, -11, 57, -37, 19, -13, 4, -35, + -13, -39, -43, 47, -48, 33, 51, 39, -23, -56, -63, -24, 47, 4, 1, 52, + -7, 23, 26, 31, 33, -47, 52, 15, 35, -44, 61, 22, -23, -33, -50, 27, + -50, -37, -12, -40, 3, -127, -21, 32, -18, 8, 31, -42, 12, -48, 32, 15, + 4, -2, 127, -32, -51, 20, -74, 34, 25, -48, -52, 63, -16, -94, -8, -2, + -2, -120, -39, -34, 12, -47, 42, 16, 110, 38, -44, -19, -45, 2, -27, -7, + -45, 12, -38, -44, 28, -59, -50, 62, 102, 35, 61, 66, 23, -9, 32, -4, + -49, 4, 39, -69, 9, 7, -59, 31, -33, -36, 13, 74, -14, -35, 43, 23, + -27, 3, 41, 96, -10, 6, 42, 84, -21, 15, -17, -15, 42, -17, -3, 10, + -42, -47, -70, -19, 35, -1, -28, -18, 127, -42, 42, 69, 59, 3, -15, 50, + 21, -84, 18, 34, -7, -7, -45, 1, -4, 11, -79, 4, -10, 62, 63, -24, + -17, 43, 14, -17, 16, 60, -71, -17, -24, 54, -3, 42, -14, -22, 23, 81, + -12, -65, -36, -6, 11, -13, -88, -31, 93, 36, -24, -32, -8, -74, -51, -40, + -38, 67, 111, -11, -24, 7, 98, 20, -73, 35, 41, -71, 8, 27, 60, 57, + -73, 54, -23, -127, -39, 3, -1, -52, -53, 13, 41, 86, 69, -45, -9, 24, + -63, 65, -56, -53, -123, 42, 14, 9, -17, -18, 50, 2, 25, -50, -84, -87, + 0, 5, 25, -4, 19, 10, -18, 58, -16, -67, 38, -66, -35, -23, 58, 32, + -9, -54, 127, 38, -7, 6, -47, -39, -7, 58, -20, 2, -67, 21, -2, -56, + 34, -11, 47, 34, -37, 51, 11, 16, -67, -33, -32, -1, 30, -47, 4, -1, + -16, 6, -9, 31, -2, 42, 29, 23, -87, -20, -48, 4, 37, 39, -67, -3, + -53, 9, -117, -10, -54, 16, -15, 73, 85, -41, 8, -31, 10, 127, -101, -28, + -2, 49, -78, -33, 26, 36, -79, 41, 20, 69, 41, 15, -17, 41, -86, -31, + 21, -42, -76, -36, 12, 0, 4, 9, 17, 90, 60, -64, 61, 99, 47, 40, + 64, -34, 9, -31, 62, 45, -19, -86, 13, 29, -16, 47, 40, 10, 45, -8, + 62, -29, -18, -8, 20, -118, 46, -121, 92, 31, -19, -74, 15, -53, -101, 75, + 41, 118, 12, 1, 6, 58, -8, 88, -117, 10, 11, 32, 1, 38, -108, -11, + 11, 86, 3, -56, 36, -93, -71, -69, -65, 6, 47, -95, 70, 85, -127, 10, + 32, -25, 30, 14, -32, 25, -32, -17, 42, 127, -56, -26, 49, 69, 86, -43, + -50, -19, -28, 19, -33, -31, -49, -43, 51, -9, -23, -21, 10, 23, -31, -28, + 45, 9, 4, 3, 48, -36, -62, -28, -34, 21, -8, -58, -6, -17, -106, 11, + -37, 54, -11, 27, 45, -46, 6, -70, -64, 35, 101, -68, 69, 49, 47, -32, + 42, -8, -30, 27, 2, 51, -13, 7, 38, 127, -37, 18, -23, 17, 44, -46, + 42, -40, -35, 48, 107, -16, -74, -65, -49, -9, 30, 65, 13, 2, 49, 14, + -48, -34, -38, -6, 21, -19, -30, 6, -48, -13, 25, 51, 74, -9, -3, 31, + 44, -21, 81, 56, 28, -31, 79, -4, -21, -27, -30, -63, 60, 23, 28, 13, + 38, -13, -19, 30, -32, 9, 127, 8, 8, -5, -51, -33, -71, -25, 8, 25, + -11, -106, 116, 36, 73, -109, -94, -30, -8, -12, 6, 48, -48, -60, 36, 34, + -73, -39, -8, -12, -22, 56, -47, -55, -73, 25, -10, -76, 74, 29, -41, 42, + 16, -72, -60, 58, -53, -59, 9, 27, -45, -127, 103, 63, 88, -87, 44, -31, + -103, 26, 5, 65, -43, 119, 24, 119, -25, -49, 4, -24, 0, -102, 24, 60, + 20, 13, 43, -29, -21, 28, -67, -27, -18, -31, -43, 11, 25, -86, 17, -12, + -32, -47, 55, 12, -31, -39, 99, 14, 96, -56, -60, 13, 20, 44, 61, -29, + -68, 16, -16, -48, 57, -9, -15, -14, 127, 41, 8, 50, 66, -69, -17, -37, + 28, -20, 43, -23, -25, -20, 1, 7, -39, -82, 28, 59, 12, -37, 38, -17, + -54, -26, -4, -35, -108, -3, 68, 45, 127, 49, 14, 30, -9, 86, -52, -4, + 51, 94, -21, -21, 23, 28, 0, 23, 48, -29, -27, -38, 12, 46, -47, -60, + 18, -47, -76, -8, -69, -4, -63, -8, -13, 104, 35, 16, 53, 72, 28, 50, + -13, 27, 1, 8, 38, -10, -32, -32, 89, 42, -32, -43, -8, 47, -10, 48, + 5, -30, 97, -4, -13, 10, -95, 43, -74, -5, -26, 40, -28, -37, 109, -90, + 44, 31, 69, -8, 27, 5, 14, -12, 45, -6, -2, 81, -65, -54, 3, -62, + 26, -15, 127, -48, 8, -18, 9, -67, 43, -3, -57, 36, 112, 19, -49, 5, + 21, -11, -26, 50, 18, 12, -49, -19, -14, -6, -30, -60, -14, -45, -1, 54, + 13, 50, -22, -45, -9, -27, -7, -38, 95, -33, 61, -9, 9, -11, -14, 27, + 0, 12, 73, 91, -52, -27, 127, 91, -74, -3, 5, 1, -27, -5, 6, -58, + -41, 66, 1, -77, 68, -79, -1, 25, -19, -45, 34, -11, 71, 5, -97, 19, + -32, 38, -24, -9, 1, -95, 95, -33, -48, 7, -18, -18, -5, 20, -64, -64, + 85, 69, -33, -12, -77, 6, -65, -8, -101, -22, 10, 49, -32, 39, -17, -51, + 38, -17, -23, 52, -73, 0, -16, 100, 50, -74, 21, 30, -43, -5, 11, 26, + 56, -39, 89, 82, 12, -7, 117, -21, 45, -72, 46, 52, -36, 67, -13, 95, + 9, -57, 24, -12, 91, -91, 36, 18, -57, 41, 98, -126, -8, -82, -40, 127, + -21, -53, -68, 12, -61, 60, -3, 12, -68, -47, -36, -36, -51, 68, -28, -25, + -1, 49, -73, 10, 110, 45, -25, 22, 26, 101, 83, 12, 11, -44, -127, -60, + 55, 1, -7, 23, -24, 65, -38, -88, 37, -11, 4, -87, 88, 26, 123, -32, + 47, 30, -2, 10, 44, 127, -26, -99, 71, 7, -62, 27, -26, 54, 71, 68, + -1, 3, 19, 36, -75, 94, -54, 120, 94, -12, -22, 82, 66, 5, -49, 31, + 30, -127, 98, 18, 35, -18, -41, 10, 79, 61, -13, -16, -32, 1, -12, 24, + -33, -48, -14, -89, 49, -11, 32, -28, 71, -35, 59, 80, -13, -31, 80, 9, + -8, 27, 40, -57, 67, -51, -123, 28, -11, -42, 94, -10, -51, -12, -10, 28, + -69, 35, -44, 40, 44, -40, 5, -8, -1, -46, 40, 44, -37, 102, -3, 40, + -15, -50, -15, 45, -32, 49, -127, 55, -11, -24, 118, 44, -55, -29, -52, -117, + 63, 25, 120, 48, -9, -49, -6, -56, -74, 89, -38, -108, 112, 24, 70, 28, + 53, 50, -65, 92, 5, 118, -3, -74, 42, 104, -92, -30, 14, -14, -22, -16, + 18, 37, -94, -53, -23, 55, -69, 68, -35, 42, 127, 13, 42, 89, -51, -83, + 29, -22, -80, -54, 34, 39, 21, 78, -70, -75, 37, 53, -60, -16, -53, 69, + 45, 10, 72, -37, 71, -31, 52, -74, -50, -67, -12, -113, 27, 49, 42, 20, + 39, -46, -56, 21, 97, -41, 32, -10, 51, 84, -3, -10, -21, -85, 22, 61, + 69, -4, -51, -12, 18, 32, -37, -13, 12, -7, -62, 62, -55, -20, 25, 29, + -44, -72, -71, -105, 59, 53, -24, 32, 76, -33, 7, 127, -53, -39, -113, 1, + 5, 28, 73, -3, 40, -63, 27, -39, 20, 28, -61, -79, 54, 6, 32, -77, + -37, -35, -16, 38, 7, 55, 68, -40, 12, 82, -43, 117, 31, 14, 30, 5, + -5, -28, -2, 15, -42, -6, 31, -9, -26, -18, 15, -55, 53, 56, -55, -39, + 34, 127, 10, -75, 19, 11, -15, 21, 50, -40, 30, -19, 55, 23, -13, -45, + 9, -44, -3, 14, -59, 2, -52, -9, 49, 37, -1, -13, 20, 30, 23, -3, + -39, -14, 14, 51, 21, -7, -42, -73, 60, -1, 20, -43, -6, -12, 0, 17, + -7, 4, 50, 62, 45, -37, 47, 3, -24, 21, 28, -45, 2, -9, 0, -13, + -2, 127, 91, 14, 31, -18, 1, -21, -2, 35, 11, 18, 41, 36, 38, 57, + -9, -24, 17, -8, -15, -6, -53, 19, 7, -50, -101, -36, 47, 17, -34, 17, + 51, 5, 3, 63, -55, 46, -20, -22, -11, -9, -60, 38, -73, 73, -7, -44, + -71, 50, -6, 40, 3, 58, 49, 46, 73, 54, 35, -67, 70, -48, 90, 53, + -58, -43, -37, -46, -21, -63, 47, -63, 2, -62, -92, 35, -59, -9, 29, 127, + -55, 29, 19, 9, 42, -19, 24, 2, 21, -16, 23, 1, -16, -26, -9, -38, + 13, 74, 14, -70, -36, 2, 90, 0, 33, -37, -15, 117, 28, 4, -60, 23, + 44, -27, -4, 57, 41, -59, 71, 29, 18, 30, -13, -31, -56, -60, 60, 15, + -13, -64, 76, -48, -33, 39, -24, -92, 0, 13, -34, -77, -32, 10, 7, 31, + -11, -59, -60, 13, -25, 75, -54, 44, 31, 11, -66, 59, -1, 28, 35, 14, + -41, 28, 31, 23, -9, 127, 21, 10, -52, 13, -19, 68, -8, 49, -75, 1, + 21, 32, -22, -29, 4, -2, -36, -79, -33, 49, -44, -18, 1, -48, 34, -30, + 20, 38, -44, -10, 2, -4, 61, 28, 30, -33, -82, -11, -18, 11, 8, 27, + -42, 54, 2, -6, -29, -20, 9, -12, 127, -19, -43, 0, 18, -31, -11, -51, + 14, -2, 58, -44, -4, 47, 90, 11, -54, -5, -1, 34, 77, 4, -28, 12, + 4, 56, -14, 59, -80, -4, 35, 56, 125, -20, 11, 29, 68, 6, -29, -40, + -79, -18, -25, 17, 22, -80, 70, 18, 43, -38, -83, -35, 77, 37, -70, 88, + -33, 10, -43, -31, 20, 78, -28, 38, 127, 53, -26, 78, 28, -20, 27, 44, + -16, 20, 88, -97, 10, 13, -20, 58, 62, -26, 34, 65, -15, 41, -111, -29, + -17, 23, 31, -8, -53, 18, -23, 30, 53, 34, -13, 54, -27, -21, -4, -13, + 21, -19, -15, 9, 28, -23, 7, 11, 36, -50, -26, -7, -13, -17, -2, -13, + -27, 8, 10, 2, 3, 31, -12, 34, 127, 16, -36, 44, 14, -22, 27, -33, + 19, 33, 22, -59, 13, -39, -27, 10, -11, -45, -9, 3, 22, 29, -31, 46, + 83, -12, -68, -67, -34, 5, -71, -59, 39, -66, -43, -54, -33, 28, -49, 21, + -45, -45, -21, 34, 66, 62, -41, 54, -3, 14, 39, 78, -77, -34, -2, -70, + 42, -5, 14, -52, 28, -35, 64, -55, 43, -27, -36, -49, -45, 44, 49, 45, + 20, 43, -24, -3, 62, 69, 47, -101, -61, 127, -27, 0, -2, -13, 26, -28, + 58, 21, 127, 4, -73, -35, -24, -5, -37, 57, 5, -18, 18, -88, 11, 82, + -56, 38, 81, 24, -2, -39, -62, 25, 48, 66, 0, -26, 80, 44, 54, -8, + -48, -65, -21, -68, -59, -15, -114, 25, 112, -3, 1, 94, 65, -55, -67, -47, + -46, -17, 40, -57, -57, -57, -76, -64, -78, -68, 10, 58, -37, 76, 38, -26, + 43, 23, -66, -32, 40, 38, -37, 45, -10, -29, -31, 1, 27, -11, -121, -25, + -13, 24, 6, 29, 75, 79, -27, 27, 63, 2, 44, 77, -53, 0, -49, -9, + 4, -29, -52, 52, -12, 8, -39, -14, 68, 28, -25, -65, 43, 82, 47, 3, + -56, -4, 50, 10, -24, 51, 5, -91, 0, 24, 56, 127, 12, -18, 38, 82, + 71, 25, -12, 88, -20, 6, -51, -29, 12, -3, -26, -79, -19, 13, 63, -64, + 21, 68, -44, -33, -55, 42, 31, -58, 35, -58, -79, 69, 21, -24, 45, 94, + 10, -79, 48, 61, -38, 52, 99, 44, 83, -34, -127, -19, -68, -79, -76, 12, + -20, -76, 0, -60, 58, 0, 26, -7, -51, -32, 32, -34, -17, 53, -25, 65, + -110, -85, 38, 53, 41, 23, 23, 77, 74, 49, -17, 36, 77, -9, 35, 3, + 36, 60, -62, -30, 23, -3, -32, -56, 54, -9, -65, -61, 72, 99, -11, -43, + -30, -12, -21, 74, -46, -24, -5, -44, -32, 66, 43, -27, 61, -53, -34, -38, + -6, 10, 81, 0, 71, 0, -20, 81, 127, -60, -62, -9, -1, -28, 81, 18, + -53, 7, 5, -20, 23, 84, -19, 127, -81, -54, -15, 22, -78, 49, -9, 74, + 9, -54, 74, -14, -4, 15, -113, 26, -19, 123, 48, -10, -47, -18, -4, 22, + 27, -38, 25, 0, 3, -44, -26, -39, -56, -38, -37, -26, 86, 36, 24, -1, + 105, 14, -90, 66, 0, 69, 2, -59, -3, 27, -55, 31, -10, 34, 47, -27, + -41, -37, 82, -38, 5, -5, 0, 1, 43, 15, 6, -62, 18, 46, -63, 45, + -2, 29, -18, 9, 36, -4, -55, 16, 31, 14, 55, 46, 59, -33, -65, -2, + 14, 20, 2, -10, 37, -77, -57, -89, -7, 28, 43, -55, 64, 70, -8, -54, + -13, -3, -50, 48, -8, 16, -95, -67, -3, 72, -58, -41, -92, 3, 127, -1 +}; + +const int32_t Pw3Bias[64] = +{ + -2241, 320, 4492, -2330, 155, 1546, 840, -1179, + -568, 2921, 2327, 591, -3189, 201, 139, -1487, + 2109, 356, -542, 144, -310, -1824, 2441, 1720, + -4507, 1124, -2457, -279, -4350, -3238, 2539, 539, + -4345, 3273, 812, -926, 2821, 1276, -863, 1786, + -572, -1756, 198, 4091, -558, -2881, -1277, -1196, + -1087, -756, -68, -2056, -951, -1289, 1052, -3196, + -1230, 2455, 304, -1894, 1051, -2293, -1029, -717 +}; + +const int32_t Pw3Multipliers[64] = +{ + 1983574130, 1659815286, 1397699647, 1142058250, 2005847645, 1092435325, 1149804914, 1907098021, + 2116646946, 1669883586, 1748834732, 1747857353, 1376348244, 1823292477, 2146130598, 1124853039, + 1739028655, 1694012551, 1557143424, 2121531597, 1105238000, 1815371674, 1886230872, 2029042418, + 1803914647, 2121305185, 1434008598, 1942825869, 2065699219, 1738938326, 1224050891, 1636930521, + 1245020664, 1336968186, 1880743736, 1168234120, 1731018592, 1329535699, 1915273244, 1434514338, + 1622869109, 1114863203, 1885817813, 1710378069, 1813910576, 2022679992, 1663488449, 1877399288, + 1647128383, 1262339188, 1400294839, 1303358546, 2016143320, 1933989154, 1894666501, 1298397140, + 1226167602, 1193484671, 1428863327, 1767407397, 2073427389, 1902807515, 2002076883, 1102973127 +}; + +const int32_t Pw3Shifts[64] = +{ + -9, -8, -9, -8, -9, -8, -8, -9, + -9, -9, -9, -9, -8, -8, -9, -8, + -9, -9, -9, -9, -8, -9, -9, -9, + -9, -9, -8, -9, -9, -9, -8, -9, + -8, -9, -9, -8, -9, -8, -9, -9, + -9, -8, -9, -9, -9, -9, -9, -9, + -9, -8, -8, -8, -9, -9, -9, -9, + -8, -8, -9, -9, -9, -9, -9, -8 +}; + +const int8_t DenseWeights[896] = +{ + -64, 32, 86, -54, -68, -59, -127, 72, -45, 54, -11, -39, -57, -29, 32, -4, + 14, -62, -51, 118, -61, 46, -12, 82, 19, 49, -96, 54, -44, -15, 17, -22, + 32, -21, 20, -92, -7, -8, 2, 27, -11, -6, 120, 25, -21, -44, -6, 59, + 8, -26, -44, 54, 47, -9, -57, -125, 29, -71, 55, -106, -20, -13, 127, 50, + -44, 58, 60, -64, 26, 31, -94, -43, -75, 53, 15, -47, -18, 79, 90, 36, + 119, -88, 25, 57, -67, -78, 44, 101, -97, -50, -127, 89, 12, -74, 117, -65, + -109, 36, -17, -83, -5, -37, 93, -42, 16, -98, 54, 107, 65, -97, -79, -42, + 80, 44, -87, -49, 113, -85, 114, -71, 30, 62, -75, -33, 55, -122, -34, -80, + -3, 50, -11, 26, 7, -2, 2, 22, 96, -34, 39, 38, 62, -102, -127, -37, + -73, 8, 38, -44, 77, 20, 22, -13, -53, 84, 37, -53, -22, 11, 54, -85, + -50, 72, -57, 20, 83, 82, -5, 9, -50, 48, -116, 46, -52, -64, -58, -57, + 8, -125, -31, 24, -4, -61, -15, 7, -73, -65, -6, -29, -64, 20, -78, 33, + -42, 41, 32, -7, -63, -17, -69, -91, 79, 83, 71, 16, -79, 20, 91, -51, + 0, -86, 95, 7, 57, 2, 16, -10, -96, 43, -33, -84, 88, -27, -45, -102, + -86, 57, 97, -75, -20, 39, 84, 87, -19, -127, 28, 35, -34, 9, -34, 43, + -21, 5, -86, 20, 58, 77, 59, -67, -99, 14, -19, -80, -32, -31, 60, -51, + -48, 37, 67, 67, -70, -127, -49, -53, 40, -82, -57, -77, 14, 71, 42, -71, + 68, 83, -73, -66, -58, 38, 60, -67, -22, 28, -92, -41, -62, -17, -61, -24, + 18, -46, -32, 74, -52, 6, -82, -119, 6, 88, 36, 25, 41, 12, -21, -70, + -85, -62, 41, -14, 25, 47, 77, 81, 82, -40, -32, 7, 89, 60, -75, -44, + 1, -82, -3, -41, 22, -63, 28, 27, 27, -28, -67, -119, 41, -52, 85, 22, + -11, 17, -80, -43, -37, -56, 5, 8, 72, -77, 23, -66, -56, 78, -127, 74, + 26, -32, -62, 72, -87, -37, -45, -68, 79, 51, -29, 7, -31, -4, 17, -47, + -83, 78, 20, -19, 66, 13, 64, 92, 72, -23, 25, 74, -66, 69, 0, -62, + 18, -48, -48, -36, 105, 44, 78, 19, 78, -123, 107, -42, 21, -55, -101, -65, + -37, 15, 38, -87, 47, 100, 2, 10, 38, -66, 88, -23, 82, -90, 127, -62, + 56, -69, -56, -36, -41, -19, 99, 62, -34, -29, -95, -43, 93, -30, 2, -104, + 39, -70, 8, -74, -80, -70, -15, 22, -111, -42, -30, -26, 69, 25, -101, -12, + 55, -18, -26, 70, -47, -52, -127, 34, -85, -36, -76, 34, 22, 50, -60, -64, + 74, -18, 19, 84, 24, -40, -3, 78, -84, -57, 5, 12, 6, 68, -36, -29, + -42, 43, 64, -41, -27, -70, -74, -94, -1, -4, 67, 64, 60, -65, 61, 25, + 29, -91, 0, 17, -8, 23, -61, -5, 21, 21, 22, -19, -34, -90, 37, -81, + 21, 23, -8, -13, 40, -74, -75, -48, -56, -92, -17, -20, -24, 36, -68, 43, + 51, 59, -26, -7, 49, -55, 61, 32, -127, 10, -5, -29, 11, -32, -103, -41, + -12, 24, -9, -11, 33, -68, -45, -80, 25, 32, 37, 19, 44, 30, -44, -22, + -57, -15, 49, 35, -39, 42, 69, 42, 20, 31, 29, 33, 39, -60, -75, -76, + -30, -64, -14, -60, -14, -22, 43, 25, 74, 71, -36, 38, -27, -14, -93, 70, + -40, 40, 43, -28, -66, 33, -44, -45, -127, 61, -29, 51, 2, -52, 55, 29, + -14, 91, -38, -30, 27, 65, -9, 58, -9, 12, -62, 37, 5, -53, 71, -56, + -43, -65, -99, -62, -8, 52, 34, 34, 23, 39, 31, -57, -62, -82, -16, 31, + 89, -3, -16, -30, 20, -27, -8, -56, 14, -51, -84, 25, 12, 67, -15, -70, + 51, 69, 17, 19, 59, 91, -37, 3, 70, 65, 32, 50, -19, -76, -101, -85, + 48, -97, 19, 112, -77, -96, -15, 12, -104, -6, 70, -76, 30, 59, -38, 66, + 28, 127, 62, 38, -100, -56, -38, -56, 51, 43, -81, 60, -10, -74, 28, 16, + 49, -31, -87, -15, 0, 102, 26, 71, -20, -67, 66, 65, 51, 6, -27, -43, + 5, -89, -56, -20, -12, -70, -41, 49, 93, -28, 18, -9, -65, 8, 85, 62, + 92, -54, -93, 117, 0, 34, -78, 73, 41, 20, -39, -99, 17, 55, -57, 9, + 13, 19, 18, -127, -64, 30, -33, -58, -18, 12, 82, -2, -113, -3, 31, 51, + -13, 49, -53, 6, 20, 9, -99, -87, -104, 29, -41, 24, 49, -77, -68, 11, + 23, -9, -20, -10, 21, 14, -43, -127, 6, -18, 6, 9, 38, -80, -41, -23, + 55, 15, -47, -11, 66, -52, -86, 5, 4, -16, 19, -62, -18, 26, -63, -96, + -43, -60, 20, 45, 74, 65, 2, -32, 27, -34, 58, 36, 26, -6, 40, -47, + 46, -38, 68, 37, 1, -29, 65, -16, -21, -57, -29, 48, -11, 12, 28, 76, + -34, -30, -25, -4, -58, -6, 64, -57, 8, -34, -9, 80, 30, -37, -6, -42, + 19, -9, -44, 8, 34, 127, 55, -65, -31, 6, -10, 26, 33, 67, -16, 82, + 37, 34, -30, -13, 3, -33, 14, -54, -77, 41, -62, 43, -20, 29, 16, 38 +}; + +const int32_t DenseBias[14] = +{ + -889, 178, 1110, 615, 1219, -584, 261, 993, + 860, 573, -7111, -2707, 283, 4324 +}; + +const int32_t DenseMultipliers[14] = +{ + 1930280974, 1483222052, 1079150712, 1894453436, 1759545938, 1872103413, 1907311973, 1206328558, + 1237604101, 1233167241, 1697879513, 1978775023, 1833856616, 1872862821 +}; + +const int32_t DenseShifts[14] = +{ + -10, -10, -9, -10, -10, -10, -10, -9, + -9, -9, -10, -10, -9, -10 +}; + + const ConvLayerData Stem = { StemWeights, StemBias, StemMultipliers, StemShifts, { 0.5732762218f, 88 }, { 0.06965828687f, -128 }, true }; + + const ConvLayerData Depthwise[BlockCount] = + { + { Dw0Weights, Dw0Bias, Dw0Multipliers, Dw0Shifts, { 0.06965828687f, -128 }, { 0.08377603441f, -128 }, true }, + { Dw1Weights, Dw1Bias, Dw1Multipliers, Dw1Shifts, { 0.1205527037f, -128 }, { 0.06096315756f, -128 }, true }, + { Dw2Weights, Dw2Bias, Dw2Multipliers, Dw2Shifts, { 0.1222028211f, -128 }, { 0.04881476611f, -128 }, true }, + { Dw3Weights, Dw3Bias, Dw3Multipliers, Dw3Shifts, { 0.1046880037f, -128 }, { 0.04908436909f, -128 }, true } + }; + + const ConvLayerData Pointwise[BlockCount] = + { + { Pw0Weights, Pw0Bias, Pw0Multipliers, Pw0Shifts, { 0.08377603441f, -128 }, { 0.1916304529f, -33 }, false }, + { Pw1Weights, Pw1Bias, Pw1Multipliers, Pw1Shifts, { 0.06096315756f, -128 }, { 0.1858363748f, -41 }, false }, + { Pw2Weights, Pw2Bias, Pw2Multipliers, Pw2Shifts, { 0.04881476611f, -128 }, { 0.1591497064f, -41 }, false }, + { Pw3Weights, Pw3Bias, Pw3Multipliers, Pw3Shifts, { 0.04908436909f, -128 }, { 0.2350924909f, -48 }, false } + }; + + const AddLayerData Adds[BlockCount] = + { + { { 0.06965828687f, -128 }, { 0.1916304529f, -33 }, { 0.1205527037f, -128 }, 1561234446, -2, 1073741824, 0, 1706818890, -18, 20 }, + { { 0.1205527037f, -128 }, { 0.1858363748f, -41 }, { 0.1222028211f, -128 }, 1393080124, -1, 1073741824, 0, 1632861551, -18, 20 }, + { { 0.1222028211f, -128 }, { 0.1591497064f, -41 }, { 0.1046880037f, -128 }, 1648941528, -1, 1073741824, 0, 1632333123, -18, 20 }, + { { 0.1046880037f, -128 }, { 0.2350924909f, -48 }, { 0.1612241864f, -128 }, 1912573006, -2, 1073741824, 0, 1565699574, -18, 20 } + }; + + const MeanLayerData GlobalMean = + { + { 0.1612241864f, -128 }, + { 0.02757269889f, -128 }, + 803638539, + -3, + 125 + }; + + const ConvLayerData Dense = { DenseWeights, DenseBias, DenseMultipliers, DenseShifts, { 0.02757269889f, -128 }, { 0.1395348608f, -4 }, false }; + const Quantization ModelInput = { 0.5732762218f, 88 }; + const Quantization ModelOutput = { 0.1395348608f, -4 }; +} +} diff --git a/src/Apps/Game/src/recognition/ResBnKwsCnModelData.h b/src/Apps/Game/src/recognition/ResBnKwsCnModelData.h new file mode 100644 index 0000000..913dd14 --- /dev/null +++ b/src/Apps/Game/src/recognition/ResBnKwsCnModelData.h @@ -0,0 +1,106 @@ +#pragma once + +#include +#include + +namespace Game +{ +namespace ResBnKwsCnModelData +{ + const size_t InputFrameCount = 49; + const size_t InputMfccCount = 10; + const size_t ChannelCount = 64; + const size_t ClassCount = 14; + const size_t BlockCount = 4; + + struct Quantization + { + float scale; + int32_t zeroPoint; + }; + + struct ConvLayerData + { + const int8_t* weights; + const int32_t* bias; + const int32_t* multipliers; + const int32_t* shifts; + Quantization input; + Quantization output; + bool relu; + }; + + struct AddLayerData + { + Quantization input0; + Quantization input1; + Quantization output; + int32_t input0Multiplier; + int32_t input0Shift; + int32_t input1Multiplier; + int32_t input1Shift; + int32_t outputMultiplier; + int32_t outputShift; + int32_t leftShift; + }; + + struct MeanLayerData + { + Quantization input; + Quantization output; + int32_t multiplier; + int32_t shift; + int32_t elementCount; + }; + + extern const int8_t StemWeights[2560]; + extern const int32_t StemBias[64]; + extern const int32_t StemMultipliers[64]; + extern const int32_t StemShifts[64]; + extern const int8_t Dw0Weights[576]; + extern const int32_t Dw0Bias[64]; + extern const int32_t Dw0Multipliers[64]; + extern const int32_t Dw0Shifts[64]; + extern const int8_t Dw1Weights[576]; + extern const int32_t Dw1Bias[64]; + extern const int32_t Dw1Multipliers[64]; + extern const int32_t Dw1Shifts[64]; + extern const int8_t Dw2Weights[576]; + extern const int32_t Dw2Bias[64]; + extern const int32_t Dw2Multipliers[64]; + extern const int32_t Dw2Shifts[64]; + extern const int8_t Dw3Weights[576]; + extern const int32_t Dw3Bias[64]; + extern const int32_t Dw3Multipliers[64]; + extern const int32_t Dw3Shifts[64]; + extern const int8_t Pw0Weights[4096]; + extern const int32_t Pw0Bias[64]; + extern const int32_t Pw0Multipliers[64]; + extern const int32_t Pw0Shifts[64]; + extern const int8_t Pw1Weights[4096]; + extern const int32_t Pw1Bias[64]; + extern const int32_t Pw1Multipliers[64]; + extern const int32_t Pw1Shifts[64]; + extern const int8_t Pw2Weights[4096]; + extern const int32_t Pw2Bias[64]; + extern const int32_t Pw2Multipliers[64]; + extern const int32_t Pw2Shifts[64]; + extern const int8_t Pw3Weights[4096]; + extern const int32_t Pw3Bias[64]; + extern const int32_t Pw3Multipliers[64]; + extern const int32_t Pw3Shifts[64]; + extern const int8_t DenseWeights[896]; + extern const int32_t DenseBias[14]; + extern const int32_t DenseMultipliers[14]; + extern const int32_t DenseShifts[14]; + + extern const ConvLayerData Stem; + extern const ConvLayerData Depthwise[BlockCount]; + extern const ConvLayerData Pointwise[BlockCount]; + extern const AddLayerData Adds[BlockCount]; + extern const MeanLayerData GlobalMean; + extern const ConvLayerData Dense; + extern const Quantization ModelInput; + extern const Quantization ModelOutput; +} +} diff --git a/src/Apps/Game/src/recognition/ResBnKwsCnRecognizer.cpp b/src/Apps/Game/src/recognition/ResBnKwsCnRecognizer.cpp new file mode 100644 index 0000000..e69d049 --- /dev/null +++ b/src/Apps/Game/src/recognition/ResBnKwsCnRecognizer.cpp @@ -0,0 +1,1131 @@ +#include "ResBnKwsCnRecognizer.h" +#include "ResBnKwsCnModelData.h" +#include "../audio/VoiceEffect.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + const float Pi = 3.14159265358979323846f; + const uint32_t TargetSampleRate = 16000u; + const uint32_t TargetChannels = 1u; + const size_t WindowSamples = 16000u; + const size_t FftSize = 512u; + const size_t SpectrumBins = FftSize / 2u + 1u; + const size_t FrameStepSamples = 320u; + const size_t FeatureFrameCount = 49u; + const size_t MelBinCount = 40u; + const size_t MfccBinCount = 10u; + const size_t ClassCount = Game::ResBnKwsCnModelData::ClassCount; + static_assert(ClassCount == 14u, "ResBnKwsCnRecognizer expects 14 classes"); + const size_t ActivationHeight = 25u; + const size_t ActivationWidth = 5u; + const size_t ActivationChannels = 64u; + const size_t ActivationElementCount = + ActivationHeight * ActivationWidth * ActivationChannels; + const size_t InvalidClass = static_cast(-1); + + struct MelEnergyCache + { + size_t frameCount; + std::vector values; + + MelEnergyCache() : frameCount(0u) + { + } + }; + + struct FusionState + { + bool hasSmoothedLogits; + size_t currentClass; + size_t consecutiveHits; + std::vector smoothedLogits; + Game::KeywordRecognitionResult best; + + FusionState() + : hasSmoothedLogits(false), + currentClass(InvalidClass), + consecutiveHits(0u), + smoothedLogits(ClassCount, 0.0f) + { + } + }; + + static float HertzToMel(float hertz) + { + return 1127.0f * std::log(1.0f + hertz / 700.0f); + } + + static Game::KeywordCommand CommandFromClass(size_t classIndex) + { + switch (classIndex) + { + case 0: + return Game::KeywordCommand::Down; + case 1: + return Game::KeywordCommand::Go; + case 2: + return Game::KeywordCommand::Left; + case 3: + return Game::KeywordCommand::No; + case 4: + return Game::KeywordCommand::Off; + case 5: + return Game::KeywordCommand::On; + case 6: + return Game::KeywordCommand::Right; + case 7: + return Game::KeywordCommand::Stop; + case 8: + return Game::KeywordCommand::Up; + case 9: + return Game::KeywordCommand::Yes; + case 10: + return Game::KeywordCommand::OpenLight; + case 11: + return Game::KeywordCommand::CloseLight; + case 12: + case 13: + default: + return Game::KeywordCommand::None; + } + } + + static const std::vector& GetHannWindow() + { + static std::vector window; + if (!window.empty()) + { + return window; + } + + window.resize(FftSize, 0.0f); + for (size_t i = 0; i < FftSize; ++i) + { + window[i] = + 0.5f - + 0.5f * std::cos( + (2.0f * Pi * static_cast(i)) / + static_cast(FftSize - 1u)); + } + return window; + } + + static const std::vector& GetMelFilterBank() + { + static std::vector filters; + if (!filters.empty()) + { + return filters; + } + + filters.assign(MelBinCount * SpectrumBins, 0.0f); + const float minMel = HertzToMel(20.0f); + const float maxMel = HertzToMel(4000.0f); + std::vector melPoints(MelBinCount + 2u, 0.0f); + + for (size_t i = 0; i < melPoints.size(); ++i) + { + const float t = + static_cast(i) / + static_cast(melPoints.size() - 1u); + melPoints[i] = minMel + (maxMel - minMel) * t; + } + + for (size_t mel = 0; mel < MelBinCount; ++mel) + { + const float left = melPoints[mel]; + const float center = melPoints[mel + 1u]; + const float right = melPoints[mel + 2u]; + + for (size_t bin = 0; bin < SpectrumBins; ++bin) + { + const float frequency = + static_cast(bin) * + static_cast(TargetSampleRate) / + static_cast(FftSize); + const float frequencyMel = HertzToMel(frequency); + const float lowerSlope = + (frequencyMel - left) / (center - left); + const float upperSlope = + (right - frequencyMel) / (right - center); + const float weight = + std::max(0.0f, std::min(lowerSlope, upperSlope)); + filters[mel * SpectrumBins + bin] = weight; + } + } + return filters; + } + + static const std::vector& GetDctMatrix() + { + static std::vector matrix; + if (!matrix.empty()) + { + return matrix; + } + + matrix.resize(MfccBinCount * MelBinCount, 0.0f); + for (size_t coefficient = 0; + coefficient < MfccBinCount; + ++coefficient) + { + for (size_t mel = 0; mel < MelBinCount; ++mel) + { + const float angle = + Pi * + (static_cast(mel) + 0.5f) * + static_cast(coefficient) / + static_cast(MelBinCount); + matrix[coefficient * MelBinCount + mel] = + std::cos(angle); + } + } + return matrix; + } + + static void Fft(std::vector >& data) + { + const size_t count = data.size(); + for (size_t i = 1u, j = 0u; i < count; ++i) + { + size_t bit = count >> 1u; + for (; (j & bit) != 0u; bit >>= 1u) + { + j ^= bit; + } + j ^= bit; + if (i < j) + { + std::swap(data[i], data[j]); + } + } + + for (size_t length = 2u; length <= count; length <<= 1u) + { + const float angle = -2.0f * Pi / static_cast(length); + const std::complex step(std::cos(angle), std::sin(angle)); + for (size_t offset = 0u; offset < count; offset += length) + { + std::complex factor(1.0f, 0.0f); + for (size_t i = 0u; i < length / 2u; ++i) + { + const std::complex even = data[offset + i]; + const std::complex odd = + data[offset + i + length / 2u] * factor; + data[offset + i] = even + odd; + data[offset + i + length / 2u] = even - odd; + factor *= step; + } + } + } + } + + static std::vector PrepareAudio( + const std::vector& samples, + uint32_t sampleRate, + uint32_t channels) + { + if (samples.empty() || sampleRate == 0u || channels == 0u) + { + return std::vector(); + } + + std::vector prepared = samples; + uint32_t preparedChannels = channels; + uint32_t preparedSampleRate = sampleRate; + + if (preparedChannels != TargetChannels) + { + prepared = Game::VoiceEffect::convert_channels( + prepared, + preparedChannels, + TargetChannels); + preparedChannels = TargetChannels; + } + + if (preparedSampleRate != TargetSampleRate) + { + prepared = Game::VoiceEffect::resample( + prepared, + preparedSampleRate, + TargetSampleRate, + preparedChannels); + } + + std::vector trimmed = + Game::VoiceEffect::trim_silence(prepared, 0.02f, TargetChannels); + return trimmed.empty() ? prepared : trimmed; + } + + static std::vector PadToMinimumWindow( + const std::vector& samples) + { + if (samples.size() >= WindowSamples) + { + return samples; + } + std::vector padded = samples; + padded.resize(WindowSamples, 0); + return padded; + } + + static MelEnergyCache ExtractMelEnergyCache( + const std::vector& samples) + { + MelEnergyCache cache; + if (samples.size() < FftSize) + { + return cache; + } + + cache.frameCount = + 1u + (samples.size() - FftSize) / FrameStepSamples; + cache.values.assign(cache.frameCount * MelBinCount, 0.0f); + + const std::vector& hann = GetHannWindow(); + const std::vector& filters = GetMelFilterBank(); + std::vector > fftBuffer(FftSize); + std::vector spectrum(SpectrumBins, 0.0f); + + for (size_t frame = 0; frame < cache.frameCount; ++frame) + { + const size_t sampleOffset = frame * FrameStepSamples; + for (size_t i = 0; i < FftSize; ++i) + { + fftBuffer[i] = std::complex( + static_cast(samples[sampleOffset + i]) * hann[i], + 0.0f); + } + Fft(fftBuffer); + + for (size_t bin = 0; bin < SpectrumBins; ++bin) + { + spectrum[bin] = std::abs(fftBuffer[bin]); + } + + for (size_t mel = 0; mel < MelBinCount; ++mel) + { + float energy = 0.0f; + for (size_t bin = 0; bin < SpectrumBins; ++bin) + { + energy += filters[mel * SpectrumBins + bin] * spectrum[bin]; + } + cache.values[frame * MelBinCount + mel] = energy; + } + } + return cache; + } + + static int32_t WindowMaxAbs( + const std::vector& samples, + size_t sampleStart) + { + int32_t maximum = 0; + const size_t sampleEnd = + std::min(samples.size(), sampleStart + WindowSamples); + for (size_t i = sampleStart; i < sampleEnd; ++i) + { + maximum = std::max( + maximum, + std::abs(static_cast(samples[i]))); + } + return maximum; + } + + static std::vector BuildMfccWindow( + const MelEnergyCache& cache, + const std::vector& samples, + size_t frameStart, + float gain) + { + std::vector features( + FeatureFrameCount * MfccBinCount, + 0.0f); + const size_t sampleStart = frameStart * FrameStepSamples; + const int32_t maxAbs = WindowMaxAbs(samples, sampleStart); + if (maxAbs == 0) + { + return features; + } + + const float amplitudeScale = + gain / static_cast(maxAbs); + const std::vector& dct = GetDctMatrix(); + const float dctNormalization = + std::sqrt(2.0f / static_cast(MelBinCount)); + std::vector logMel(MelBinCount, 0.0f); + + for (size_t frame = 0; frame < FeatureFrameCount; ++frame) + { + const size_t cacheFrame = frameStart + frame; + for (size_t mel = 0; mel < MelBinCount; ++mel) + { + const float energy = + cache.values[cacheFrame * MelBinCount + mel] * + amplitudeScale; + logMel[mel] = std::log(energy + 1.0e-6f); + } + + for (size_t coefficient = 0; + coefficient < MfccBinCount; + ++coefficient) + { + float value = 0.0f; + for (size_t mel = 0; mel < MelBinCount; ++mel) + { + value += + logMel[mel] * + dct[coefficient * MelBinCount + mel]; + } + features[frame * MfccBinCount + coefficient] = + value * dctNormalization; + } + } + return features; + } + + static size_t Offset( + size_t width, + size_t channels, + size_t y, + size_t x, + size_t channel) + { + return (y * width + x) * channels + channel; + } + + static int ComputePaddingBefore( + size_t inputSize, + size_t outputSize, + size_t filterSize, + size_t stride) + { + const int required = + static_cast((outputSize - 1u) * stride + filterSize); + const int totalPadding = + std::max(0, required - static_cast(inputSize)); + return totalPadding / 2; + } + + static int32_t SaturatingCastInt32(int64_t value) + { + if (value > std::numeric_limits::max()) + { + return std::numeric_limits::max(); + } + if (value < std::numeric_limits::min()) + { + return std::numeric_limits::min(); + } + return static_cast(value); + } + + static int32_t SaturatingRoundingDoublingHighMul( + int32_t a, + int32_t b) + { + const bool overflow = + a == std::numeric_limits::min() && a == b; + const int64_t product = + static_cast(a) * static_cast(b); + const int64_t nudge = + product >= 0 + ? static_cast(1) << 30 + : 1 - (static_cast(1) << 30); + const int32_t result = static_cast( + (product + nudge) / (static_cast(1) << 31)); + return overflow + ? std::numeric_limits::max() + : result; + } + + static int32_t RoundingDivideByPowerOfTwo( + int32_t value, + int exponent) + { + if (exponent <= 0) + { + return value; + } + const uint32_t mask = + (static_cast(1) << exponent) - 1u; + const int32_t remainder = + static_cast(static_cast(value) & mask); + const int32_t threshold = + static_cast(mask >> 1u) + (value < 0 ? 1 : 0); + return (value >> exponent) + (remainder > threshold ? 1 : 0); + } + + static int32_t MultiplyByQuantizedMultiplier( + int32_t value, + int32_t multiplier, + int shift) + { + const int leftShift = shift > 0 ? shift : 0; + const int rightShift = shift > 0 ? 0 : -shift; + const int32_t shifted = SaturatingCastInt32( + static_cast(value) * + (static_cast(1) << leftShift)); + return RoundingDivideByPowerOfTwo( + SaturatingRoundingDoublingHighMul(shifted, multiplier), + rightShift); + } + + static int8_t ClampInt8(int32_t value, int32_t minimum, int32_t maximum) + { + return static_cast( + std::max(minimum, std::min(maximum, value))); + } + + static int8_t QuantizeFloat( + float value, + const Game::ResBnKwsCnModelData::Quantization& quantization) + { + const int32_t quantized = + static_cast( + std::nearbyint(value / quantization.scale)) + + quantization.zeroPoint; + return ClampInt8(quantized, -128, 127); + } + + static void Conv2DInt8( + const int8_t* input, + size_t inputHeight, + size_t inputWidth, + size_t inputChannels, + const Game::ResBnKwsCnModelData::ConvLayerData& layer, + size_t outputChannels, + size_t filterHeight, + size_t filterWidth, + size_t strideY, + size_t strideX, + int8_t* output, + size_t outputHeight, + size_t outputWidth) + { + const int padY = + ComputePaddingBefore(inputHeight, outputHeight, filterHeight, strideY); + const int padX = + ComputePaddingBefore(inputWidth, outputWidth, filterWidth, strideX); + const int32_t activationMinimum = + layer.relu ? std::max(-128, layer.output.zeroPoint) : -128; + + for (size_t outputY = 0; outputY < outputHeight; ++outputY) + { + for (size_t outputX = 0; outputX < outputWidth; ++outputX) + { + for (size_t outputChannel = 0; + outputChannel < outputChannels; + ++outputChannel) + { + int32_t accumulator = layer.bias[outputChannel]; + for (size_t kernelY = 0; kernelY < filterHeight; ++kernelY) + { + const int inputY = + static_cast(outputY * strideY + kernelY) - padY; + if (inputY < 0 || inputY >= static_cast(inputHeight)) + { + continue; + } + for (size_t kernelX = 0; kernelX < filterWidth; ++kernelX) + { + const int inputX = + static_cast(outputX * strideX + kernelX) - padX; + if (inputX < 0 || inputX >= static_cast(inputWidth)) + { + continue; + } + for (size_t inputChannel = 0; + inputChannel < inputChannels; + ++inputChannel) + { + const size_t inputIndex = + Offset( + inputWidth, + inputChannels, + static_cast(inputY), + static_cast(inputX), + inputChannel); + const size_t weightIndex = + ((outputChannel * filterHeight + kernelY) * + filterWidth + + kernelX) * + inputChannels + + inputChannel; + const int32_t inputValue = + static_cast(input[inputIndex]) - + layer.input.zeroPoint; + accumulator += + inputValue * + static_cast(layer.weights[weightIndex]); + } + } + } + + int32_t quantized = MultiplyByQuantizedMultiplier( + accumulator, + layer.multipliers[outputChannel], + layer.shifts[outputChannel]); + quantized += layer.output.zeroPoint; + output[Offset( + outputWidth, + outputChannels, + outputY, + outputX, + outputChannel)] = + ClampInt8(quantized, activationMinimum, 127); + } + } + } + } + + static void DepthwiseConv2DInt8( + const int8_t* input, + size_t height, + size_t width, + size_t channels, + const Game::ResBnKwsCnModelData::ConvLayerData& layer, + int8_t* output) + { + const size_t filterHeight = 3u; + const size_t filterWidth = 3u; + const int padY = + ComputePaddingBefore(height, height, filterHeight, 1u); + const int padX = + ComputePaddingBefore(width, width, filterWidth, 1u); + const int32_t activationMinimum = + layer.relu ? std::max(-128, layer.output.zeroPoint) : -128; + + for (size_t y = 0; y < height; ++y) + { + for (size_t x = 0; x < width; ++x) + { + for (size_t channel = 0; channel < channels; ++channel) + { + int32_t accumulator = layer.bias[channel]; + for (size_t kernelY = 0; kernelY < filterHeight; ++kernelY) + { + const int inputY = + static_cast(y + kernelY) - padY; + if (inputY < 0 || inputY >= static_cast(height)) + { + continue; + } + for (size_t kernelX = 0; kernelX < filterWidth; ++kernelX) + { + const int inputX = + static_cast(x + kernelX) - padX; + if (inputX < 0 || inputX >= static_cast(width)) + { + continue; + } + const size_t inputIndex = + Offset( + width, + channels, + static_cast(inputY), + static_cast(inputX), + channel); + const size_t weightIndex = + (kernelY * filterWidth + kernelX) * + channels + + channel; + const int32_t inputValue = + static_cast(input[inputIndex]) - + layer.input.zeroPoint; + accumulator += + inputValue * + static_cast(layer.weights[weightIndex]); + } + } + + int32_t quantized = MultiplyByQuantizedMultiplier( + accumulator, + layer.multipliers[channel], + layer.shifts[channel]); + quantized += layer.output.zeroPoint; + output[Offset(width, channels, y, x, channel)] = + ClampInt8(quantized, activationMinimum, 127); + } + } + } + } + + static void AddInt8( + const int8_t* input0, + const int8_t* input1, + size_t count, + const Game::ResBnKwsCnModelData::AddLayerData& layer, + int8_t* output) + { + const int32_t activationMinimum = + std::max(-128, layer.output.zeroPoint); + for (size_t i = 0; i < count; ++i) + { + int32_t value0 = + static_cast(input0[i]) - + layer.input0.zeroPoint; + int32_t value1 = + static_cast(input1[i]) - + layer.input1.zeroPoint; + value0 = SaturatingCastInt32( + static_cast(value0) * + (static_cast(1) << layer.leftShift)); + value1 = SaturatingCastInt32( + static_cast(value1) * + (static_cast(1) << layer.leftShift)); + value0 = MultiplyByQuantizedMultiplier( + value0, + layer.input0Multiplier, + layer.input0Shift); + value1 = MultiplyByQuantizedMultiplier( + value1, + layer.input1Multiplier, + layer.input1Shift); + int32_t quantized = MultiplyByQuantizedMultiplier( + value0 + value1, + layer.outputMultiplier, + layer.outputShift); + quantized += layer.output.zeroPoint; + output[i] = ClampInt8(quantized, activationMinimum, 127); + } + } + + static void GlobalMeanInt8( + const int8_t* input, + size_t height, + size_t width, + size_t channels, + int8_t* output) + { + const Game::ResBnKwsCnModelData::MeanLayerData& layer = + Game::ResBnKwsCnModelData::GlobalMean; + for (size_t channel = 0; channel < channels; ++channel) + { + int32_t sum = 0; + for (size_t y = 0; y < height; ++y) + { + for (size_t x = 0; x < width; ++x) + { + sum += + static_cast( + input[Offset(width, channels, y, x, channel)]) - + layer.input.zeroPoint; + } + } + const int32_t quantized = + MultiplyByQuantizedMultiplier( + sum, + layer.multiplier, + layer.shift) + + layer.output.zeroPoint; + output[channel] = ClampInt8(quantized, -128, 127); + } + } + + static void FullyConnectedInt8( + const int8_t* input, + int8_t* output) + { + const Game::ResBnKwsCnModelData::ConvLayerData& layer = + Game::ResBnKwsCnModelData::Dense; + for (size_t outputChannel = 0; + outputChannel < ClassCount; + ++outputChannel) + { + int32_t accumulator = layer.bias[outputChannel]; + for (size_t inputChannel = 0; + inputChannel < ActivationChannels; + ++inputChannel) + { + const int32_t inputValue = + static_cast(input[inputChannel]) - + layer.input.zeroPoint; + accumulator += + inputValue * + static_cast( + layer.weights[ + outputChannel * ActivationChannels + inputChannel]); + } + int32_t quantized = MultiplyByQuantizedMultiplier( + accumulator, + layer.multipliers[outputChannel], + layer.shifts[outputChannel]); + quantized += layer.output.zeroPoint; + output[outputChannel] = ClampInt8(quantized, -128, 127); + } + } + + static void Softmax( + const std::vector& logits, + std::vector& probabilities) + { + probabilities.assign(logits.size(), 0.0f); + if (logits.empty()) + { + return; + } + + float maximum = logits[0]; + for (size_t i = 1u; i < logits.size(); ++i) + { + maximum = std::max(maximum, logits[i]); + } + + float sum = 0.0f; + for (size_t i = 0u; i < logits.size(); ++i) + { + probabilities[i] = std::exp(logits[i] - maximum); + sum += probabilities[i]; + } + if (sum <= 0.0f) + { + return; + } + for (size_t i = 0u; i < probabilities.size(); ++i) + { + probabilities[i] /= sum; + } + } + + static void UpdateFusion( + FusionState& state, + const std::vector& logits, + float smoothingAlpha, + float confidenceThreshold, + float marginThreshold, + size_t requiredHits) + { + if (!state.hasSmoothedLogits) + { + state.smoothedLogits = logits; + state.hasSmoothedLogits = true; + } + else + { + for (size_t i = 0u; i < ClassCount; ++i) + { + state.smoothedLogits[i] = + smoothingAlpha * state.smoothedLogits[i] + + (1.0f - smoothingAlpha) * logits[i]; + } + } + + std::vector probabilities; + Softmax(state.smoothedLogits, probabilities); + size_t bestIndex = 0u; + size_t secondIndex = 1u; + if (probabilities[secondIndex] > probabilities[bestIndex]) + { + std::swap(bestIndex, secondIndex); + } + for (size_t i = 2u; i < probabilities.size(); ++i) + { + if (probabilities[i] > probabilities[bestIndex]) + { + secondIndex = bestIndex; + bestIndex = i; + } + else if (probabilities[i] > probabilities[secondIndex]) + { + secondIndex = i; + } + } + + const float confidence = probabilities[bestIndex]; + const float margin = confidence - probabilities[secondIndex]; + const Game::KeywordCommand command = CommandFromClass(bestIndex); + const bool valid = + command != Game::KeywordCommand::None && + confidence >= confidenceThreshold && + margin >= marginThreshold; + + if (!valid) + { + state.currentClass = InvalidClass; + state.consecutiveHits = 0u; + return; + } + + if (state.currentClass == bestIndex) + { + ++state.consecutiveHits; + } + else + { + state.currentClass = bestIndex; + state.consecutiveHits = 1u; + } + + if (state.consecutiveHits >= requiredHits && + confidence > state.best.confidence) + { + state.best = Game::KeywordRecognitionResult(command, confidence); + } + } +} + +namespace Game +{ + ResBnKwsCnRecognizer::ResBnKwsCnRecognizer() + : confidenceThreshold(0.75f), + marginThreshold(0.15f), + smoothingAlpha(0.60f), + inputGain(1.0f), + minConsecutiveHits(2u), + featureWindowStepFrames(10u), + initialized(false) + { + } + + ResBnKwsCnRecognizer::ResBnKwsCnRecognizer( + const std::string& unusedModelPath) + : confidenceThreshold(0.75f), + marginThreshold(0.15f), + smoothingAlpha(0.60f), + inputGain(1.0f), + minConsecutiveHits(2u), + featureWindowStepFrames(10u), + initialized(false) + { + (void)unusedModelPath; + } + + ResBnKwsCnRecognizer::~ResBnKwsCnRecognizer() + { + } + + bool ResBnKwsCnRecognizer::init() + { + if (initialized) + { + return true; + } + initialized = true; + return true; + } + + KeywordRecognitionResult ResBnKwsCnRecognizer::recognize( + const std::vector& samples, + uint32_t sampleRate, + uint32_t channels) + { + if (!initialized) + { + return KeywordRecognitionResult(); + } + + std::vector prepared = + PadToMinimumWindow(PrepareAudio(samples, sampleRate, channels)); + if (prepared.empty()) + { + return KeywordRecognitionResult(); + } + + const MelEnergyCache cache = ExtractMelEnergyCache(prepared); + if (cache.frameCount < FeatureFrameCount) + { + return KeywordRecognitionResult(); + } + + std::vector frameStarts; + for (size_t frameStart = 0u; + frameStart + FeatureFrameCount <= cache.frameCount; + frameStart += featureWindowStepFrames) + { + frameStarts.push_back(frameStart); + } + const size_t lastFrameStart = + cache.frameCount - FeatureFrameCount; + if (frameStarts.empty() || frameStarts.back() != lastFrameStart) + { + frameStarts.push_back(lastFrameStart); + } + + FusionState fusion; + const size_t requiredHits = + frameStarts.size() == 1u ? 1u : minConsecutiveHits; + + for (size_t i = 0u; i < frameStarts.size(); ++i) + { + const std::vector features = + BuildMfccWindow( + cache, + prepared, + frameStarts[i], + inputGain); + std::vector logits; + if (!run_embedded_model_logits(features, logits)) + { + continue; + } + UpdateFusion( + fusion, + logits, + smoothingAlpha, + confidenceThreshold, + marginThreshold, + requiredHits); + } + + std::cout + << "[INFO] ResBN-CN INT8 KWS result: class=" + << keyword_command_to_string(fusion.best.command) + << ", confidence=" + << fusion.best.confidence + << ", windows=" + << frameStarts.size() + << ", stepFrames=" + << featureWindowStepFrames + << std::endl; + return fusion.best; + } + + bool ResBnKwsCnRecognizer::run_embedded_model_logits( + const std::vector& features, + std::vector& logits) const + { + if (features.size() != FeatureFrameCount * MfccBinCount) + { + return false; + } + + std::vector quantizedInput(features.size(), 0); + for (size_t i = 0u; i < features.size(); ++i) + { + quantizedInput[i] = QuantizeFloat( + features[i], + ResBnKwsCnModelData::ModelInput); + } + + std::vector layerA(ActivationElementCount, 0); + std::vector layerB(ActivationElementCount, 0); + std::vector layerC(ActivationElementCount, 0); + + Conv2DInt8( + quantizedInput.data(), + FeatureFrameCount, + MfccBinCount, + 1u, + ResBnKwsCnModelData::Stem, + ActivationChannels, + 10u, + 4u, + 2u, + 2u, + layerA.data(), + ActivationHeight, + ActivationWidth); + + for (size_t block = 0u; + block < ResBnKwsCnModelData::BlockCount; + ++block) + { + DepthwiseConv2DInt8( + layerA.data(), + ActivationHeight, + ActivationWidth, + ActivationChannels, + ResBnKwsCnModelData::Depthwise[block], + layerB.data()); + Conv2DInt8( + layerB.data(), + ActivationHeight, + ActivationWidth, + ActivationChannels, + ResBnKwsCnModelData::Pointwise[block], + ActivationChannels, + 1u, + 1u, + 1u, + 1u, + layerC.data(), + ActivationHeight, + ActivationWidth); + AddInt8( + layerA.data(), + layerC.data(), + ActivationElementCount, + ResBnKwsCnModelData::Adds[block], + layerB.data()); + layerA.swap(layerB); + } + + int8_t pooled[ActivationChannels] = {}; + int8_t quantizedLogits[ClassCount] = {}; + GlobalMeanInt8( + layerA.data(), + ActivationHeight, + ActivationWidth, + ActivationChannels, + pooled); + FullyConnectedInt8(pooled, quantizedLogits); + + logits.resize(ClassCount, 0.0f); + for (size_t i = 0u; i < ClassCount; ++i) + { + logits[i] = + (static_cast(quantizedLogits[i]) - + ResBnKwsCnModelData::ModelOutput.zeroPoint) * + ResBnKwsCnModelData::ModelOutput.scale; + } + return true; + } + + void ResBnKwsCnRecognizer::set_confidence_threshold(float threshold) + { + if (threshold >= 0.0f && threshold <= 1.0f) + { + confidenceThreshold = threshold; + } + } + + void ResBnKwsCnRecognizer::set_margin_threshold(float threshold) + { + if (threshold >= 0.0f && threshold <= 1.0f) + { + marginThreshold = threshold; + } + } + + void ResBnKwsCnRecognizer::set_smoothing_alpha(float alpha) + { + if (alpha >= 0.0f && alpha < 1.0f) + { + smoothingAlpha = alpha; + } + } + + void ResBnKwsCnRecognizer::set_min_consecutive_hits(size_t hits) + { + if (hits > 0u) + { + minConsecutiveHits = hits; + } + } + + void ResBnKwsCnRecognizer::set_feature_window_step_frames(size_t frames) + { + if (frames > 0u) + { + featureWindowStepFrames = frames; + } + } + + void ResBnKwsCnRecognizer::set_input_gain(float gain) + { + if (gain > 0.0f && gain <= 1.0f) + { + inputGain = gain; + } + } +} diff --git a/src/Apps/Game/src/recognition/ResBnKwsCnRecognizer.h b/src/Apps/Game/src/recognition/ResBnKwsCnRecognizer.h new file mode 100644 index 0000000..a520271 --- /dev/null +++ b/src/Apps/Game/src/recognition/ResBnKwsCnRecognizer.h @@ -0,0 +1,50 @@ +#pragma once + +#include "KeywordRecognizer.h" +#include +#include +#include + +namespace Game +{ + class ResBnKwsCnRecognizer : public IKeywordRecognizer + { + private: + float confidenceThreshold; + float marginThreshold; + float smoothingAlpha; + float inputGain; + size_t minConsecutiveHits; + size_t featureWindowStepFrames; + bool initialized; + + bool run_embedded_model_logits( + const std::vector& features, + std::vector& logits) const; + + public: + ResBnKwsCnRecognizer(); + explicit ResBnKwsCnRecognizer(const std::string& unusedModelPath); + ~ResBnKwsCnRecognizer(); + + bool init() override; + KeywordRecognitionResult recognize( + const std::vector& samples, + uint32_t sampleRate, + uint32_t channels) override; + + void set_confidence_threshold(float threshold); + float get_confidence_threshold() const { return confidenceThreshold; } + void set_margin_threshold(float threshold); + float get_margin_threshold() const { return marginThreshold; } + void set_smoothing_alpha(float alpha); + float get_smoothing_alpha() const { return smoothingAlpha; } + void set_min_consecutive_hits(size_t hits); + size_t get_min_consecutive_hits() const { return minConsecutiveHits; } + void set_feature_window_step_frames(size_t frames); + size_t get_feature_window_step_frames() const { return featureWindowStepFrames; } + void set_input_gain(float gain) override; + float get_input_gain() const override { return inputGain; } + bool is_initialized() const { return initialized; } + }; +} diff --git a/src/Apps/Game/tests/TomAudioDataTests.cpp b/src/Apps/Game/tests/TomAudioDataTests.cpp new file mode 100644 index 0000000..5887cde --- /dev/null +++ b/src/Apps/Game/tests/TomAudioDataTests.cpp @@ -0,0 +1,53 @@ +#include "TomAudioData.h" +#include "WavAudioData.h" +#include "KeywordCommandRouter.h" +#include + +namespace +{ + static bool CheckAudio( + const uint8_t* bytes, + size_t byteCount, + const char* name) + { + Game::WavAudioData audio; + if (!Game::DecodePcm16Wav(bytes, byteCount, audio)) + { + std::cerr << name << " failed to decode" << std::endl; + return false; + } + if (audio.sampleRate != 48000u || audio.channels != 2u || audio.samples.empty()) + { + std::cerr << name << " has unexpected audio format" << std::endl; + return false; + } + return true; + } +} + +int main() +{ + if (!CheckAudio( + Game::EmbeddedAudio::OpenLightWav, + Game::EmbeddedAudio::OpenLightWavSize, + "OpenLight.wav") || + !CheckAudio( + Game::EmbeddedAudio::CloseLightWav, + Game::EmbeddedAudio::CloseLightWavSize, + "CloseLight.wav")) + { + return 1; + } + + const Game::KeywordCommandRouter router; + if (router.route(Game::KeywordRecognitionResult(Game::KeywordCommand::OpenLight, 1.0f)) != + Game::KeywordGameAction::OpenLight || + router.route(Game::KeywordRecognitionResult(Game::KeywordCommand::CloseLight, 1.0f)) != + Game::KeywordGameAction::CloseLight) + { + std::cerr << "Light keyword routing failed" << std::endl; + return 2; + } + + return 0; +} diff --git a/src/Apps/Game/tools/EmbedTomAudio.cmake b/src/Apps/Game/tools/EmbedTomAudio.cmake new file mode 100644 index 0000000..281140f --- /dev/null +++ b/src/Apps/Game/tools/EmbedTomAudio.cmake @@ -0,0 +1,55 @@ +if(NOT DEFINED OPEN_LIGHT_WAV OR + NOT DEFINED CLOSE_LIGHT_WAV OR + NOT DEFINED OUTPUT_HEADER OR + NOT DEFINED OUTPUT_SOURCE) + message(FATAL_ERROR "EmbedTomAudio.cmake is missing an input or output path") +endif() + +get_filename_component(OUTPUT_DIRECTORY "${OUTPUT_HEADER}" DIRECTORY) +file(MAKE_DIRECTORY "${OUTPUT_DIRECTORY}") + +file(WRITE "${OUTPUT_HEADER}" [=[#pragma once + +#include +#include + +namespace Game +{ +namespace EmbeddedAudio +{ + extern const uint8_t OpenLightWav[]; + extern const size_t OpenLightWavSize; + extern const uint8_t CloseLightWav[]; + extern const size_t CloseLightWavSize; +} +} +]=]) + +file(WRITE "${OUTPUT_SOURCE}" [=[#include "TomAudioData.h" + +namespace Game +{ +namespace EmbeddedAudio +{ +]=]) + +function(append_embedded_wav SYMBOL INPUT_PATH) + file(READ "${INPUT_PATH}" WAV_HEX HEX) + string(TOLOWER "${WAV_HEX}" WAV_HEX) + string(LENGTH "${WAV_HEX}" WAV_HEX_LENGTH) + math(EXPR WAV_SIZE "${WAV_HEX_LENGTH} / 2") + string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," WAV_BYTES "${WAV_HEX}") + set(BYTE_TOKEN "0x[0-9a-f][0-9a-f],") + set(LINE_PATTERN "(${BYTE_TOKEN}${BYTE_TOKEN}${BYTE_TOKEN}${BYTE_TOKEN}${BYTE_TOKEN}${BYTE_TOKEN}${BYTE_TOKEN}${BYTE_TOKEN}${BYTE_TOKEN}${BYTE_TOKEN}${BYTE_TOKEN}${BYTE_TOKEN}${BYTE_TOKEN}${BYTE_TOKEN}${BYTE_TOKEN}${BYTE_TOKEN})") + string(REGEX REPLACE "${LINE_PATTERN}" "\\1\n\t" WAV_BYTES "${WAV_BYTES}") + + file(APPEND "${OUTPUT_SOURCE}" "\tconst uint8_t ${SYMBOL}[] = {\n\t${WAV_BYTES}\n\t};\n") + file(APPEND "${OUTPUT_SOURCE}" "\tconst size_t ${SYMBOL}Size = ${WAV_SIZE}u;\n\n") +endfunction() + +append_embedded_wav("OpenLightWav" "${OPEN_LIGHT_WAV}") +append_embedded_wav("CloseLightWav" "${CLOSE_LIGHT_WAV}") + +file(APPEND "${OUTPUT_SOURCE}" [=[} +} +]=]) diff --git a/src/Core/Platform/DefaultHardware.h b/src/Core/Platform/DefaultHardware.h index eda6a75..290f841 100644 --- a/src/Core/Platform/DefaultHardware.h +++ b/src/Core/Platform/DefaultHardware.h @@ -7,6 +7,7 @@ #include "EvdevKeyboardState.h" #include "EvdevTouchInput.h" #include "Ap3216cPhotoSensor.h" +#include "Imx6ullAlphaLed.h" #endif #ifdef TARGET_PC #include "SdlAudioInput.h" @@ -15,6 +16,7 @@ #include "SdlKeyboardState.h" #include "SdlPointerInput.h" #include "SdlPhotoSensor.h" +#include "SimulatedBoardLed.h" #endif namespace Platform @@ -26,6 +28,7 @@ namespace Platform typedef EvdevKeyboardState DefaultKeyboardState; typedef EvdevTouchInput DefaultPointerInput; typedef Ap3216cPhotoSensor DefaultPhotoSensor; + typedef Imx6ullAlphaLed DefaultBoardLed; #endif #ifdef TARGET_PC typedef SdlAudioInput DefaultAudioInput; @@ -34,5 +37,6 @@ namespace Platform typedef SdlKeyboardState DefaultKeyboardState; typedef SdlPointerInput DefaultPointerInput; typedef SdlPhotoSensor DefaultPhotoSensor; + typedef SimulatedBoardLed DefaultBoardLed; #endif } diff --git a/src/Core/Platform/IBoardLed.h b/src/Core/Platform/IBoardLed.h new file mode 100644 index 0000000..47746f0 --- /dev/null +++ b/src/Core/Platform/IBoardLed.h @@ -0,0 +1,14 @@ +#pragma once + +namespace Platform +{ + class IBoardLed + { + public: + virtual ~IBoardLed() {} + + virtual bool init() = 0; + virtual bool set_on(bool on) = 0; + virtual bool is_on() const = 0; + }; +} diff --git a/src/Core/Platform/Imx6ullAlphaLed.cpp b/src/Core/Platform/Imx6ullAlphaLed.cpp new file mode 100644 index 0000000..52836c8 --- /dev/null +++ b/src/Core/Platform/Imx6ullAlphaLed.cpp @@ -0,0 +1,172 @@ +#include "Imx6ullAlphaLed.h" +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + const char* const CharacterDevices[] = { + "/dev/gpioled", + "/dev/dtsled", + "/dev/led"}; + + const char* const LedClassBrightnessPaths[] = { + "/sys/class/leds/sys-led/brightness", + "/sys/class/leds/user-led/brightness", + "/sys/class/leds/user/brightness", + "/sys/class/leds/led0/brightness"}; + + const char* const GpioNumber = "3"; + const char* const GpioDirectory = "/sys/class/gpio/gpio3"; + const char* const GpioDirectionPath = "/sys/class/gpio/gpio3/direction"; + const char* const GpioValuePath = "/sys/class/gpio/gpio3/value"; + + static bool PathExists(const char* path) + { + struct stat info; + return path != nullptr && stat(path, &info) == 0; + } + + static ssize_t WriteFile(const std::string& path, const void* data, size_t size) + { + const int fd = open(path.c_str(), O_WRONLY | O_CLOEXEC); + if (fd < 0) + { + return -1; + } + + const ssize_t written = write(fd, data, size); + const int savedErrno = errno; + close(fd); + errno = savedErrno; + return written; + } + + static bool WriteTextFile(const std::string& path, const char* value, size_t size) + { + return WriteFile(path, value, size) == static_cast(size); + } +} + +namespace Platform +{ + Imx6ullAlphaLed::Imx6ullAlphaLed() + : backend_(Backend::None), + path_(), + on_(false) + { + } + + bool Imx6ullAlphaLed::init() + { + if (backend_ != Backend::None) + { + return true; + } + + for (size_t i = 0; i < sizeof(CharacterDevices) / sizeof(CharacterDevices[0]); ++i) + { + if (PathExists(CharacterDevices[i])) + { + backend_ = Backend::CharacterDevice; + path_ = CharacterDevices[i]; + std::cout << "[INFO] Board LED backend: " << path_ << std::endl; + return set_on(false); + } + } + + for (size_t i = 0; i < sizeof(LedClassBrightnessPaths) / sizeof(LedClassBrightnessPaths[0]); ++i) + { + if (PathExists(LedClassBrightnessPaths[i])) + { + backend_ = Backend::LedClass; + path_ = LedClassBrightnessPaths[i]; + std::cout << "[INFO] Board LED backend: " << path_ << std::endl; + return set_on(false); + } + } + + if (configure_sysfs_gpio()) + { + backend_ = Backend::SysfsGpio; + path_ = GpioValuePath; + std::cout << "[INFO] Board LED backend: GPIO1_IO03 via sysfs" << std::endl; + return set_on(false); + } + + std::cerr + << "[WARN] Board LED is unavailable. Load the ALPHA gpioled driver or enable GPIO sysfs." + << std::endl; + return false; + } + + bool Imx6ullAlphaLed::set_on(bool on) + { + if (backend_ == Backend::None && !init()) + { + return false; + } + + bool success = false; + if (backend_ == Backend::CharacterDevice) + { + const unsigned char command = on ? 1u : 0u; + // The teaching driver consumes the byte but returns zero from write(). + success = WriteFile(path_, &command, sizeof(command)) >= 0; + } + else if (backend_ == Backend::LedClass) + { + const char value = on ? '1' : '0'; + success = WriteTextFile(path_, &value, 1u); + } + else if (backend_ == Backend::SysfsGpio) + { + // GPIO1_IO03 is active-low on the ALIENTEK I.MX6U-ALPHA board. + const char value = on ? '0' : '1'; + success = WriteTextFile(path_, &value, 1u); + } + + if (!success) + { + std::cerr + << "[WARN] Failed to turn board LED " + << (on ? "on" : "off") + << " using " << path_ + << ": errno=" << errno + << std::endl; + return false; + } + + on_ = on; + std::cout << "[INFO] Board LED: " << (on_ ? "on" : "off") << std::endl; + return true; + } + + bool Imx6ullAlphaLed::configure_sysfs_gpio() + { + if (!PathExists(GpioDirectory)) + { + const ssize_t exported = WriteFile("/sys/class/gpio/export", GpioNumber, 1u); + if (exported < 0 && errno != EBUSY) + { + return false; + } + + for (size_t attempt = 0u; attempt < 50u && !PathExists(GpioDirectory); ++attempt) + { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + } + + if (!PathExists(GpioValuePath)) + { + return false; + } + + return WriteTextFile(GpioDirectionPath, "out", 3u); + } +} diff --git a/src/Core/Platform/Imx6ullAlphaLed.h b/src/Core/Platform/Imx6ullAlphaLed.h new file mode 100644 index 0000000..b2a552e --- /dev/null +++ b/src/Core/Platform/Imx6ullAlphaLed.h @@ -0,0 +1,32 @@ +#pragma once + +#include "IBoardLed.h" +#include + +namespace Platform +{ + class Imx6ullAlphaLed : public IBoardLed + { + private: + enum class Backend + { + None, + CharacterDevice, + LedClass, + SysfsGpio + }; + + Backend backend_; + std::string path_; + bool on_; + + bool configure_sysfs_gpio(); + + public: + Imx6ullAlphaLed(); + + bool init() override; + bool set_on(bool on) override; + bool is_on() const override { return on_; } + }; +} diff --git a/src/Core/Platform/SimulatedBoardLed.h b/src/Core/Platform/SimulatedBoardLed.h new file mode 100644 index 0000000..6cce939 --- /dev/null +++ b/src/Core/Platform/SimulatedBoardLed.h @@ -0,0 +1,27 @@ +#pragma once + +#include "IBoardLed.h" +#include + +namespace Platform +{ + class SimulatedBoardLed : public IBoardLed + { + private: + bool on_; + + public: + SimulatedBoardLed() : on_(false) {} + + bool init() override { return true; } + + bool set_on(bool on) override + { + on_ = on; + std::cout << "[INFO] Simulated board LED: " << (on_ ? "on" : "off") << std::endl; + return true; + } + + bool is_on() const override { return on_; } + }; +}