From a08da9817d5c520c2645e4bc8bbf4c5f18071879 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Mon, 7 Sep 2026 02:15:28 +0200 Subject: [PATCH 1/6] Rewrite spectrogram to use RHI --- CHANGELOG.md | 5 + .../source/examples/SpectrumAnalyzer.h | 166 +- .../displays/yup_SpectrogramComponent.cpp | 472 +- .../displays/yup_SpectrogramComponent.h | 116 +- .../yup_SpectrogramComponentShader.frag | 97 + .../yup_SpectrogramComponentShader.inc | 4154 +++++++++++++++++ .../yup_SpectrogramComponentShader.vert | 29 + .../yup_SpectrumAnalyzerComponent.cpp | 3 +- .../frequency/yup_SpectrumAnalyzerState.cpp | 32 +- .../yup_SpectrogramComponent.cpp | 278 +- 10 files changed, 5133 insertions(+), 219 deletions(-) create mode 100644 modules/yup_audio_gui/displays/yup_SpectrogramComponentShader.frag create mode 100644 modules/yup_audio_gui/displays/yup_SpectrogramComponentShader.inc create mode 100644 modules/yup_audio_gui/displays/yup_SpectrogramComponentShader.vert diff --git a/CHANGELOG.md b/CHANGELOG.md index 961bd814c..4e21e72d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -119,6 +119,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - Fixed `CodeEditor` reshaping (tokenizing, laying out and re-tessellating) the entire document on every single edit, making typing in a large file cost seconds per keystroke (measured: 2s for one backspace in a large file, mostly `StyledText::update()`). `styledText` now only ever holds the currently visible lines rather than the whole document; scrolling reshapes just the newly-visible range. Selection, search highlights, the caret, and Up/Down arrow navigation were adjusted to work correctly when their target is outside the currently-shaped range (falling back to an exact document-position computation rather than depending on `styledText`). Components that never call `setSize()`/`setBounds()` on their `CodeEditor` (as none of its unit tests do) keep shaping the whole document, since there's no meaningful "visible range" to restrict to without a real size. - `CodeEditor` now renders through a `CodeEditorScheme` (new `code/yup_CodeEditorScheme.h`): every color — background, gutter, caret, current line, selection, search highlight, breakpoint and the per-token syntax colors — is stored keyed by `Identifier` (`CodeEditorScheme::setColor` / `getColor`, string constants in `CodeEditorScheme::ColorId`) and switched with `CodeEditor::setScheme`. Built-in well-known schemes are provided via `CodeEditorScheme::getBuiltIn`: `monokai`, `alabaster`, `oneDark`, `solarizedDark` and `solarizedLight`. The editor's painting moved into the theme (Themes v1) as a registered `CodeEditor` component style, and a vertical auto-hide `ScrollBar` now appears when the document overflows the viewport. The `CodeEditor` demo gained a scheme dropdown. +### Audio GUI (`yup_audio_gui`) + +- `SpectrogramComponent` now keeps its waterfall history on the GPU: a precompiled `.ysl` shader bundle (embedded in `yup_SpectrogramComponentShader.inc`, built with the `yup_shader_bundler` host tool) drives a single fullscreen-triangle `GpuRenderPass` (see `GpuPipeline`) that scrolls the previous frame down by the pending rows and writes the new rows with the color map applied entirely on the GPU, uploading only the raw magnitudes as a uniform buffer - no per-paint CPU pixel upload, no GPU texture creation, and no 2D canvas flush (if the bundle cannot be compiled no waterfall is rendered). Pending FFT rows are always consumed (applied or dropped) so the update queue can never accumulate. The log-frequency → FFT-bin mapping is precomputed once per configuration instead of recomputed with pow/log per row, and the frequency grid (lines + labels) is cached in an offscreen canvas and only re-rendered when the frequency range or size changes. The component now requires a GPU render context (the CPU `Image` fallback was removed). The component's per-frame `refreshDisplay` hook processes pending FFT rows, and the history is presented at a fractional vertical offset that advances at the FFT row rate, so the waterfall scrolls smoothly between rows instead of jumping a row per update; the offset is clamped to a single row so bursts of FFT rows can never push the waterfall off-screen. The scroll speed is adjustable via the new `setScrollSpeed()` multiplier (1.0 = realtime, 0.0 = paused). +- `SpectrogramComponent` waterfall failures (shader bundle load, pipeline compile, and GPU pass encode/draw) are now reported via `Logger::outputDebugString` in all build configurations instead of silently dropping pending rows, and the waterfall texture's render resolution is exposed as the new `defaultSpectrogramRenderWidth` constant (2x the frequency-bin count - `getSpectrogramImage()` returns that full-resolution image). +- Fixed `SpectrumAnalyzerState` never flagging FFT data as ready after a single bulk `pushSamples()`: the readiness check ran before the scoped FIFO write had committed (the `AbstractFifo::ScopedWrite` commits in its destructor), so `isFFTDataReady()` stayed false until a second push arrived. `pushSample()`/`pushSamples()` now commit the write before checking, so a pushed window is immediately available to `SpectrogramComponent::refreshDisplay()` instead of leaving the backlog untouched. ### Shading diff --git a/examples/graphics/source/examples/SpectrumAnalyzer.h b/examples/graphics/source/examples/SpectrumAnalyzer.h index 56932c585..b8d29ca98 100644 --- a/examples/graphics/source/examples/SpectrumAnalyzer.h +++ b/examples/graphics/source/examples/SpectrumAnalyzer.h @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -376,8 +377,7 @@ class SignalGenerator float generatePinkNoise() { - // Paul Kellett's refined method for pink noise - float white = yup::Random::getSystemRandom().nextFloat() * 2.0f - 1.0f; + float white = generateWhiteNoise(); pinkFilters[0] = 0.99886f * pinkFilters[0] + white * 0.0555179f; pinkFilters[1] = 0.99332f * pinkFilters[1] + white * 0.0750759f; @@ -394,10 +394,12 @@ class SignalGenerator float generateBrownNoise() { - float white = yup::Random::getSystemRandom().nextFloat() * 2.0f - 1.0f; - brownState = (brownState + (0.02f * white)) / 1.02f; - brownState *= 3.5f; // Scale up - return brownState; + float white = generateWhiteNoise(); + + brownState = (brownState + 0.02f * white) / 1.02f; + + float output = brownState * 3.5f; + return yup::jlimit (output, -1.0f, 1.0f); } void updatePhaseIncrement() @@ -445,6 +447,99 @@ class SignalGenerator int pendingReadPosition = 0; }; +//============================================================================== +// Plays a decoded audio file (e.g. the bundled mp3) through the demo, resampled +// to the device rate and looped. The whole file is decoded to mono floats on +// load so playback is a simple indexed read on the audio thread. +class AudioFilePlayer +{ +public: + bool load (yup::AudioFormatManager& formatManager, const yup::File& file) + { + auto newReader = formatManager.createReaderFor (file); + if (newReader == nullptr) + return false; + + const double newSampleRate = newReader->sampleRate > 0 ? newReader->sampleRate : 44100.0; + const int numChannels = yup::jmax (1, newReader->numChannels); + const auto numFrames = newReader->lengthInSamples; + if (numFrames <= 0) + return false; + + // Decode the whole file into an AudioBuffer, then downmix to mono + // floats so playback is an indexed read. + yup::AudioBuffer decoded (numChannels, static_cast (numFrames)); + if (! newReader->read (&decoded, 0, static_cast (numFrames), 0, true, numChannels > 1)) + return false; + + std::vector mono (static_cast (numFrames), 0.0f); + const float gain = 1.0f / static_cast (numChannels); + for (int ch = 0; ch < numChannels; ++ch) + { + const auto* channelData = decoded.getReadPointer (ch); + for (std::size_t i = 0; i < mono.size(); ++i) + mono[i] += channelData[i] * gain; + } + + reader = std::move (newReader); + samples = std::move (mono); + sourceSampleRate = newSampleRate; + position = 0.0; + return true; + } + + bool isLoaded() const noexcept { return ! samples.empty(); } + + void setAmplitude (float value) { gain = value; } + + void setTargetSampleRate (double newRate) noexcept + { + targetSampleRate = newRate > 0 ? newRate : 44100.0; + } + + void renderNextBlock (float* output, int numSamples) noexcept + { + if (output == nullptr || numSamples <= 0) + return; + + if (samples.empty()) + { + for (int i = 0; i < numSamples; ++i) + output[i] = 0.0f; + return; + } + + const std::size_t length = samples.size(); + const double ratio = sourceSampleRate / targetSampleRate; + + if (position >= static_cast (length)) + position = std::fmod (position, static_cast (length)); + + const float gainToApply = gain.load(); + for (int i = 0; i < numSamples; ++i) + { + // Linear interpolation between adjacent source samples, wrapping at + // the end of the file to loop playback. + const std::size_t i0 = static_cast (position) % length; + const std::size_t i1 = (i0 + 1) % length; + const float s0 = samples[i0]; + const float s1 = samples[i1]; + const float frac = static_cast (position - static_cast (static_cast (position))); + output[i] = (s0 + frac * (s1 - s0)) * gainToApply; + + position += ratio; + } + } + +private: + std::unique_ptr reader; + std::vector samples; + double sourceSampleRate = 44100.0; + double targetSampleRate = 44100.0; + double position = 0.0; + std::atomic gain = 1.0f; +}; + //============================================================================== class SpectrumAnalyzerDemo @@ -463,8 +558,9 @@ class SpectrumAnalyzerDemo SpectrumAnalyzerDemo() : Component ("SpectrumAnalyzerDemo") , analyzerComponent (analyzerState) - , spectrogramComponent (analyzerState) + , spectrogramComponent (spectrogramState) { + loadAudioFile(); // Pre-decode the bundled mp3 before the audio device starts setupUI(); setupAudio(); } @@ -560,7 +656,10 @@ class SpectrumAnalyzerDemo if (monoOutputBuffer.size() < static_cast (numSamples)) monoOutputBuffer.resize (static_cast (numSamples), 0.0f); - signalGenerator.renderNextBlock (monoOutputBuffer.data(), numSamples); + if (useAudioFile.load() && filePlayer.isLoaded()) + filePlayer.renderNextBlock (monoOutputBuffer.data(), numSamples); + else + signalGenerator.renderNextBlock (monoOutputBuffer.data(), numSamples); for (int sample = 0; sample < numSamples; ++sample) { @@ -570,8 +669,10 @@ class SpectrumAnalyzerDemo for (int channel = 0; channel < numOutputChannels; ++channel) outputChannelData[channel][sample] = audioSample; - // Feed to spectrum analyzer + // Feed to both spectrum displays (each owns its own analysis state + // so they do not compete for the same FIFO and starve each other) analyzerState.pushSample (audioSample); + spectrogramState.pushSample (audioSample); } } @@ -589,10 +690,14 @@ class SpectrumAnalyzerDemo signalGenerator.setAmplitude (currentAmplitude); signalGenerator.setSweepParameters (20.0, 22000.0, sweepDurationSeconds); monoOutputBuffer.assign (static_cast (maxBlockSize), 0.0f); + + // Match the file player's resampler to the device rate. + filePlayer.setTargetSampleRate (sampleRate); } - // Configure spectrum analyzer + // Configure spectrum displays analyzerComponent.setSampleRate (sampleRate); + spectrogramComponent.setSampleRate (sampleRate); } void audioDeviceStopped() override @@ -622,6 +727,7 @@ class SpectrumAnalyzerDemo signalTypeCombo->addItem ("White Noise", 7); signalTypeCombo->addItem ("Pink Noise", 8); signalTypeCombo->addItem ("Brown Noise", 9); + signalTypeCombo->addItem ("Audio File", 10); signalTypeCombo->setSelectedId (3); signalTypeCombo->onSelectedItemChanged = [this] { @@ -651,9 +757,10 @@ class SpectrumAnalyzerDemo amplitudeSlider->onValueChanged = [this] (double value) { currentAmplitude = (float) value; - updateSignalGenerator ([value] (SignalGenerator& generator) + updateSignalGenerator ([value, this] (SignalGenerator& generator) { generator.setAmplitude ((float) value); + filePlayer.setAmplitude ((float) value); }); }; addAndMakeVisible (*amplitudeSlider); @@ -745,6 +852,7 @@ class SpectrumAnalyzerDemo overlapSlider->onValueChanged = [this] (double value) { analyzerComponent.setOverlapFactor ((float) value); + spectrogramComponent.setOverlapFactor ((float) value); }; addAndMakeVisible (*overlapSlider); @@ -818,7 +926,6 @@ class SpectrumAnalyzerDemo spectrogramComponent.setWindowType (yup::WindowType::hann); spectrogramComponent.setFrequencyRange (20.0f, 22000.0f); spectrogramComponent.setDecibelRange (-100.0f, 10.0f); - spectrogramComponent.setUpdateRate (25); spectrogramComponent.setSampleRate (44100.0); spectrogramComponent.setOverlapFactor (0.75f); spectrogramComponent.setColorMap (yup::SpectrogramColorMap::Type::heatmap); @@ -933,6 +1040,23 @@ class SpectrumAnalyzerDemo fftInfoLabel->setBounds (fftStatus); } + // Decodes the bundled mp3 into the file player so it is ready (and + // immutable) before the audio device starts rendering. + void loadAudioFile() + { + formatManager.registerDefaultFormats(); + + auto dataDir = yup::File (__FILE__) + .getParentDirectory() + .getParentDirectory() + .getParentDirectory() + .getChildFile ("data"); + + auto audioFile = dataDir.getChildFile ("break_boomblastic_92bpm.mp3"); + if (audioFile.existsAsFile()) + filePlayer.load (formatManager, audioFile); + } + void updateSignalType() { SignalGenerator::SignalType signalType = SignalGenerator::SignalType::singleTone; @@ -974,6 +1098,11 @@ class SpectrumAnalyzerDemo break; } + // The "Audio File" source plays the pre-decoded mp3 through the file + // player instead of the signal generator (loaded in the constructor, so + // it is immutable while the audio callback reads it). + useAudioFile = (signalTypeCombo->getSelectedId() == 10); + updateSignalGenerator ([signalType, sweepPlaybackMode] (SignalGenerator& generator) { generator.setSignalType (signalType); @@ -990,8 +1119,9 @@ class SpectrumAnalyzerDemo int selectedId = fftSizeCombo->getSelectedId(); currentFFTSize = 64 << (selectedId - 1); // 64, 128, 256, ..., 16384 - // Update the analyzer component (which will update the state) + // Update both displays (each owns its own state) analyzerComponent.setFFTSize (currentFFTSize); + spectrogramComponent.setFFTSize (currentFFTSize); } void updateWindowType() @@ -1036,6 +1166,7 @@ class SpectrumAnalyzerDemo } analyzerComponent.setWindowType (windowType); + spectrogramComponent.setWindowType (windowType); } void updateDisplayType() @@ -1136,9 +1267,16 @@ class SpectrumAnalyzerDemo yup::AudioDeviceManager deviceManager; SignalGenerator signalGenerator; - // Spectrum analyzer + // Optional audio-file playback (the bundled mp3) + yup::AudioFormatManager formatManager; + AudioFilePlayer filePlayer; + std::atomic useAudioFile { false }; + + // Spectrum displays (each with its own analysis state - sharing one FIFO + // between two consumers would starve the spectrogram) yup::SpectrumAnalyzerState analyzerState; yup::SpectrumAnalyzerComponent analyzerComponent; + yup::SpectrumAnalyzerState spectrogramState; yup::SpectrogramComponent spectrogramComponent; // UI components diff --git a/modules/yup_audio_gui/displays/yup_SpectrogramComponent.cpp b/modules/yup_audio_gui/displays/yup_SpectrogramComponent.cpp index 48d249d2c..56d7872f1 100644 --- a/modules/yup_audio_gui/displays/yup_SpectrogramComponent.cpp +++ b/modules/yup_audio_gui/displays/yup_SpectrogramComponent.cpp @@ -22,6 +22,53 @@ namespace yup { +namespace +{ + +//============================================================================== +/* + +// The waterfall shader is precompiled into a .ysl bundle embedded in +// yup_SpectrogramComponentShader.inc (do not edit by hand). Regenerate it after +// changing yup_SpectrogramComponentShader.vert / .frag with: + + build/mac/_host_tools/shader_bundler/yup_shader_bundler \ + --vert modules/yup_audio_gui/displays/yup_SpectrogramComponentShader.vert \ + --frag modules/yup_audio_gui/displays/yup_SpectrogramComponentShader.frag \ + --output /tmp/yup_SpectrogramComponentShader.ysl \ + --target-langs glsl,essl,hlsl,msl,wgsl + +// then embed the bundle bytes into the .inc (keep the two-line comment header): + + xxd -i /tmp/yup_SpectrogramComponentShader.ysl \ + | sed -e '1d' -e '/^};/d' -e '/_len =/d' -e '/^[[:space:]]*$/d' \ + > /tmp/yup_SpectrogramComponentShader.inc.body + { echo '// Generated shader bundle (yup_SpectrogramComponentShader.ysl) - do not edit by hand.'; \ + echo '// Regenerate with the command at the top of yup_SpectrogramComponent.cpp.'; \ + cat /tmp/yup_SpectrogramComponentShader.inc.body; } \ + > modules/yup_audio_gui/displays/yup_SpectrogramComponentShader.inc + +*/ + +// Embedded precompiled shader bundle (.ysl), consumed by ShaderBundle::loadFromData(). +constexpr uint8_t kSpectrogramShaderBundle[] = { +#include "yup_SpectrogramComponentShader.inc" +}; + +// Uniforms for the waterfall shader (std140: eight floats). +struct WaterfallParams +{ + float numRows; + float width; // Waterfall texture width in texels + float height; + float bins; // Number of magnitude bins per row + float pad0; + float pad1; + float pad2; + float pad3; +}; +} // namespace + //============================================================================== // SpectrogramColorMap //============================================================================== @@ -158,13 +205,14 @@ SpectrogramComponent::SpectrogramComponent (SpectrumAnalyzerState& state) initializeFFTBuffers(); generateWindow(); + updateFrequencyMapping(); - startTimerHz (25); // 25 FPS updates + rowDataCache.resize (defaultSpectrogramMagnitudes, 0.0f); + lutDataCache.resize (defaultColorLutSize, 0u); } SpectrogramComponent::~SpectrogramComponent() { - stopTimer(); } //============================================================================== @@ -179,23 +227,125 @@ void SpectrogramComponent::initializeFFTBuffers() magnitudeBuffer.resize (static_cast (numBins), 0.0f); } -void SpectrogramComponent::ensureImageSize() +//============================================================================== +void SpectrogramComponent::applyPendingRows() { - const int currentWidth = spectrogramImage.isValid() ? spectrogramImage.getWidth() : 0; - const int currentHeight = spectrogramImage.isValid() ? spectrogramImage.getHeight() : 0; + if (pendingRows.empty()) + return; + + if (waterfallPipeline == nullptr || gpuTargets[0] == nullptr || gpuTargets[1] == nullptr) + { + Logger::outputDebugString ("SpectrogramComponent: dropping " + String (static_cast (pendingRows.size())) + + " pending FFT rows - waterfall pipeline or GPU targets not ready"); + pendingRows.clear(); + return; + } + + const int numRows = static_cast (pendingRows.size()); + const int maxShaderRows = jmax (1, static_cast (rowDataCache.size()) / spectrogramWidth); + const int appliedRows = jmin (numRows, maxShaderRows); - if (currentWidth != spectrogramWidth || currentHeight != numHistoryFrames) + auto& previous = gpuTargets[pingPongIndex]; + auto& current = gpuTargets[pingPongIndex ^ 1]; + + const WaterfallParams params { static_cast (appliedRows), + static_cast (defaultSpectrogramRenderWidth), // render width + static_cast (numHistoryFrames), + static_cast (spectrogramWidth), // bins + 0.0f, + 0.0f, + 0.0f, + 0.0f }; + + for (int row = 0; row < appliedRows; ++row) + { + const auto& magnitudes = pendingRows[static_cast (row)]; + std::copy (magnitudes.begin(), + magnitudes.begin() + spectrogramWidth, + rowDataCache.begin() + static_cast (row) * spectrogramWidth); + } + + if (lutNeedsRefresh) { - spectrogramImage = Image (spectrogramWidth, numHistoryFrames, PixelFormat::RGBA); - spectrogramImage.fill (0xFF0a0a0a); + const auto colorTable = colorMap.getColorTable(); + std::copy (colorTable.begin(), + colorTable.begin() + static_cast (jmin (colorTable.size(), lutDataCache.size())), + lutDataCache.begin()); + lutNeedsRefresh = false; } + + auto frame = GpuFrame::begin (gpuDevice); + if (frame.isValid()) + { + auto pass = current->beginRenderPass (frame, { false, GpuColor::transparentBlack() }); + if (pass.isValid()) + { + pass.setPipeline (waterfallPipeline); + pass.setTexture (0, 0, previous->asTexture()); + pass.setUniformBuffer (0, 2, ¶ms, sizeof (params)); + pass.setUniformBuffer (0, 3, rowDataCache.data(), rowDataCache.size() * sizeof (float)); + pass.setUniformBuffer (0, 4, lutDataCache.data(), lutDataCache.size() * sizeof (uint32)); + + if (pass.draw (3) && pass.finish() && frame.submit()) + { + displayTexture = current->asTexture(); + + pingPongIndex ^= 1; + scrollOffset -= static_cast (appliedRows); + } + else + { + Logger::outputDebugString ("SpectrogramComponent: waterfall draw/finish/submit failed - rows not applied"); + } + } + else + { + Logger::outputDebugString ("SpectrogramComponent: beginRenderPass failed - rows not applied"); + } + } + else + { + Logger::outputDebugString ("SpectrogramComponent: GpuFrame::begin failed - rows not applied"); + } + + pendingRows.erase (pendingRows.begin(), pendingRows.begin() + appliedRows); } -//============================================================================== -void SpectrogramComponent::timerCallback() +void SpectrogramComponent::advanceScroll() +{ + const auto now = Time::getMillisecondCounter(); + const float elapsedSeconds = lastPaintTimeMs == 0 ? 0.0f : static_cast (now - lastPaintTimeMs) / 1000.0f; + lastPaintTimeMs = now; + + const float rowRate = getRowRate(); + const float advance = rowRate > 0.0f ? rowRate * scrollSpeedMultiplier * jlimit (0.0f, 0.25f, elapsedSeconds) : 0.0f; + scrollOffset = jlimit (-1.0f, 0.0f, scrollOffset + advance); +} + +void SpectrogramComponent::setScrollSpeed (float scrollSpeedMultiplier) +{ + this->scrollSpeedMultiplier = jmax (0.0f, scrollSpeedMultiplier); +} + +void SpectrogramComponent::refreshDisplay (double lastFrameTimeSeconds) { if (! isShowing()) + { + analyzerState.reset(); return; + } + + // If the analysis fell behind the audio (e.g. slow frames), skip the stale + // rows so the display keeps tracking the most recent audio instead of the + // latency accumulating over time. + const int numReady = analyzerState.getNumAvailableSamples(); + const int hopSize = analyzerState.getHopSize(); + if (hopSize > 0 && numReady > fftSize) + { + const int skipRows = (numReady - fftSize) / hopSize; + for (int i = 0; i < skipRows && analyzerState.isFFTDataReady(); ++i) + analyzerState.getFFTData (fftInputBuffer.data()); + } bool hasNewData = false; int fftCount = 0; @@ -205,14 +355,26 @@ void SpectrogramComponent::timerCallback() while (analyzerState.isFFTDataReady() && fftCount < maxFFTsPerFrame) { processFFT(); + + pendingRows.push_back (displayMagnitudes); + hasNewData = true; ++fftCount; } + // Defensive bound: never let the pending queue grow unboundedly if paint() + // is ever throttled - keep only the newest rows. + if (pendingRows.size() > 16) + pendingRows.erase (pendingRows.begin(), pendingRows.begin() + static_cast (pendingRows.size() - 16)); + if (hasNewData) - updateSpectrogramImage(); + repaint(); +} - repaint(); +float SpectrogramComponent::getRowRate() const noexcept +{ + const int hopSize = analyzerState.getHopSize(); + return hopSize > 0 ? static_cast (sampleRate) / static_cast (hopSize) : 0.0f; } void SpectrogramComponent::processFFT() @@ -227,8 +389,7 @@ void SpectrogramComponent::processFFT() } // Apply window function - for (int i = 0; i < fftSize; ++i) - fftInputBuffer[static_cast (i)] *= windowBuffer[static_cast (i)]; + FloatVectorOperations::multiply (fftInputBuffer.data(), windowBuffer.data(), fftInputBuffer.data(), fftSize); // Perform FFT fftProcessor->performRealFFTForward (fftInputBuffer.data(), fftOutputBuffer.data()); @@ -243,53 +404,19 @@ void SpectrogramComponent::processFFT() magnitudeBuffer[static_cast (binIndex)] = std::sqrt (real * real + imag * imag) * windowGain; } - // Map FFT bins to display bins using logarithmic frequency scaling - const int numDisplayBins = spectrogramWidth; - - for (int i = 0; i < numDisplayBins; ++i) + // Map FFT bins to display bins using the precomputed logarithmic mapping. + for (int i = 0; i < spectrogramWidth; ++i) { - const float proportion = static_cast (i) / static_cast (numDisplayBins - 1); - const float logFreq = logMinFrequency + proportion * (logMaxFrequency - logMinFrequency); - const float centerFreq = std::pow (10.0f, logFreq); - - float freqRangeStart, freqRangeEnd; - - if (i == 0) - { - freqRangeStart = minFrequency; - const float nextLogFreq = logMinFrequency + (static_cast (i + 1) / static_cast (numDisplayBins - 1)) * (logMaxFrequency - logMinFrequency); - const float nextFreq = std::pow (10.0f, nextLogFreq); - freqRangeEnd = (centerFreq + nextFreq) * 0.5f; - } - else if (i == numDisplayBins - 1) - { - const float prevLogFreq = logMinFrequency + (static_cast (i - 1) / static_cast (numDisplayBins - 1)) * (logMaxFrequency - logMinFrequency); - const float prevFreq = std::pow (10.0f, prevLogFreq); - freqRangeStart = (prevFreq + centerFreq) * 0.5f; - freqRangeEnd = maxFrequency; - } - else - { - const float prevLogFreq = logMinFrequency + (static_cast (i - 1) / static_cast (numDisplayBins - 1)) * (logMaxFrequency - logMinFrequency); - const float nextLogFreq = logMinFrequency + (static_cast (i + 1) / static_cast (numDisplayBins - 1)) * (logMaxFrequency - logMinFrequency); - const float prevFreq = std::pow (10.0f, prevLogFreq); - const float nextFreq = std::pow (10.0f, nextLogFreq); - freqRangeStart = (prevFreq + centerFreq) * 0.5f; - freqRangeEnd = (centerFreq + nextFreq) * 0.5f; - } - - const float startBin = (freqRangeStart * static_cast (fftSize)) / static_cast (sampleRate); - const float endBin = (freqRangeEnd * static_cast (fftSize)) / static_cast (sampleRate); - const float binSpan = endBin - startBin; + const auto& mapping = displayBinMapping[static_cast (i)]; + const float binSpan = mapping.endBin - mapping.startBin; float magnitude = 0.0f; if (binSpan <= 1.5f) { - const float exactBin = (centerFreq * static_cast (fftSize)) / static_cast (sampleRate); - const int bin1 = jlimit (0, numBins - 1, static_cast (exactBin)); + const int bin1 = jlimit (0, numBins - 1, static_cast (mapping.exactBin)); const int bin2 = jlimit (0, numBins - 1, bin1 + 1); - const float fraction = exactBin - static_cast (bin1); + const float fraction = mapping.exactBin - static_cast (bin1); const float mag1 = magnitudeBuffer[static_cast (bin1)]; const float mag2 = magnitudeBuffer[static_cast (bin2)]; @@ -297,8 +424,8 @@ void SpectrogramComponent::processFFT() } else { - const int binStart = jlimit (0, numBins - 1, static_cast (startBin)); - const int binEnd = jlimit (0, numBins - 1, static_cast (endBin + 0.5f)); + const int binStart = jlimit (0, numBins - 1, static_cast (mapping.startBin)); + const int binEnd = jlimit (0, numBins - 1, static_cast (mapping.endBin + 0.5f)); for (int binIndex = binStart; binIndex <= binEnd; ++binIndex) magnitude = jmax (magnitude, magnitudeBuffer[static_cast (binIndex)]); @@ -318,45 +445,126 @@ void SpectrogramComponent::processFFT() } } -void SpectrogramComponent::updateSpectrogramImage() +void SpectrogramComponent::updateFrequencyMapping() { - ensureImageSize(); + displayBinMapping.resize (static_cast (spectrogramWidth)); + + const int numDisplayBins = spectrogramWidth; + const float invLastBin = 1.0f / static_cast (numDisplayBins - 1); + + for (int i = 0; i < numDisplayBins; ++i) + { + const float proportion = static_cast (i) * invLastBin; + const float logFreq = logMinFrequency + proportion * (logMaxFrequency - logMinFrequency); + const float centerFreq = std::pow (10.0f, logFreq); + + const float prevProportion = static_cast (i - 1) * invLastBin; + const float nextProportion = static_cast (i + 1) * invLastBin; - scrollSpectrogram(); - writeMagnitudeRow(); + float freqRangeStart, freqRangeEnd; + + if (i == 0) + { + freqRangeStart = minFrequency; + freqRangeEnd = (centerFreq + std::pow (10.0f, logMinFrequency + nextProportion * (logMaxFrequency - logMinFrequency))) * 0.5f; + } + else if (i == numDisplayBins - 1) + { + freqRangeStart = (std::pow (10.0f, logMinFrequency + prevProportion * (logMaxFrequency - logMinFrequency)) + centerFreq) * 0.5f; + freqRangeEnd = maxFrequency; + } + else + { + freqRangeStart = (std::pow (10.0f, logMinFrequency + prevProportion * (logMaxFrequency - logMinFrequency)) + centerFreq) * 0.5f; + freqRangeEnd = (centerFreq + std::pow (10.0f, logMinFrequency + nextProportion * (logMaxFrequency - logMinFrequency))) * 0.5f; + } - // Invalidate so the GPU texture is recreated from updated pixel data - spectrogramImage.invalidateTexture(); + auto& mapping = displayBinMapping[static_cast (i)]; + mapping.startBin = (freqRangeStart * static_cast (fftSize)) / static_cast (sampleRate); + mapping.endBin = (freqRangeEnd * static_cast (fftSize)) / static_cast (sampleRate); + mapping.exactBin = (centerFreq * static_cast (fftSize)) / static_cast (sampleRate); + } } -void SpectrogramComponent::scrollSpectrogram() +bool SpectrogramComponent::ensureGpuTargets (GraphicsContext& context) { - auto raw = spectrogramImage.getRawData(); - const int width = spectrogramImage.getWidth(); - const int height = spectrogramImage.getHeight(); - const int stride = spectrogramImage.getPixelStride(); - const int rowBytes = width * stride; + if (gpuTargets[0] != nullptr && gpuTargets[1] != nullptr) + return true; + + if (! context.isGpuAvailable()) + return false; + + gpuDevice = context.getGpuDevice(); + if (gpuDevice == nullptr) + return false; + + // The waterfall texture is rendered at a higher horizontal resolution than + // the bin count and interpolated in the shader for a smooth (antialiased) + // frequency axis. + const int renderWidth = defaultSpectrogramRenderWidth; + + const auto backgroundColor = Color (0xFF0a0a0a); + + gpuTargets[0] = GpuTarget::create (gpuDevice, renderWidth, numHistoryFrames); + gpuTargets[1] = GpuTarget::create (gpuDevice, renderWidth, numHistoryFrames); - // Shift all rows down by one (row 0 stays at top, row height-1 falls off) - if (height > 1) + if (gpuTargets[0] == nullptr || gpuTargets[1] == nullptr) { - std::memmove ( - raw.data() + rowBytes, - raw.data(), - static_cast ((height - 1)) * static_cast (rowBytes)); + gpuTargets[0] = nullptr; + gpuTargets[1] = nullptr; + return false; } + + const GpuRenderOptions clearOptions { true, backgroundColor }; + for (auto& target : gpuTargets) + { + auto frame = GpuFrame::begin (gpuDevice); + if (frame.isValid()) + { + auto pass = target->beginRenderPass (frame, clearOptions); + pass.finish(); + frame.submit(); + } + } + + ensureWaterfallPipeline(); + + pingPongIndex = 0; + displayTexture = gpuTargets[0]->asTexture(); + + scrollOffset = 0.0f; + lastPaintTimeMs = 0; + + return true; } -void SpectrogramComponent::writeMagnitudeRow() +void SpectrogramComponent::ensureWaterfallPipeline() { - auto& bitmap = spectrogramImage.getPixelData(); - const int width = spectrogramImage.getWidth(); + if (waterfallPipeline != nullptr || gpuDevice == nullptr) + return; + + auto loaded = ShaderBundle::loadFromData (kSpectrogramShaderBundle, sizeof (kSpectrogramShaderBundle)); + if (loaded.failed()) + { + Logger::outputDebugString ("SpectrogramComponent: failed to load waterfall shader bundle: " + loaded.getErrorMessage()); + + jassertfalse; // Waterfall shader bundle failed to load - no waterfall will be rendered. + return; + } + + GpuPipelineOptions options; + options.colorTargetCount = 1; + options.colorTargets[0].format = GpuTextureFormat::rgba8unorm; + options.colorTargets[0].blendEnabled = false; + + auto result = GpuPipeline::compileFromBundle (gpuDevice, loaded.getReference(), options); - for (int x = 0; x < width; ++x) + if (result.wasOk()) + waterfallPipeline = result.getValue(); + else { - const float magnitude = displayMagnitudes[static_cast (x)]; - const uint32 color = colorMap.map (magnitude); - bitmap.setPixel (x, 0, color); + Logger::outputDebugString ("SpectrogramComponent: waterfall shader failed to compile: " + result.getErrorMessage()); + jassertfalse; // Waterfall shader failed to compile - no waterfall will be rendered. } } @@ -374,18 +582,31 @@ void SpectrogramComponent::generateWindow() //============================================================================== void SpectrogramComponent::paint (Graphics& g) { + // Lazily move to the GPU path on the first paint that has a live context. + if (gpuTargets[0] == nullptr && g.getGraphicsContext().isGpuAvailable()) + ensureGpuTargets (g.getGraphicsContext()); + + // Apply any FFT rows queued since the last timer tick. + applyPendingRows(); + + // Advance the fractional scroll offset on the GPU path. + advanceScroll(); + const auto bounds = getLocalBounds(); // Background g.setFillColor (Color (0xFF0a0a0a)); g.fillAll(); - // Draw the spectrogram image (stretched to fill the component) - if (spectrogramImage.isValid()) - g.drawImage (spectrogramImage, bounds); + // Draw the spectrogram, stretched to fill the component. + if (displayTexture != nullptr) + { + const float scaleY = bounds.getHeight() / static_cast (displayTexture->getHeight()); + g.drawTexture (displayTexture, Rectangle (bounds.getX(), bounds.getY() + scrollOffset * scaleY, bounds.getWidth(), bounds.getHeight())); + } - // Draw grid overlays - drawFrequencyGrid (g, bounds); + // Draw grid overlays (cached offscreen, only re-rendered on changes) + drawFrequencyGridCached (g, bounds); } void SpectrogramComponent::resized() @@ -399,9 +620,9 @@ void SpectrogramComponent::drawFrequencyGrid (Graphics& g, const RectanglegetDefaultFont().withHeight (9.0f); const int multipliers[] = { 1, 2, 5 }; - const int powers[] = { 1, 10, 100, 1000, 10000 }; + const int powers[] = { 1, 10, 100, 200, 1000, 2000, 10000 }; - for (int brightness = 0; brightness < 3; ++brightness) + for (int brightness = 0; brightness < numElementsInArray (multipliers); ++brightness) { Color lineColor; float lineWidth; @@ -427,7 +648,7 @@ void SpectrogramComponent::drawFrequencyGrid (Graphics& g, const Rectangle (multipliers[brightness] * powers[power]); @@ -466,6 +687,34 @@ float SpectrogramComponent::frequencyToX (float frequency, float width) const no return jmap (std::log10 (frequency), logMinFrequency, logMaxFrequency, 0.0f, width); } +void SpectrogramComponent::drawFrequencyGridCached (Graphics& g, const Rectangle& bounds) +{ + const Rectangle cacheBounds (0.0f, 0.0f, static_cast (static_cast (bounds.getWidth())), static_cast (static_cast (bounds.getHeight()))); + + if (gridCanvas == nullptr || gridNeedsRedraw || gridCacheBounds != cacheBounds) + { + gridCanvas = GpuCanvas::create (g.getGraphicsContext(), + static_cast (cacheBounds.getWidth()), + static_cast (cacheBounds.getHeight()), + Colors::transparentBlack); + + if (gridCanvas != nullptr) + { + auto& gridGraphics = gridCanvas->beginDraw(); + drawFrequencyGrid (gridGraphics, cacheBounds); + gridCanvas->commit(); + + gridCacheBounds = cacheBounds; + gridNeedsRedraw = false; + } + } + + if (gridCanvas != nullptr) + g.drawTexture (gridCanvas->asTexture(), bounds); + else + drawFrequencyGrid (g, bounds); +} + //============================================================================== void SpectrogramComponent::setFFTSize (int size) { @@ -478,6 +727,7 @@ void SpectrogramComponent::setFFTSize (int size) initializeFFTBuffers(); generateWindow(); + updateFrequencyMapping(); clearHistory(); repaint(); @@ -493,18 +743,6 @@ void SpectrogramComponent::setWindowType (WindowType type) } } -void SpectrogramComponent::setUpdateRate (int hz) -{ - stopTimer(); - startTimerHz (jmax (1, jmin (60, hz))); -} - -int SpectrogramComponent::getUpdateRate() const noexcept -{ - const int intervalMs = getTimerInterval(); - return isTimerRunning() ? 1000 / intervalMs : 0; -} - void SpectrogramComponent::setFrequencyRange (float minFreq, float maxFreq) { const float newMinFrequency = jmax (1.0f, minFreq); @@ -521,6 +759,9 @@ void SpectrogramComponent::setFrequencyRange (float minFreq, float maxFreq) logMinFrequency = std::log10 (minFrequency); logMaxFrequency = std::log10 (maxFrequency); + updateFrequencyMapping(); + gridNeedsRedraw = true; + clearHistory(); } @@ -550,12 +791,14 @@ void SpectrogramComponent::setSampleRate (double rate) return; sampleRate = newSampleRate; + updateFrequencyMapping(); clearHistory(); } void SpectrogramComponent::setColorMap (SpectrogramColorMap::Type type) { colorMap = SpectrogramColorMap (type); + lutNeedsRefresh = true; // Re-fill the cached LUT on the next apply clearHistory(); } @@ -563,9 +806,13 @@ void SpectrogramComponent::setNumHistoryFrames (int numFrames) { numHistoryFrames = jmax (4, numFrames); - // Recreate the image with the new height - spectrogramImage = Image (spectrogramWidth, numHistoryFrames, PixelFormat::RGBA); - spectrogramImage.fill (Color (0xFF0a0a0a).getARGB()); + pendingRows.clear(); + scrollOffset = 0.0f; + lastPaintTimeMs = 0; + + gpuTargets[0] = nullptr; + gpuTargets[1] = nullptr; + displayTexture = nullptr; repaint(); } @@ -582,10 +829,23 @@ float SpectrogramComponent::getOverlapFactor() const noexcept void SpectrogramComponent::clearHistory() { - if (spectrogramImage.isValid()) - spectrogramImage.fill (Color (0xFF0a0a0a).getARGB()); + pendingRows.clear(); + scrollOffset = 0.0f; + lastPaintTimeMs = 0; + + gpuTargets[0] = nullptr; + gpuTargets[1] = nullptr; + displayTexture = nullptr; repaint(); } +Image SpectrogramComponent::getSpectrogramImage() +{ + if (gpuTargets[pingPongIndex] != nullptr) + return Image::fromTarget (*gpuTargets[pingPongIndex]); + + return {}; +} + } // namespace yup diff --git a/modules/yup_audio_gui/displays/yup_SpectrogramComponent.h b/modules/yup_audio_gui/displays/yup_SpectrogramComponent.h index a6e9dbdf2..8eb783ad4 100644 --- a/modules/yup_audio_gui/displays/yup_SpectrogramComponent.h +++ b/modules/yup_audio_gui/displays/yup_SpectrogramComponent.h @@ -28,7 +28,7 @@ namespace yup ARGB colors for spectrogram visualization. The default color map produces a professional heatmap gradient: - black → dark blue → blue → cyan → green → yellow → red → white. + black > dark blue > blue > cyan > green > yellow > red > white. @tags{Audio} */ @@ -39,11 +39,11 @@ class YUP_API SpectrogramColorMap /** Predefined color map types. */ enum class Type { - heatmap, ///< Black → blue → cyan → green → yellow → red → white - grayscale, ///< Black → gray → white - cool, ///< Black → blue → cyan → white - warm, ///< Black → red → orange → yellow → white - viridis ///< Perceptually uniform blue → green → yellow + heatmap, ///< Black > blue > cyan > green > yellow > red > white + grayscale, ///< Black > gray > white + cool, ///< Black > blue > cyan > white + warm, ///< Black > red > orange > yellow > white + viridis ///< Perceptually uniform blue > green > yellow }; //============================================================================== @@ -68,6 +68,16 @@ class YUP_API SpectrogramColorMap /** Returns the number of color stops in the lookup table. */ int getNumColorStops() const noexcept { return numColorStops; } + /** Returns the raw ARGB color lookup table (0xAARRGGBB per entry). + + The table holds exactly getNumColorStops() entries sampled from the + gradient, which is what map() interpolates between. Uploading this table + to the GPU lets a shader perform the same color mapping. + + @return a span over the color table. + */ + Span getColorTable() const noexcept { return colorTable; } + private: //============================================================================== void buildHeatmap(); @@ -91,10 +101,6 @@ class YUP_API SpectrogramColorMap scrolling waterfall display. Each new row of FFT data is rendered at the top of the display and previous rows scroll downward. - The spectrogram image is stored in a GPU texture which is updated each frame - by invalidating the existing texture and letting the renderer recreate it. - Frequency and decibel grid lines are drawn as vector overlays. - Example usage: @code @@ -105,7 +111,6 @@ class YUP_API SpectrogramColorMap spectrogram.setFrequencyRange (20.0f, 20000.0f); spectrogram.setDecibelRange (-100.0f, 0.0f); spectrogram.setColorMap (SpectrogramColorMap::Type::heatmap); - spectrogram.setUpdateRate (25); // In audio callback: analyzerState.pushSamples (audioData, numSamples); @@ -113,16 +118,17 @@ class YUP_API SpectrogramColorMap @see SpectrumAnalyzerState, SpectrumAnalyzerComponent, SpectrogramColorMap */ -class YUP_API SpectrogramComponent - : public Component - , public Timer +class YUP_API SpectrogramComponent : public Component { public: //============================================================================== /** Display constants. */ enum { - defaultSpectrogramWidth = 512 ///< Default number of horizontal frequency bins. + defaultSpectrogramWidth = 1024, ///< Default number of horizontal frequency bins. + defaultSpectrogramRenderWidth = defaultSpectrogramWidth * 2, ///< Horizontal resolution of the waterfall texture (2x the bin count, interpolated for a smooth frequency axis). + defaultSpectrogramMagnitudes = 2048, ///< Default number of magnitudes per FFT (must be >= defaultSpectrogramWidth). + defaultColorLutSize = 256 ///< Default number of color stops in the color lookup table. }; //============================================================================== @@ -156,14 +162,20 @@ class YUP_API SpectrogramComponent WindowType getWindowType() const noexcept { return currentWindowType; } //============================================================================== - /** Sets the display update rate in Hz. + /** Sets the waterfall scroll speed, as a multiplier of the realtime FFT + row rate (sampleRate / hopSize). - @param hz update rate (typical values: 10-30 Hz) + A multiplier of 1.0 (the default) keeps the waterfall locked to the + audio: new rows slide in exactly as fast as the FFT produces them. + Values above 1.0 scroll faster than realtime (rows are dropped at the + bottom sooner), values below 1.0 scroll slower. 0.0 pauses the scroll. + + @param scrollSpeedMultiplier the scroll speed multiplier (clamped to >= 0) */ - void setUpdateRate (int hz); + void setScrollSpeed (float scrollSpeedMultiplier); - /** Returns the current update rate in Hz. */ - int getUpdateRate() const noexcept; + /** Returns the current scroll speed multiplier (1.0 = realtime). */ + float getScrollSpeed() const noexcept { return scrollSpeedMultiplier; } //============================================================================== /** Sets the frequency range for the display. @@ -217,7 +229,7 @@ class YUP_API SpectrogramComponent /** Sets the number of FFT frames kept in the scrolling history. This determines how many past FFT frames are visible in the display. - The spectrogram image height is resized to match this value. + The history canvases are resized to match this value. @param numFrames number of history frames (minimum: 4, default: matches component height) */ @@ -243,14 +255,16 @@ class YUP_API SpectrogramComponent //============================================================================== /** Returns the current spectrogram image. - The returned image contains the waterfall history used by paint() when - rendering the component. The image may be invalid until the spectrogram - has allocated its history buffer, for example after setNumHistoryFrames() - or after processing FFT data on a visible component. + The returned image contains the waterfall history held in the current + history canvas. This performs a GPU readback, which is slower than + rendering from the component's cached GPU texture (via paint()); prefer + painting the component directly when possible. The image is invalid + until the component has a live GPU render context and its history + canvases have been created (e.g. after the first paint). @returns the current spectrogram image. */ - Image getSpectrogramImage() const noexcept { return spectrogramImage; } + Image getSpectrogramImage(); //============================================================================== /** @internal */ @@ -258,36 +272,65 @@ class YUP_API SpectrogramComponent /** @internal */ void resized() override; /** @internal */ - void timerCallback() override; + void refreshDisplay (double lastFrameTimeSeconds) override; private: //============================================================================== void processFFT(); - void updateSpectrogramImage(); - void scrollSpectrogram(); - void writeMagnitudeRow(); + void updateFrequencyMapping(); + bool ensureGpuTargets (GraphicsContext& context); + void ensureWaterfallPipeline(); + void applyPendingRows(); + void advanceScroll(); + float getRowRate() const noexcept; void initializeFFTBuffers(); void generateWindow(); - void ensureImageSize(); float frequencyToX (float frequency, float width) const noexcept; void drawFrequencyGrid (Graphics& g, const Rectangle& bounds); + void drawFrequencyGridCached (Graphics& g, const Rectangle& bounds); //============================================================================== SpectrumAnalyzerState& analyzerState; - // FFT processing (performed on UI thread) std::unique_ptr fftProcessor; std::vector fftInputBuffer; std::vector fftOutputBuffer; std::vector windowBuffer; - std::vector magnitudeBuffer; // Per-bin magnitudes from FFT - std::vector displayMagnitudes; // Magnitudes mapped to display bins + std::vector magnitudeBuffer; + std::vector displayMagnitudes; - // Spectrogram image - Image spectrogramImage; + // Spectrogram color map SpectrogramColorMap colorMap; + // Precomputed log-frequency > FFT-bin mapping + struct DisplayBinMapping + { + float startBin = 0.0f; + float endBin = 0.0f; + float exactBin = 0.0f; + }; + + std::vector displayBinMapping; + + // GPU waterfall state + GpuDevice::Ptr gpuDevice; + GpuPipeline::Ptr waterfallPipeline; + GpuTarget::Ptr gpuTargets[2]; + GpuTexture::Ptr displayTexture; + std::vector> pendingRows; + std::vector rowDataCache; // Reused uniform buffer (shader mags[defaultSpectrogramMagnitudes]) + std::vector lutDataCache; // Reused color LUT (shader lut[defaultColorLutSize]) + bool lutNeedsRefresh = true; // Re-fill lutDataCache on the next apply + int pingPongIndex = 0; + float scrollOffset = 0.0f; + uint32 lastPaintTimeMs = 0; + + // Cached frequency grid (frequency lines + labels). + GpuCanvas::Ptr gridCanvas; + Rectangle gridCacheBounds; + bool gridNeedsRedraw = true; + // Configuration WindowType currentWindowType = WindowType::hann; int fftSize = 4096; @@ -300,6 +343,7 @@ class YUP_API SpectrogramComponent float minDecibels = -100.0f; float maxDecibels = 0.0f; double sampleRate = 44100.0; + float scrollSpeedMultiplier = 1.0f; // Window compensation float windowGain = 1.0f; diff --git a/modules/yup_audio_gui/displays/yup_SpectrogramComponentShader.frag b/modules/yup_audio_gui/displays/yup_SpectrogramComponentShader.frag new file mode 100644 index 000000000..391239fc8 --- /dev/null +++ b/modules/yup_audio_gui/displays/yup_SpectrogramComponentShader.frag @@ -0,0 +1,97 @@ +/* + ============================================================================== + + This file is part of the YUP library. + Copyright (c) 2026 - kunitoki@gmail.com + + YUP is an open source library subject to open-source licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#version 450 + +// Fullscreen waterfall shader. A single fullscreen triangle scrolls the +// previous history texture down by numRows rows and writes the numRows new rows +// at the top, mapping each bin's raw magnitude through the color lookup table +// (a lerp between adjacent entries, matching SpectrogramColorMap::map) entirely +// on the GPU. +// +// Bindings: +// set=0 binding=0 : previous history texture (UV.y = 0 is the top row) +// set=0 binding=1 : sampler (implicit default, like the matte pipeline) +// set=0 binding=2 : WaterfallParams UBO +// set=0 binding=3 : RowData UBO (W * numRows raw magnitudes, oldest row first) +// set=0 binding=4 : LutData UBO (256 ARGB color entries) + +layout(set = 0, binding = 0) uniform texture2D u_prev; +layout(set = 0, binding = 1) uniform sampler u_samp; +layout(set = 0, binding = 2) uniform WaterfallParams +{ + float numRows; + float width; + float height; + float bins; + float pad0; + float pad1; + float pad2; + float pad3; +} p; + +layout(set = 0, binding = 3) uniform RowData +{ + vec4 mags[512]; // 2048 raw magnitudes packed as vec4s (std140 tight when W is a multiple of 4) +} rows; + +layout(set = 0, binding = 4) uniform LutData +{ + uvec4 lut[64]; // 256 ARGB colors packed as uvec4s +} lut; + +layout(location = 0) out vec4 fragColor; + +float fetchMag(int idx) +{ + return rows.mags[idx >> 2][idx & 3]; +} + +void main() +{ + vec2 uv = gl_FragCoord.xy / vec2(p.width, p.height); + if (uv.y >= p.numRows / p.height) + { + fragColor = texture(sampler2D(u_prev, u_samp), vec2(uv.x, uv.y - p.numRows / p.height)); + } + else + { + int x = int(gl_FragCoord.x); + int y = int(gl_FragCoord.y); + int row = int(p.numRows) - 1 - y; + + float binPos = float(x) * (p.bins / p.width); + int bin0 = int(binPos); + int bin1 = min(bin0 + 1, int(p.bins) - 1); + float fracBin = binPos - float(bin0); + float mag = mix(fetchMag(row * int(p.bins) + bin0), fetchMag(row * int(p.bins) + bin1), fracBin); + + float t = clamp(mag, 0.0, 1.0) * 255.0; + int i0 = int(t); + int i1 = min(i0 + 1, 255); + float frac = t - float(i0); + uint c0 = lut.lut[i0 >> 2][i0 & 3]; + uint c1 = lut.lut[i1 >> 2][i1 & 3]; + vec4 col0 = vec4(float((c0 >> 16) & 255u), float((c0 >> 8) & 255u), float(c0 & 255u), float((c0 >> 24) & 255u)) / 255.0; + vec4 col1 = vec4(float((c1 >> 16) & 255u), float((c1 >> 8) & 255u), float(c1 & 255u), float((c1 >> 24) & 255u)) / 255.0; + fragColor = mix(col0, col1, frac); + } +} diff --git a/modules/yup_audio_gui/displays/yup_SpectrogramComponentShader.inc b/modules/yup_audio_gui/displays/yup_SpectrogramComponentShader.inc new file mode 100644 index 000000000..0946a23eb --- /dev/null +++ b/modules/yup_audio_gui/displays/yup_SpectrogramComponentShader.inc @@ -0,0 +1,4154 @@ +// Generated shader bundle (yup_SpectrogramComponentShader.ysl) - do not edit by hand. +// Regenerate with the command at the top of yup_SpectrogramComponent.cpp. + 0x52, 0x49, 0x46, 0x46, 0x8e, 0xc2, 0x00, 0x00, 0x59, 0x53, 0x4c, 0x42, + 0x56, 0x45, 0x52, 0x53, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x4c, 0x49, 0x53, 0x54, 0x76, 0xc2, 0x00, 0x00, 0x53, 0x48, 0x41, 0x44, + 0x53, 0x48, 0x44, 0x52, 0x8e, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x50, 0x56, 0x42, 0x00, 0x00, 0x00, 0x00, + 0x4c, 0x49, 0x53, 0x54, 0x76, 0x2f, 0x00, 0x00, 0x56, 0x41, 0x52, 0x53, + 0x56, 0x41, 0x52, 0x54, 0xb5, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0xe1, 0x00, 0x00, 0x00, + 0x23, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x34, 0x35, 0x30, + 0x0a, 0x0a, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, + 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x20, 0x78, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, + 0x28, 0x75, 0x69, 0x6e, 0x74, 0x28, 0x67, 0x6c, 0x5f, 0x56, 0x65, 0x72, + 0x74, 0x65, 0x78, 0x49, 0x44, 0x29, 0x20, 0x26, 0x20, 0x31, 0x75, 0x29, + 0x20, 0x3c, 0x3c, 0x20, 0x32, 0x75, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x2e, + 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x20, 0x79, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, + 0x75, 0x69, 0x6e, 0x74, 0x28, 0x67, 0x6c, 0x5f, 0x56, 0x65, 0x72, 0x74, + 0x65, 0x78, 0x49, 0x44, 0x29, 0x20, 0x26, 0x20, 0x32, 0x75, 0x29, 0x20, + 0x3c, 0x3c, 0x20, 0x31, 0x75, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x2e, 0x30, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x67, 0x6c, 0x5f, 0x50, 0x6f, 0x73, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, + 0x28, 0x78, 0x2c, 0x20, 0x79, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, + 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x67, 0x6c, + 0x5f, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x79, 0x20, + 0x3d, 0x20, 0x2d, 0x67, 0x6c, 0x5f, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x2e, 0x79, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x49, 0x53, 0x52, + 0x43, 0x17, 0x04, 0x00, 0x00, 0x2f, 0x2a, 0x0a, 0x20, 0x20, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x54, 0x68, 0x69, + 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x69, 0x73, 0x20, 0x70, 0x61, + 0x72, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x59, 0x55, + 0x50, 0x20, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x2e, 0x0a, 0x20, + 0x20, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, + 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x32, 0x36, 0x20, 0x2d, 0x20, 0x6b, + 0x75, 0x6e, 0x69, 0x74, 0x6f, 0x6b, 0x69, 0x40, 0x67, 0x6d, 0x61, 0x69, + 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x59, 0x55, + 0x50, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x6f, 0x70, 0x65, 0x6e, + 0x20, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x6c, 0x69, 0x62, 0x72, + 0x61, 0x72, 0x79, 0x20, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, + 0x74, 0x6f, 0x20, 0x6f, 0x70, 0x65, 0x6e, 0x2d, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x20, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x69, 0x6e, 0x67, + 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x54, 0x68, 0x65, 0x20, 0x63, 0x6f, + 0x64, 0x65, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x20, + 0x69, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, + 0x20, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, + 0x20, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, + 0x65, 0x72, 0x6d, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x49, 0x53, 0x43, 0x20, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a, + 0x20, 0x20, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, + 0x77, 0x2e, 0x69, 0x73, 0x63, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x64, 0x6f, + 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x2f, 0x73, 0x6f, 0x66, 0x74, + 0x77, 0x61, 0x72, 0x65, 0x2d, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, + 0x2d, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2f, 0x69, 0x73, 0x63, 0x2d, + 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x2e, 0x20, 0x50, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x74, + 0x6f, 0x20, 0x75, 0x73, 0x65, 0x2c, 0x20, 0x63, 0x6f, 0x70, 0x79, 0x2c, + 0x20, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x2c, 0x20, 0x61, 0x6e, 0x64, + 0x2f, 0x6f, 0x72, 0x20, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x73, 0x6f, 0x66, 0x74, + 0x77, 0x61, 0x72, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x6e, 0x79, + 0x20, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x20, 0x77, 0x69, 0x74, + 0x68, 0x20, 0x6f, 0x72, 0x0a, 0x20, 0x20, 0x20, 0x77, 0x69, 0x74, 0x68, + 0x6f, 0x75, 0x74, 0x20, 0x66, 0x65, 0x65, 0x20, 0x69, 0x73, 0x20, 0x68, + 0x65, 0x72, 0x65, 0x62, 0x79, 0x20, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, + 0x64, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x74, + 0x68, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x62, 0x6f, 0x76, + 0x65, 0x20, 0x63, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, + 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x0a, 0x20, + 0x20, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, + 0x20, 0x61, 0x70, 0x70, 0x65, 0x61, 0x72, 0x20, 0x69, 0x6e, 0x20, 0x61, + 0x6c, 0x6c, 0x20, 0x63, 0x6f, 0x70, 0x69, 0x65, 0x73, 0x2e, 0x0a, 0x0a, + 0x20, 0x20, 0x20, 0x59, 0x55, 0x50, 0x20, 0x49, 0x53, 0x20, 0x50, 0x52, + 0x4f, 0x56, 0x49, 0x44, 0x45, 0x44, 0x20, 0x22, 0x41, 0x53, 0x20, 0x49, + 0x53, 0x22, 0x20, 0x57, 0x49, 0x54, 0x48, 0x4f, 0x55, 0x54, 0x20, 0x41, + 0x4e, 0x59, 0x20, 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x59, 0x2c, + 0x20, 0x41, 0x4e, 0x44, 0x20, 0x41, 0x4c, 0x4c, 0x20, 0x57, 0x41, 0x52, + 0x52, 0x41, 0x4e, 0x54, 0x49, 0x45, 0x53, 0x2c, 0x20, 0x57, 0x48, 0x45, + 0x54, 0x48, 0x45, 0x52, 0x0a, 0x20, 0x20, 0x20, 0x45, 0x58, 0x50, 0x52, + 0x45, 0x53, 0x53, 0x45, 0x44, 0x20, 0x4f, 0x52, 0x20, 0x49, 0x4d, 0x50, + 0x4c, 0x49, 0x45, 0x44, 0x2c, 0x20, 0x49, 0x4e, 0x43, 0x4c, 0x55, 0x44, + 0x49, 0x4e, 0x47, 0x20, 0x4d, 0x45, 0x52, 0x43, 0x48, 0x41, 0x4e, 0x54, + 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x20, 0x41, 0x4e, 0x44, 0x20, + 0x46, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x20, 0x46, 0x4f, 0x52, 0x20, + 0x50, 0x55, 0x52, 0x50, 0x4f, 0x53, 0x45, 0x2c, 0x20, 0x41, 0x52, 0x45, + 0x0a, 0x20, 0x20, 0x20, 0x44, 0x49, 0x53, 0x43, 0x4c, 0x41, 0x49, 0x4d, + 0x45, 0x44, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x0a, 0x2a, 0x2f, 0x0a, 0x0a, 0x23, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x20, 0x34, 0x35, 0x30, 0x0a, 0x0a, 0x76, 0x6f, 0x69, 0x64, + 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x78, 0x20, 0x3d, 0x20, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x67, 0x6c, 0x5f, 0x56, 0x65, + 0x72, 0x74, 0x65, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x26, 0x20, + 0x31, 0x75, 0x29, 0x20, 0x3c, 0x3c, 0x20, 0x32, 0x75, 0x29, 0x20, 0x2d, + 0x20, 0x31, 0x2e, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x20, 0x79, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x28, 0x28, 0x67, 0x6c, 0x5f, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, + 0x49, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x26, 0x20, 0x32, 0x75, 0x29, 0x20, + 0x3c, 0x3c, 0x20, 0x31, 0x75, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x2e, 0x30, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x67, 0x6c, 0x5f, 0x50, 0x6f, 0x73, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, + 0x28, 0x78, 0x2c, 0x20, 0x79, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, + 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x7d, 0x0a, 0x00, 0x52, 0x45, 0x46, + 0x4c, 0x9b, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, + 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x29, 0x00, 0x00, 0x00, 0x5f, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, + 0x45, 0x44, 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x45, + 0x52, 0x5f, 0x46, 0x49, 0x58, 0x55, 0x50, 0x5f, 0x67, 0x6c, 0x5f, 0x56, + 0x65, 0x72, 0x74, 0x65, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x5f, 0x52, 0x45, + 0x53, 0x45, 0x52, 0x56, 0x45, 0x44, 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, + 0x49, 0x46, 0x49, 0x45, 0x52, 0x5f, 0x46, 0x49, 0x58, 0x55, 0x50, 0x5f, + 0x67, 0x6c, 0x5f, 0x50, 0x65, 0x72, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x0c, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x27, + 0x00, 0x00, 0x00, 0x5f, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x45, 0x44, + 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x45, 0x52, 0x5f, + 0x46, 0x49, 0x58, 0x55, 0x50, 0x5f, 0x67, 0x6c, 0x5f, 0x50, 0x65, 0x72, + 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x21, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x03, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x5f, 0x52, 0x45, + 0x53, 0x45, 0x52, 0x56, 0x45, 0x44, 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, + 0x49, 0x46, 0x49, 0x45, 0x52, 0x5f, 0x46, 0x49, 0x58, 0x55, 0x50, 0x5f, + 0x67, 0x6c, 0x5f, 0x50, 0x65, 0x72, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x0c, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x27, + 0x00, 0x00, 0x00, 0x5f, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x45, 0x44, + 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x45, 0x52, 0x5f, + 0x46, 0x49, 0x58, 0x55, 0x50, 0x5f, 0x67, 0x6c, 0x5f, 0x50, 0x65, 0x72, + 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x21, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x43, + 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x28, 0x31, 0x29, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x41, 0x52, 0x54, 0xb8, 0x08, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, + 0x69, 0x6e, 0xe4, 0x00, 0x00, 0x00, 0x23, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x20, 0x33, 0x30, 0x30, 0x20, 0x65, 0x73, 0x0a, 0x0a, 0x76, + 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x78, + 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x75, 0x69, + 0x6e, 0x74, 0x28, 0x67, 0x6c, 0x5f, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, + 0x49, 0x44, 0x29, 0x20, 0x26, 0x20, 0x31, 0x75, 0x29, 0x20, 0x3c, 0x3c, + 0x20, 0x32, 0x75, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x2e, 0x30, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x79, 0x20, + 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x75, 0x69, 0x6e, + 0x74, 0x28, 0x67, 0x6c, 0x5f, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x49, + 0x44, 0x29, 0x20, 0x26, 0x20, 0x32, 0x75, 0x29, 0x20, 0x3c, 0x3c, 0x20, + 0x31, 0x75, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x2e, 0x30, 0x3b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x67, 0x6c, 0x5f, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x78, 0x2c, + 0x20, 0x79, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, + 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x67, 0x6c, 0x5f, 0x50, 0x6f, + 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x79, 0x20, 0x3d, 0x20, 0x2d, + 0x67, 0x6c, 0x5f, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, + 0x79, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x49, 0x53, 0x52, 0x43, 0x17, 0x04, + 0x00, 0x00, 0x2f, 0x2a, 0x0a, 0x20, 0x20, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x66, + 0x69, 0x6c, 0x65, 0x20, 0x69, 0x73, 0x20, 0x70, 0x61, 0x72, 0x74, 0x20, + 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x59, 0x55, 0x50, 0x20, 0x6c, + 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x43, + 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x63, 0x29, + 0x20, 0x32, 0x30, 0x32, 0x36, 0x20, 0x2d, 0x20, 0x6b, 0x75, 0x6e, 0x69, + 0x74, 0x6f, 0x6b, 0x69, 0x40, 0x67, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x63, + 0x6f, 0x6d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x59, 0x55, 0x50, 0x20, 0x69, + 0x73, 0x20, 0x61, 0x6e, 0x20, 0x6f, 0x70, 0x65, 0x6e, 0x20, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x20, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, + 0x20, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x74, 0x6f, 0x20, + 0x6f, 0x70, 0x65, 0x6e, 0x2d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, + 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x69, 0x6e, 0x67, 0x2e, 0x0a, 0x0a, + 0x20, 0x20, 0x20, 0x54, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, + 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, + 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x69, 0x73, + 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x75, 0x6e, + 0x64, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x65, 0x72, 0x6d, + 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x49, 0x53, 0x43, + 0x20, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, + 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, + 0x73, 0x63, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, + 0x6f, 0x61, 0x64, 0x73, 0x2f, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, + 0x65, 0x2d, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x2d, 0x70, 0x6f, + 0x6c, 0x69, 0x63, 0x79, 0x2f, 0x69, 0x73, 0x63, 0x2d, 0x6c, 0x69, 0x63, + 0x65, 0x6e, 0x73, 0x65, 0x2e, 0x20, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x74, 0x6f, 0x20, 0x75, + 0x73, 0x65, 0x2c, 0x20, 0x63, 0x6f, 0x70, 0x79, 0x2c, 0x20, 0x6d, 0x6f, + 0x64, 0x69, 0x66, 0x79, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x2f, 0x6f, 0x72, + 0x20, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, + 0x74, 0x68, 0x69, 0x73, 0x20, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, + 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x70, 0x75, + 0x72, 0x70, 0x6f, 0x73, 0x65, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, + 0x72, 0x0a, 0x20, 0x20, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, + 0x20, 0x66, 0x65, 0x65, 0x20, 0x69, 0x73, 0x20, 0x68, 0x65, 0x72, 0x65, + 0x62, 0x79, 0x20, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x20, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x74, 0x68, 0x61, 0x74, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, 0x20, 0x63, + 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x6e, 0x6f, 0x74, + 0x69, 0x63, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x74, + 0x68, 0x69, 0x73, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x20, 0x61, 0x70, + 0x70, 0x65, 0x61, 0x72, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x6c, 0x6c, 0x20, + 0x63, 0x6f, 0x70, 0x69, 0x65, 0x73, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, + 0x59, 0x55, 0x50, 0x20, 0x49, 0x53, 0x20, 0x50, 0x52, 0x4f, 0x56, 0x49, + 0x44, 0x45, 0x44, 0x20, 0x22, 0x41, 0x53, 0x20, 0x49, 0x53, 0x22, 0x20, + 0x57, 0x49, 0x54, 0x48, 0x4f, 0x55, 0x54, 0x20, 0x41, 0x4e, 0x59, 0x20, + 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x59, 0x2c, 0x20, 0x41, 0x4e, + 0x44, 0x20, 0x41, 0x4c, 0x4c, 0x20, 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, + 0x54, 0x49, 0x45, 0x53, 0x2c, 0x20, 0x57, 0x48, 0x45, 0x54, 0x48, 0x45, + 0x52, 0x0a, 0x20, 0x20, 0x20, 0x45, 0x58, 0x50, 0x52, 0x45, 0x53, 0x53, + 0x45, 0x44, 0x20, 0x4f, 0x52, 0x20, 0x49, 0x4d, 0x50, 0x4c, 0x49, 0x45, + 0x44, 0x2c, 0x20, 0x49, 0x4e, 0x43, 0x4c, 0x55, 0x44, 0x49, 0x4e, 0x47, + 0x20, 0x4d, 0x45, 0x52, 0x43, 0x48, 0x41, 0x4e, 0x54, 0x41, 0x42, 0x49, + 0x4c, 0x49, 0x54, 0x59, 0x20, 0x41, 0x4e, 0x44, 0x20, 0x46, 0x49, 0x54, + 0x4e, 0x45, 0x53, 0x53, 0x20, 0x46, 0x4f, 0x52, 0x20, 0x50, 0x55, 0x52, + 0x50, 0x4f, 0x53, 0x45, 0x2c, 0x20, 0x41, 0x52, 0x45, 0x0a, 0x20, 0x20, + 0x20, 0x44, 0x49, 0x53, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x45, 0x44, 0x2e, + 0x0a, 0x0a, 0x20, 0x20, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x0a, 0x2a, + 0x2f, 0x0a, 0x0a, 0x23, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, + 0x34, 0x35, 0x30, 0x0a, 0x0a, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, + 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x78, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x28, 0x28, 0x67, 0x6c, 0x5f, 0x56, 0x65, 0x72, 0x74, 0x65, + 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x26, 0x20, 0x31, 0x75, 0x29, + 0x20, 0x3c, 0x3c, 0x20, 0x32, 0x75, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x2e, + 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x20, 0x79, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, + 0x67, 0x6c, 0x5f, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x49, 0x6e, 0x64, + 0x65, 0x78, 0x20, 0x26, 0x20, 0x32, 0x75, 0x29, 0x20, 0x3c, 0x3c, 0x20, + 0x31, 0x75, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x2e, 0x30, 0x3b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x67, 0x6c, 0x5f, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x78, 0x2c, + 0x20, 0x79, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, + 0x29, 0x3b, 0x0a, 0x7d, 0x0a, 0x00, 0x52, 0x45, 0x46, 0x4c, 0x9b, 0x03, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x07, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x29, 0x00, + 0x00, 0x00, 0x5f, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x45, 0x44, 0x5f, + 0x49, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x45, 0x52, 0x5f, 0x46, + 0x49, 0x58, 0x55, 0x50, 0x5f, 0x67, 0x6c, 0x5f, 0x56, 0x65, 0x72, 0x74, + 0x65, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x5f, 0x52, 0x45, 0x53, 0x45, 0x52, + 0x56, 0x45, 0x44, 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, + 0x45, 0x52, 0x5f, 0x46, 0x49, 0x58, 0x55, 0x50, 0x5f, 0x67, 0x6c, 0x5f, + 0x50, 0x65, 0x72, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x5f, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x45, 0x44, 0x5f, 0x49, 0x44, + 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x45, 0x52, 0x5f, 0x46, 0x49, 0x58, + 0x55, 0x50, 0x5f, 0x67, 0x6c, 0x5f, 0x50, 0x65, 0x72, 0x56, 0x65, 0x72, + 0x74, 0x65, 0x78, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0x00, + 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x5f, 0x52, 0x45, 0x53, 0x45, 0x52, + 0x56, 0x45, 0x44, 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, + 0x45, 0x52, 0x5f, 0x46, 0x49, 0x58, 0x55, 0x50, 0x5f, 0x67, 0x6c, 0x5f, + 0x50, 0x65, 0x72, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x5f, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x45, 0x44, 0x5f, 0x49, 0x44, + 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x45, 0x52, 0x5f, 0x46, 0x49, 0x58, + 0x55, 0x50, 0x5f, 0x67, 0x6c, 0x5f, 0x50, 0x65, 0x72, 0x56, 0x65, 0x72, + 0x74, 0x65, 0x78, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x43, 0x61, 0x70, 0x61, + 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x28, 0x31, 0x29, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x56, 0x41, 0x52, 0x54, 0x42, 0x0a, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x6e, 0x02, + 0x00, 0x00, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x34, 0x20, 0x67, 0x6c, 0x5f, 0x50, 0x6f, 0x73, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x3b, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x20, + 0x69, 0x6e, 0x74, 0x20, 0x67, 0x6c, 0x5f, 0x56, 0x65, 0x72, 0x74, 0x65, + 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x3b, 0x0a, 0x73, 0x74, 0x72, 0x75, + 0x63, 0x74, 0x20, 0x53, 0x50, 0x49, 0x52, 0x56, 0x5f, 0x43, 0x72, 0x6f, + 0x73, 0x73, 0x5f, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x0a, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x20, 0x67, 0x6c, 0x5f, 0x56, + 0x65, 0x72, 0x74, 0x65, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x3a, + 0x20, 0x53, 0x56, 0x5f, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x49, 0x44, + 0x3b, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, + 0x20, 0x53, 0x50, 0x49, 0x52, 0x56, 0x5f, 0x43, 0x72, 0x6f, 0x73, 0x73, + 0x5f, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x0a, 0x7b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x67, 0x6c, 0x5f, + 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3a, 0x20, 0x53, + 0x56, 0x5f, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3b, 0x0a, + 0x7d, 0x3b, 0x0a, 0x0a, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x76, 0x65, 0x72, + 0x74, 0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x78, 0x20, 0x3d, + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x75, 0x69, 0x6e, 0x74, + 0x28, 0x67, 0x6c, 0x5f, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x49, 0x6e, + 0x64, 0x65, 0x78, 0x29, 0x20, 0x26, 0x20, 0x31, 0x75, 0x29, 0x20, 0x3c, + 0x3c, 0x20, 0x32, 0x75, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x2e, 0x30, 0x66, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, + 0x79, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x75, + 0x69, 0x6e, 0x74, 0x28, 0x67, 0x6c, 0x5f, 0x56, 0x65, 0x72, 0x74, 0x65, + 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x29, 0x20, 0x26, 0x20, 0x32, 0x75, + 0x29, 0x20, 0x3c, 0x3c, 0x20, 0x31, 0x75, 0x29, 0x20, 0x2d, 0x20, 0x31, + 0x2e, 0x30, 0x66, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x67, 0x6c, 0x5f, + 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x28, 0x78, 0x2c, 0x20, 0x79, 0x2c, 0x20, + 0x30, 0x2e, 0x30, 0x66, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x66, 0x29, 0x3b, + 0x0a, 0x7d, 0x0a, 0x0a, 0x53, 0x50, 0x49, 0x52, 0x56, 0x5f, 0x43, 0x72, + 0x6f, 0x73, 0x73, 0x5f, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x20, 0x6d, + 0x61, 0x69, 0x6e, 0x28, 0x53, 0x50, 0x49, 0x52, 0x56, 0x5f, 0x43, 0x72, + 0x6f, 0x73, 0x73, 0x5f, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x73, 0x74, + 0x61, 0x67, 0x65, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x29, 0x0a, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x67, 0x6c, 0x5f, 0x56, 0x65, 0x72, 0x74, + 0x65, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x3d, 0x20, 0x69, 0x6e, + 0x74, 0x28, 0x73, 0x74, 0x61, 0x67, 0x65, 0x5f, 0x69, 0x6e, 0x70, 0x75, + 0x74, 0x2e, 0x67, 0x6c, 0x5f, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x49, + 0x6e, 0x64, 0x65, 0x78, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, + 0x65, 0x72, 0x74, 0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x53, 0x50, 0x49, 0x52, 0x56, 0x5f, 0x43, 0x72, + 0x6f, 0x73, 0x73, 0x5f, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x20, 0x73, + 0x74, 0x61, 0x67, 0x65, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x61, 0x67, 0x65, 0x5f, 0x6f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x2e, 0x67, 0x6c, 0x5f, 0x50, 0x6f, 0x73, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x67, 0x6c, 0x5f, 0x50, + 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x73, 0x74, 0x61, 0x67, + 0x65, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x3b, 0x0a, 0x7d, 0x0a, + 0x49, 0x53, 0x52, 0x43, 0x17, 0x04, 0x00, 0x00, 0x2f, 0x2a, 0x0a, 0x20, + 0x20, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x0a, 0x0a, 0x20, 0x20, 0x20, + 0x54, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x69, 0x73, + 0x20, 0x70, 0x61, 0x72, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x59, 0x55, 0x50, 0x20, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, + 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, + 0x68, 0x74, 0x20, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x32, 0x36, 0x20, + 0x2d, 0x20, 0x6b, 0x75, 0x6e, 0x69, 0x74, 0x6f, 0x6b, 0x69, 0x40, 0x67, + 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x0a, 0x0a, 0x20, 0x20, + 0x20, 0x59, 0x55, 0x50, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x6f, + 0x70, 0x65, 0x6e, 0x20, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x6c, + 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x20, 0x73, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x6f, 0x70, 0x65, 0x6e, 0x2d, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, + 0x69, 0x6e, 0x67, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x54, 0x68, 0x65, + 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, + 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, + 0x69, 0x6c, 0x65, 0x20, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x64, 0x65, 0x64, 0x20, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x74, 0x65, 0x72, 0x6d, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x49, 0x53, 0x43, 0x20, 0x6c, 0x69, 0x63, 0x65, 0x6e, + 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, + 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x73, 0x63, 0x2e, 0x6f, 0x72, 0x67, + 0x2f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x2f, 0x73, + 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x2d, 0x73, 0x75, 0x70, 0x70, + 0x6f, 0x72, 0x74, 0x2d, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2f, 0x69, + 0x73, 0x63, 0x2d, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x2e, 0x20, + 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x0a, 0x20, + 0x20, 0x20, 0x74, 0x6f, 0x20, 0x75, 0x73, 0x65, 0x2c, 0x20, 0x63, 0x6f, + 0x70, 0x79, 0x2c, 0x20, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x2c, 0x20, + 0x61, 0x6e, 0x64, 0x2f, 0x6f, 0x72, 0x20, 0x64, 0x69, 0x73, 0x74, 0x72, + 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x73, + 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, + 0x61, 0x6e, 0x79, 0x20, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x20, + 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x72, 0x0a, 0x20, 0x20, 0x20, 0x77, + 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x66, 0x65, 0x65, 0x20, 0x69, + 0x73, 0x20, 0x68, 0x65, 0x72, 0x65, 0x62, 0x79, 0x20, 0x67, 0x72, 0x61, + 0x6e, 0x74, 0x65, 0x64, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x64, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, + 0x62, 0x6f, 0x76, 0x65, 0x20, 0x63, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, + 0x68, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x20, 0x61, 0x6e, + 0x64, 0x0a, 0x20, 0x20, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x70, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6e, 0x6f, 0x74, + 0x69, 0x63, 0x65, 0x20, 0x61, 0x70, 0x70, 0x65, 0x61, 0x72, 0x20, 0x69, + 0x6e, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x63, 0x6f, 0x70, 0x69, 0x65, 0x73, + 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x59, 0x55, 0x50, 0x20, 0x49, 0x53, + 0x20, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x44, 0x45, 0x44, 0x20, 0x22, 0x41, + 0x53, 0x20, 0x49, 0x53, 0x22, 0x20, 0x57, 0x49, 0x54, 0x48, 0x4f, 0x55, + 0x54, 0x20, 0x41, 0x4e, 0x59, 0x20, 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, + 0x54, 0x59, 0x2c, 0x20, 0x41, 0x4e, 0x44, 0x20, 0x41, 0x4c, 0x4c, 0x20, + 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x49, 0x45, 0x53, 0x2c, 0x20, + 0x57, 0x48, 0x45, 0x54, 0x48, 0x45, 0x52, 0x0a, 0x20, 0x20, 0x20, 0x45, + 0x58, 0x50, 0x52, 0x45, 0x53, 0x53, 0x45, 0x44, 0x20, 0x4f, 0x52, 0x20, + 0x49, 0x4d, 0x50, 0x4c, 0x49, 0x45, 0x44, 0x2c, 0x20, 0x49, 0x4e, 0x43, + 0x4c, 0x55, 0x44, 0x49, 0x4e, 0x47, 0x20, 0x4d, 0x45, 0x52, 0x43, 0x48, + 0x41, 0x4e, 0x54, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x20, 0x41, + 0x4e, 0x44, 0x20, 0x46, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x20, 0x46, + 0x4f, 0x52, 0x20, 0x50, 0x55, 0x52, 0x50, 0x4f, 0x53, 0x45, 0x2c, 0x20, + 0x41, 0x52, 0x45, 0x0a, 0x20, 0x20, 0x20, 0x44, 0x49, 0x53, 0x43, 0x4c, + 0x41, 0x49, 0x4d, 0x45, 0x44, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x0a, 0x2a, 0x2f, 0x0a, 0x0a, 0x23, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x34, 0x35, 0x30, 0x0a, 0x0a, 0x76, + 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x78, + 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x67, 0x6c, + 0x5f, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, + 0x20, 0x26, 0x20, 0x31, 0x75, 0x29, 0x20, 0x3c, 0x3c, 0x20, 0x32, 0x75, + 0x29, 0x20, 0x2d, 0x20, 0x31, 0x2e, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x79, 0x20, 0x3d, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x67, 0x6c, 0x5f, 0x56, 0x65, 0x72, + 0x74, 0x65, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x26, 0x20, 0x32, + 0x75, 0x29, 0x20, 0x3c, 0x3c, 0x20, 0x31, 0x75, 0x29, 0x20, 0x2d, 0x20, + 0x31, 0x2e, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x67, 0x6c, 0x5f, + 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x76, + 0x65, 0x63, 0x34, 0x28, 0x78, 0x2c, 0x20, 0x79, 0x2c, 0x20, 0x30, 0x2e, + 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x7d, 0x0a, 0x00, + 0x52, 0x45, 0x46, 0x4c, 0x9b, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x5f, 0x52, 0x45, 0x53, + 0x45, 0x52, 0x56, 0x45, 0x44, 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, 0x49, + 0x46, 0x49, 0x45, 0x52, 0x5f, 0x46, 0x49, 0x58, 0x55, 0x50, 0x5f, 0x67, + 0x6c, 0x5f, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x49, 0x6e, 0x64, 0x65, + 0x78, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x5f, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x45, 0x44, 0x5f, 0x49, 0x44, + 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x45, 0x52, 0x5f, 0x46, 0x49, 0x58, + 0x55, 0x50, 0x5f, 0x67, 0x6c, 0x5f, 0x50, 0x65, 0x72, 0x56, 0x65, 0x72, + 0x74, 0x65, 0x78, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x02, 0x00, + 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x5f, 0x52, 0x45, 0x53, 0x45, 0x52, + 0x56, 0x45, 0x44, 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, + 0x45, 0x52, 0x5f, 0x46, 0x49, 0x58, 0x55, 0x50, 0x5f, 0x67, 0x6c, 0x5f, + 0x50, 0x65, 0x72, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x03, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x5f, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x45, 0x44, 0x5f, 0x49, 0x44, + 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x45, 0x52, 0x5f, 0x46, 0x49, 0x58, + 0x55, 0x50, 0x5f, 0x67, 0x6c, 0x5f, 0x50, 0x65, 0x72, 0x56, 0x65, 0x72, + 0x74, 0x65, 0x78, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x04, 0x00, + 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x5f, 0x52, 0x45, 0x53, 0x45, 0x52, + 0x56, 0x45, 0x44, 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, + 0x45, 0x52, 0x5f, 0x46, 0x49, 0x58, 0x55, 0x50, 0x5f, 0x67, 0x6c, 0x5f, + 0x50, 0x65, 0x72, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, + 0x00, 0x00, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, + 0x28, 0x31, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x41, 0x52, 0x54, + 0x73, 0x09, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x6d, 0x61, 0x69, 0x6e, 0x9f, 0x01, 0x00, 0x00, 0x23, 0x69, 0x6e, 0x63, + 0x6c, 0x75, 0x64, 0x65, 0x20, 0x3c, 0x6d, 0x65, 0x74, 0x61, 0x6c, 0x5f, + 0x73, 0x74, 0x64, 0x6c, 0x69, 0x62, 0x3e, 0x0a, 0x23, 0x69, 0x6e, 0x63, + 0x6c, 0x75, 0x64, 0x65, 0x20, 0x3c, 0x73, 0x69, 0x6d, 0x64, 0x2f, 0x73, + 0x69, 0x6d, 0x64, 0x2e, 0x68, 0x3e, 0x0a, 0x0a, 0x75, 0x73, 0x69, 0x6e, + 0x67, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x20, + 0x6d, 0x65, 0x74, 0x61, 0x6c, 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, + 0x63, 0x74, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x30, 0x5f, 0x6f, 0x75, 0x74, + 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x34, 0x20, 0x67, 0x6c, 0x5f, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x5b, 0x5b, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x5d, 0x5d, 0x3b, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x76, 0x65, 0x72, 0x74, + 0x65, 0x78, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x30, 0x5f, 0x6f, 0x75, 0x74, + 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x30, 0x28, 0x75, 0x69, 0x6e, 0x74, 0x20, + 0x67, 0x6c, 0x5f, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x49, 0x6e, 0x64, + 0x65, 0x78, 0x20, 0x5b, 0x5b, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x5f, + 0x69, 0x64, 0x5d, 0x5d, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x6d, 0x61, 0x69, 0x6e, 0x30, 0x5f, 0x6f, 0x75, 0x74, 0x20, 0x6f, 0x75, + 0x74, 0x20, 0x3d, 0x20, 0x7b, 0x7d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x78, 0x20, 0x3d, 0x20, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x28, 0x28, 0x75, 0x69, 0x6e, 0x74, 0x28, 0x69, 0x6e, + 0x74, 0x28, 0x67, 0x6c, 0x5f, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x49, + 0x6e, 0x64, 0x65, 0x78, 0x29, 0x29, 0x20, 0x26, 0x20, 0x31, 0x75, 0x29, + 0x20, 0x3c, 0x3c, 0x20, 0x32, 0x75, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x2e, + 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x20, 0x79, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, + 0x75, 0x69, 0x6e, 0x74, 0x28, 0x69, 0x6e, 0x74, 0x28, 0x67, 0x6c, 0x5f, + 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x29, + 0x29, 0x20, 0x26, 0x20, 0x32, 0x75, 0x29, 0x20, 0x3c, 0x3c, 0x20, 0x31, + 0x75, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x2e, 0x30, 0x3b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x6f, 0x75, 0x74, 0x2e, 0x67, 0x6c, 0x5f, 0x50, 0x6f, 0x73, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x34, 0x28, 0x78, 0x2c, 0x20, 0x79, 0x2c, 0x20, 0x30, 0x2e, 0x30, + 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6f, 0x75, 0x74, 0x3b, 0x0a, + 0x7d, 0x0a, 0x0a, 0x49, 0x53, 0x52, 0x43, 0x17, 0x04, 0x00, 0x00, 0x2f, + 0x2a, 0x0a, 0x20, 0x20, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x0a, 0x0a, + 0x20, 0x20, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, + 0x20, 0x69, 0x73, 0x20, 0x70, 0x61, 0x72, 0x74, 0x20, 0x6f, 0x66, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x59, 0x55, 0x50, 0x20, 0x6c, 0x69, 0x62, 0x72, + 0x61, 0x72, 0x79, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x43, 0x6f, 0x70, 0x79, + 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, + 0x32, 0x36, 0x20, 0x2d, 0x20, 0x6b, 0x75, 0x6e, 0x69, 0x74, 0x6f, 0x6b, + 0x69, 0x40, 0x67, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x0a, + 0x0a, 0x20, 0x20, 0x20, 0x59, 0x55, 0x50, 0x20, 0x69, 0x73, 0x20, 0x61, + 0x6e, 0x20, 0x6f, 0x70, 0x65, 0x6e, 0x20, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x20, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x20, 0x73, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x6f, 0x70, 0x65, + 0x6e, 0x2d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x6c, 0x69, 0x63, + 0x65, 0x6e, 0x73, 0x69, 0x6e, 0x67, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, + 0x54, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x69, 0x6e, 0x63, + 0x6c, 0x75, 0x64, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x69, + 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x69, 0x73, 0x20, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x75, 0x6e, 0x64, 0x65, 0x72, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x65, 0x72, 0x6d, 0x73, 0x20, 0x6f, + 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x49, 0x53, 0x43, 0x20, 0x6c, 0x69, + 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x68, 0x74, 0x74, + 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x73, 0x63, 0x2e, + 0x6f, 0x72, 0x67, 0x2f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, + 0x73, 0x2f, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x2d, 0x73, + 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x2d, 0x70, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x2f, 0x69, 0x73, 0x63, 0x2d, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, + 0x65, 0x2e, 0x20, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x74, 0x6f, 0x20, 0x75, 0x73, 0x65, 0x2c, + 0x20, 0x63, 0x6f, 0x70, 0x79, 0x2c, 0x20, 0x6d, 0x6f, 0x64, 0x69, 0x66, + 0x79, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x2f, 0x6f, 0x72, 0x20, 0x64, 0x69, + 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x74, 0x68, 0x69, + 0x73, 0x20, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x20, 0x66, + 0x6f, 0x72, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x70, 0x75, 0x72, 0x70, 0x6f, + 0x73, 0x65, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x72, 0x0a, 0x20, + 0x20, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x66, 0x65, + 0x65, 0x20, 0x69, 0x73, 0x20, 0x68, 0x65, 0x72, 0x65, 0x62, 0x79, 0x20, + 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x20, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x64, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, 0x20, 0x63, 0x6f, 0x70, 0x79, + 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, + 0x20, 0x61, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x74, 0x68, 0x69, 0x73, + 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x20, + 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x20, 0x61, 0x70, 0x70, 0x65, 0x61, + 0x72, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x63, 0x6f, 0x70, + 0x69, 0x65, 0x73, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x59, 0x55, 0x50, + 0x20, 0x49, 0x53, 0x20, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x44, 0x45, 0x44, + 0x20, 0x22, 0x41, 0x53, 0x20, 0x49, 0x53, 0x22, 0x20, 0x57, 0x49, 0x54, + 0x48, 0x4f, 0x55, 0x54, 0x20, 0x41, 0x4e, 0x59, 0x20, 0x57, 0x41, 0x52, + 0x52, 0x41, 0x4e, 0x54, 0x59, 0x2c, 0x20, 0x41, 0x4e, 0x44, 0x20, 0x41, + 0x4c, 0x4c, 0x20, 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x49, 0x45, + 0x53, 0x2c, 0x20, 0x57, 0x48, 0x45, 0x54, 0x48, 0x45, 0x52, 0x0a, 0x20, + 0x20, 0x20, 0x45, 0x58, 0x50, 0x52, 0x45, 0x53, 0x53, 0x45, 0x44, 0x20, + 0x4f, 0x52, 0x20, 0x49, 0x4d, 0x50, 0x4c, 0x49, 0x45, 0x44, 0x2c, 0x20, + 0x49, 0x4e, 0x43, 0x4c, 0x55, 0x44, 0x49, 0x4e, 0x47, 0x20, 0x4d, 0x45, + 0x52, 0x43, 0x48, 0x41, 0x4e, 0x54, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, + 0x59, 0x20, 0x41, 0x4e, 0x44, 0x20, 0x46, 0x49, 0x54, 0x4e, 0x45, 0x53, + 0x53, 0x20, 0x46, 0x4f, 0x52, 0x20, 0x50, 0x55, 0x52, 0x50, 0x4f, 0x53, + 0x45, 0x2c, 0x20, 0x41, 0x52, 0x45, 0x0a, 0x20, 0x20, 0x20, 0x44, 0x49, + 0x53, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x45, 0x44, 0x2e, 0x0a, 0x0a, 0x20, + 0x20, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x0a, 0x2a, 0x2f, 0x0a, 0x0a, + 0x23, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x34, 0x35, 0x30, + 0x0a, 0x0a, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, + 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x20, 0x78, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, + 0x28, 0x67, 0x6c, 0x5f, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x49, 0x6e, + 0x64, 0x65, 0x78, 0x20, 0x26, 0x20, 0x31, 0x75, 0x29, 0x20, 0x3c, 0x3c, + 0x20, 0x32, 0x75, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x2e, 0x30, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x79, 0x20, + 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x67, 0x6c, 0x5f, + 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x20, + 0x26, 0x20, 0x32, 0x75, 0x29, 0x20, 0x3c, 0x3c, 0x20, 0x31, 0x75, 0x29, + 0x20, 0x2d, 0x20, 0x31, 0x2e, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x67, 0x6c, 0x5f, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x78, 0x2c, 0x20, 0x79, 0x2c, + 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, + 0x7d, 0x0a, 0x00, 0x52, 0x45, 0x46, 0x4c, 0x9b, 0x03, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, + 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x5f, + 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x45, 0x44, 0x5f, 0x49, 0x44, 0x45, + 0x4e, 0x54, 0x49, 0x46, 0x49, 0x45, 0x52, 0x5f, 0x46, 0x49, 0x58, 0x55, + 0x50, 0x5f, 0x67, 0x6c, 0x5f, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x49, + 0x6e, 0x64, 0x65, 0x78, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x27, + 0x00, 0x00, 0x00, 0x5f, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x45, 0x44, + 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x45, 0x52, 0x5f, + 0x46, 0x49, 0x58, 0x55, 0x50, 0x5f, 0x67, 0x6c, 0x5f, 0x50, 0x65, 0x72, + 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x21, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x5f, 0x52, 0x45, + 0x53, 0x45, 0x52, 0x56, 0x45, 0x44, 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, + 0x49, 0x46, 0x49, 0x45, 0x52, 0x5f, 0x46, 0x49, 0x58, 0x55, 0x50, 0x5f, + 0x67, 0x6c, 0x5f, 0x50, 0x65, 0x72, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0x00, 0x00, 0x00, 0x0c, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x27, + 0x00, 0x00, 0x00, 0x5f, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x45, 0x44, + 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x45, 0x52, 0x5f, + 0x46, 0x49, 0x58, 0x55, 0x50, 0x5f, 0x67, 0x6c, 0x5f, 0x50, 0x65, 0x72, + 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x21, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x04, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x5f, 0x52, 0x45, + 0x53, 0x45, 0x52, 0x56, 0x45, 0x44, 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, + 0x49, 0x46, 0x49, 0x45, 0x52, 0x5f, 0x46, 0x49, 0x58, 0x55, 0x50, 0x5f, + 0x67, 0x6c, 0x5f, 0x50, 0x65, 0x72, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0d, 0x00, 0x00, 0x00, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, + 0x69, 0x74, 0x79, 0x28, 0x31, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x56, 0x41, 0x52, 0x54, 0x26, 0x0a, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0xda, 0x02, 0x00, 0x00, + 0x76, 0x61, 0x72, 0x3c, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x3e, + 0x20, 0x67, 0x6c, 0x5f, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x3a, 0x20, 0x76, 0x65, 0x63, 0x34, 0x3c, 0x66, 0x33, 0x32, 0x3e, 0x3b, + 0x0a, 0x76, 0x61, 0x72, 0x3c, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, + 0x3e, 0x20, 0x67, 0x6c, 0x5f, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x49, + 0x6e, 0x64, 0x65, 0x78, 0x3a, 0x20, 0x75, 0x33, 0x32, 0x3b, 0x0a, 0x76, + 0x61, 0x72, 0x3c, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x3e, 0x20, + 0x67, 0x6c, 0x5f, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, + 0x6e, 0x64, 0x65, 0x78, 0x3a, 0x20, 0x75, 0x33, 0x32, 0x3b, 0x0a, 0x66, + 0x6e, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, + 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, + 0x20, 0x78, 0x3a, 0x20, 0x66, 0x33, 0x32, 0x20, 0x3d, 0x20, 0x28, 0x66, + 0x33, 0x32, 0x28, 0x28, 0x28, 0x28, 0x67, 0x6c, 0x5f, 0x56, 0x65, 0x72, + 0x74, 0x65, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x26, 0x20, 0x31, + 0x75, 0x29, 0x29, 0x20, 0x3c, 0x3c, 0x20, 0x32, 0x75, 0x29, 0x29, 0x20, + 0x2d, 0x20, 0x31, 0x2e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x29, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x79, 0x3a, 0x20, + 0x66, 0x33, 0x32, 0x20, 0x3d, 0x20, 0x28, 0x66, 0x33, 0x32, 0x28, 0x28, + 0x28, 0x28, 0x67, 0x6c, 0x5f, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x49, + 0x6e, 0x64, 0x65, 0x78, 0x20, 0x26, 0x20, 0x32, 0x75, 0x29, 0x29, 0x20, + 0x3c, 0x3c, 0x20, 0x31, 0x75, 0x29, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x2e, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x67, 0x6c, 0x5f, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x3c, 0x66, 0x33, 0x32, 0x3e, + 0x28, 0x78, 0x2c, 0x20, 0x79, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x29, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, + 0x74, 0x20, 0x56, 0x53, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x40, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, + 0x28, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x5f, 0x69, 0x6e, 0x64, 0x65, + 0x78, 0x29, 0x20, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x5f, 0x69, 0x6e, + 0x64, 0x65, 0x78, 0x3a, 0x20, 0x75, 0x33, 0x32, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x40, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x28, 0x69, + 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, + 0x78, 0x29, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, + 0x69, 0x6e, 0x64, 0x65, 0x78, 0x3a, 0x20, 0x75, 0x33, 0x32, 0x2c, 0x0a, + 0x7d, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x56, 0x53, 0x4f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x40, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x28, 0x70, 0x6f, 0x73, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x29, 0x20, 0x70, 0x6f, 0x73, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x34, 0x3c, 0x66, 0x33, + 0x32, 0x3e, 0x2c, 0x0a, 0x7d, 0x0a, 0x40, 0x76, 0x65, 0x72, 0x74, 0x65, + 0x78, 0x0a, 0x66, 0x6e, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x69, 0x6e, + 0x70, 0x75, 0x74, 0x3a, 0x20, 0x56, 0x53, 0x49, 0x6e, 0x70, 0x75, 0x74, + 0x29, 0x20, 0x2d, 0x3e, 0x20, 0x56, 0x53, 0x4f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x67, 0x6c, 0x5f, 0x56, + 0x65, 0x72, 0x74, 0x65, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x3d, + 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2e, 0x76, 0x65, 0x72, 0x74, 0x65, + 0x78, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x67, 0x6c, 0x5f, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x49, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x70, 0x75, + 0x74, 0x2e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, + 0x6e, 0x64, 0x65, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, + 0x69, 0x6e, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x28, 0x29, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x6f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x3a, 0x20, 0x56, 0x53, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x2e, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3d, 0x20, + 0x67, 0x6c, 0x5f, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, + 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x3b, 0x0a, 0x7d, 0x0a, 0x49, 0x53, + 0x52, 0x43, 0x17, 0x04, 0x00, 0x00, 0x2f, 0x2a, 0x0a, 0x20, 0x20, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x54, 0x68, + 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x69, 0x73, 0x20, 0x70, + 0x61, 0x72, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x59, + 0x55, 0x50, 0x20, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x2e, 0x0a, + 0x20, 0x20, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, + 0x20, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x32, 0x36, 0x20, 0x2d, 0x20, + 0x6b, 0x75, 0x6e, 0x69, 0x74, 0x6f, 0x6b, 0x69, 0x40, 0x67, 0x6d, 0x61, + 0x69, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x59, + 0x55, 0x50, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x6f, 0x70, 0x65, + 0x6e, 0x20, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x6c, 0x69, 0x62, + 0x72, 0x61, 0x72, 0x79, 0x20, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x20, 0x74, 0x6f, 0x20, 0x6f, 0x70, 0x65, 0x6e, 0x2d, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x20, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x69, 0x6e, + 0x67, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x54, 0x68, 0x65, 0x20, 0x63, + 0x6f, 0x64, 0x65, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, + 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, + 0x65, 0x20, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x64, 0x20, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x74, 0x65, 0x72, 0x6d, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x49, 0x53, 0x43, 0x20, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, + 0x0a, 0x20, 0x20, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, + 0x77, 0x77, 0x2e, 0x69, 0x73, 0x63, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x64, + 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x2f, 0x73, 0x6f, 0x66, + 0x74, 0x77, 0x61, 0x72, 0x65, 0x2d, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, + 0x74, 0x2d, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2f, 0x69, 0x73, 0x63, + 0x2d, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x2e, 0x20, 0x50, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, + 0x74, 0x6f, 0x20, 0x75, 0x73, 0x65, 0x2c, 0x20, 0x63, 0x6f, 0x70, 0x79, + 0x2c, 0x20, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x2c, 0x20, 0x61, 0x6e, + 0x64, 0x2f, 0x6f, 0x72, 0x20, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, + 0x75, 0x74, 0x65, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x73, 0x6f, 0x66, + 0x74, 0x77, 0x61, 0x72, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x6e, + 0x79, 0x20, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x20, 0x77, 0x69, + 0x74, 0x68, 0x20, 0x6f, 0x72, 0x0a, 0x20, 0x20, 0x20, 0x77, 0x69, 0x74, + 0x68, 0x6f, 0x75, 0x74, 0x20, 0x66, 0x65, 0x65, 0x20, 0x69, 0x73, 0x20, + 0x68, 0x65, 0x72, 0x65, 0x62, 0x79, 0x20, 0x67, 0x72, 0x61, 0x6e, 0x74, + 0x65, 0x64, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, + 0x74, 0x68, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x62, 0x6f, + 0x76, 0x65, 0x20, 0x63, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, + 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x0a, + 0x20, 0x20, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x70, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x63, + 0x65, 0x20, 0x61, 0x70, 0x70, 0x65, 0x61, 0x72, 0x20, 0x69, 0x6e, 0x20, + 0x61, 0x6c, 0x6c, 0x20, 0x63, 0x6f, 0x70, 0x69, 0x65, 0x73, 0x2e, 0x0a, + 0x0a, 0x20, 0x20, 0x20, 0x59, 0x55, 0x50, 0x20, 0x49, 0x53, 0x20, 0x50, + 0x52, 0x4f, 0x56, 0x49, 0x44, 0x45, 0x44, 0x20, 0x22, 0x41, 0x53, 0x20, + 0x49, 0x53, 0x22, 0x20, 0x57, 0x49, 0x54, 0x48, 0x4f, 0x55, 0x54, 0x20, + 0x41, 0x4e, 0x59, 0x20, 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x59, + 0x2c, 0x20, 0x41, 0x4e, 0x44, 0x20, 0x41, 0x4c, 0x4c, 0x20, 0x57, 0x41, + 0x52, 0x52, 0x41, 0x4e, 0x54, 0x49, 0x45, 0x53, 0x2c, 0x20, 0x57, 0x48, + 0x45, 0x54, 0x48, 0x45, 0x52, 0x0a, 0x20, 0x20, 0x20, 0x45, 0x58, 0x50, + 0x52, 0x45, 0x53, 0x53, 0x45, 0x44, 0x20, 0x4f, 0x52, 0x20, 0x49, 0x4d, + 0x50, 0x4c, 0x49, 0x45, 0x44, 0x2c, 0x20, 0x49, 0x4e, 0x43, 0x4c, 0x55, + 0x44, 0x49, 0x4e, 0x47, 0x20, 0x4d, 0x45, 0x52, 0x43, 0x48, 0x41, 0x4e, + 0x54, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x20, 0x41, 0x4e, 0x44, + 0x20, 0x46, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x20, 0x46, 0x4f, 0x52, + 0x20, 0x50, 0x55, 0x52, 0x50, 0x4f, 0x53, 0x45, 0x2c, 0x20, 0x41, 0x52, + 0x45, 0x0a, 0x20, 0x20, 0x20, 0x44, 0x49, 0x53, 0x43, 0x4c, 0x41, 0x49, + 0x4d, 0x45, 0x44, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x0a, 0x2a, 0x2f, 0x0a, 0x0a, 0x23, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x20, 0x34, 0x35, 0x30, 0x0a, 0x0a, 0x76, 0x6f, 0x69, + 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x78, 0x20, 0x3d, + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x67, 0x6c, 0x5f, 0x56, + 0x65, 0x72, 0x74, 0x65, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x26, + 0x20, 0x31, 0x75, 0x29, 0x20, 0x3c, 0x3c, 0x20, 0x32, 0x75, 0x29, 0x20, + 0x2d, 0x20, 0x31, 0x2e, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x79, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x28, 0x28, 0x67, 0x6c, 0x5f, 0x56, 0x65, 0x72, 0x74, 0x65, + 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x26, 0x20, 0x32, 0x75, 0x29, + 0x20, 0x3c, 0x3c, 0x20, 0x31, 0x75, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x2e, + 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x67, 0x6c, 0x5f, 0x50, 0x6f, + 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, + 0x34, 0x28, 0x78, 0x2c, 0x20, 0x79, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, + 0x20, 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x7d, 0x0a, 0x00, 0x52, 0x45, + 0x46, 0x4c, 0x14, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, + 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x67, 0x6c, 0x5f, 0x56, 0x65, 0x72, + 0x74, 0x65, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x67, 0x6c, 0x5f, 0x50, 0x65, + 0x72, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x67, 0x6c, + 0x5f, 0x50, 0x65, 0x72, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, + 0x00, 0x67, 0x6c, 0x5f, 0x50, 0x65, 0x72, 0x56, 0x65, 0x72, 0x74, 0x65, + 0x78, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x67, 0x6c, 0x5f, 0x50, 0x65, 0x72, 0x56, 0x65, + 0x72, 0x74, 0x65, 0x78, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x43, 0x61, 0x70, + 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x28, 0x31, 0x29, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x53, 0x48, 0x44, 0x52, 0xd4, 0x92, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x50, 0x56, 0x42, 0x00, 0x00, + 0x00, 0x00, 0x4c, 0x49, 0x53, 0x54, 0xbc, 0x92, 0x00, 0x00, 0x56, 0x41, + 0x52, 0x53, 0x56, 0x41, 0x52, 0x54, 0x4a, 0x1c, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x34, 0x07, + 0x00, 0x00, 0x23, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x34, + 0x35, 0x30, 0x0a, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, 0x62, + 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x3d, 0x20, 0x33, 0x2c, 0x20, + 0x73, 0x74, 0x64, 0x31, 0x34, 0x30, 0x29, 0x20, 0x75, 0x6e, 0x69, 0x66, + 0x6f, 0x72, 0x6d, 0x20, 0x52, 0x6f, 0x77, 0x44, 0x61, 0x74, 0x61, 0x0a, + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x6d, + 0x61, 0x67, 0x73, 0x5b, 0x35, 0x31, 0x32, 0x5d, 0x3b, 0x0a, 0x7d, 0x20, + 0x72, 0x6f, 0x77, 0x73, 0x3b, 0x0a, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, + 0x74, 0x28, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x3d, 0x20, + 0x32, 0x2c, 0x20, 0x73, 0x74, 0x64, 0x31, 0x34, 0x30, 0x29, 0x20, 0x75, + 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x57, 0x61, 0x74, 0x65, 0x72, + 0x66, 0x61, 0x6c, 0x6c, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x0a, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x6e, + 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x62, 0x69, 0x6e, 0x73, 0x3b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, 0x61, 0x64, + 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x20, 0x70, 0x61, 0x64, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, 0x61, 0x64, 0x32, 0x3b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, 0x61, 0x64, + 0x33, 0x3b, 0x0a, 0x7d, 0x20, 0x70, 0x3b, 0x0a, 0x0a, 0x6c, 0x61, 0x79, + 0x6f, 0x75, 0x74, 0x28, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, + 0x3d, 0x20, 0x34, 0x2c, 0x20, 0x73, 0x74, 0x64, 0x31, 0x34, 0x30, 0x29, + 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x4c, 0x75, 0x74, + 0x44, 0x61, 0x74, 0x61, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x75, + 0x76, 0x65, 0x63, 0x34, 0x20, 0x6c, 0x75, 0x74, 0x5b, 0x36, 0x34, 0x5d, + 0x3b, 0x0a, 0x7d, 0x20, 0x6c, 0x75, 0x74, 0x3b, 0x0a, 0x0a, 0x6c, 0x61, + 0x79, 0x6f, 0x75, 0x74, 0x28, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x20, 0x3d, 0x20, 0x30, 0x29, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, + 0x6d, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, 0x44, 0x20, + 0x79, 0x75, 0x70, 0x5f, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x64, + 0x5f, 0x75, 0x5f, 0x70, 0x72, 0x65, 0x76, 0x5f, 0x75, 0x5f, 0x73, 0x61, + 0x6d, 0x70, 0x3b, 0x0a, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, + 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x30, + 0x29, 0x20, 0x6f, 0x75, 0x74, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x66, + 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x0a, 0x0a, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x66, 0x65, 0x74, 0x63, 0x68, 0x4d, 0x61, + 0x67, 0x28, 0x69, 0x6e, 0x74, 0x20, 0x69, 0x64, 0x78, 0x29, 0x0a, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, + 0x72, 0x6f, 0x77, 0x73, 0x2e, 0x6d, 0x61, 0x67, 0x73, 0x5b, 0x69, 0x64, + 0x78, 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x5d, 0x5b, 0x69, 0x64, 0x78, 0x20, + 0x26, 0x20, 0x33, 0x5d, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x76, 0x6f, 0x69, + 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x75, 0x76, 0x20, 0x3d, + 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, + 0x64, 0x2e, 0x78, 0x79, 0x20, 0x2f, 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, + 0x70, 0x2e, 0x77, 0x69, 0x64, 0x74, 0x68, 0x2c, 0x20, 0x70, 0x2e, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x69, 0x66, 0x20, 0x28, 0x75, 0x76, 0x2e, 0x79, 0x20, 0x3e, 0x3d, 0x20, + 0x28, 0x70, 0x2e, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, 0x2f, + 0x20, 0x70, 0x2e, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x29, 0x29, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x66, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, + 0x3d, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x79, 0x75, + 0x70, 0x5f, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x75, + 0x5f, 0x70, 0x72, 0x65, 0x76, 0x5f, 0x75, 0x5f, 0x73, 0x61, 0x6d, 0x70, + 0x2c, 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, 0x75, 0x76, 0x2e, 0x78, 0x2c, + 0x20, 0x75, 0x76, 0x2e, 0x79, 0x20, 0x2d, 0x20, 0x28, 0x70, 0x2e, 0x6e, + 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, 0x2f, 0x20, 0x70, 0x2e, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x29, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x78, 0x20, 0x3d, 0x20, 0x69, + 0x6e, 0x74, 0x28, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, + 0x6f, 0x72, 0x64, 0x2e, 0x78, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x79, 0x20, 0x3d, 0x20, + 0x69, 0x6e, 0x74, 0x28, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, + 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x79, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x72, 0x6f, 0x77, + 0x20, 0x3d, 0x20, 0x28, 0x69, 0x6e, 0x74, 0x28, 0x70, 0x2e, 0x6e, 0x75, + 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x29, 0x20, + 0x2d, 0x20, 0x79, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x62, 0x69, 0x6e, 0x50, 0x6f, + 0x73, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x78, 0x29, + 0x20, 0x2a, 0x20, 0x28, 0x70, 0x2e, 0x62, 0x69, 0x6e, 0x73, 0x20, 0x2f, + 0x20, 0x70, 0x2e, 0x77, 0x69, 0x64, 0x74, 0x68, 0x29, 0x3b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x62, + 0x69, 0x6e, 0x30, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x62, 0x69, + 0x6e, 0x50, 0x6f, 0x73, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x62, 0x69, 0x6e, 0x31, 0x20, + 0x3d, 0x20, 0x6d, 0x69, 0x6e, 0x28, 0x28, 0x62, 0x69, 0x6e, 0x30, 0x20, + 0x2b, 0x20, 0x31, 0x29, 0x2c, 0x20, 0x28, 0x69, 0x6e, 0x74, 0x28, 0x70, + 0x2e, 0x62, 0x69, 0x6e, 0x73, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x29, 0x29, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x20, 0x66, 0x72, 0x61, 0x63, 0x42, 0x69, 0x6e, 0x20, + 0x3d, 0x20, 0x62, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x20, 0x2d, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x62, 0x69, 0x6e, 0x30, 0x29, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, + 0x70, 0x61, 0x72, 0x61, 0x6d, 0x20, 0x3d, 0x20, 0x28, 0x72, 0x6f, 0x77, + 0x20, 0x2a, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x70, 0x2e, 0x62, 0x69, 0x6e, + 0x73, 0x29, 0x29, 0x20, 0x2b, 0x20, 0x62, 0x69, 0x6e, 0x30, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, + 0x70, 0x61, 0x72, 0x61, 0x6d, 0x5f, 0x31, 0x20, 0x3d, 0x20, 0x28, 0x72, + 0x6f, 0x77, 0x20, 0x2a, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x70, 0x2e, 0x62, + 0x69, 0x6e, 0x73, 0x29, 0x29, 0x20, 0x2b, 0x20, 0x62, 0x69, 0x6e, 0x31, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x20, 0x6d, 0x61, 0x67, 0x20, 0x3d, 0x20, 0x6d, 0x69, + 0x78, 0x28, 0x66, 0x65, 0x74, 0x63, 0x68, 0x4d, 0x61, 0x67, 0x28, 0x70, + 0x61, 0x72, 0x61, 0x6d, 0x29, 0x2c, 0x20, 0x66, 0x65, 0x74, 0x63, 0x68, + 0x4d, 0x61, 0x67, 0x28, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x5f, 0x31, 0x29, + 0x2c, 0x20, 0x66, 0x72, 0x61, 0x63, 0x42, 0x69, 0x6e, 0x29, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x20, 0x74, 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, + 0x6d, 0x61, 0x67, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, + 0x30, 0x29, 0x20, 0x2a, 0x20, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, + 0x69, 0x30, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x74, 0x29, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, + 0x20, 0x69, 0x31, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x6e, 0x28, 0x28, 0x69, + 0x30, 0x20, 0x2b, 0x20, 0x31, 0x29, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x29, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x20, 0x66, 0x72, 0x61, 0x63, 0x20, 0x3d, 0x20, 0x74, + 0x20, 0x2d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x69, 0x30, 0x29, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x75, 0x69, + 0x6e, 0x74, 0x20, 0x63, 0x30, 0x20, 0x3d, 0x20, 0x6c, 0x75, 0x74, 0x2e, + 0x6c, 0x75, 0x74, 0x5b, 0x69, 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x5d, + 0x5b, 0x69, 0x30, 0x20, 0x26, 0x20, 0x33, 0x5d, 0x3b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x20, 0x63, + 0x31, 0x20, 0x3d, 0x20, 0x6c, 0x75, 0x74, 0x2e, 0x6c, 0x75, 0x74, 0x5b, + 0x69, 0x31, 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x5d, 0x5b, 0x69, 0x31, 0x20, + 0x26, 0x20, 0x33, 0x5d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x63, 0x6f, 0x6c, 0x30, 0x20, + 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x28, 0x28, 0x63, 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x75, 0x69, 0x6e, 0x74, + 0x28, 0x31, 0x36, 0x29, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, + 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x30, + 0x20, 0x3e, 0x3e, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x28, 0x38, 0x29, 0x29, + 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x28, 0x63, 0x30, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, + 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, + 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x28, 0x32, 0x34, + 0x29, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x29, 0x20, + 0x2f, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x32, 0x35, 0x35, 0x2e, 0x30, + 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, + 0x65, 0x63, 0x34, 0x20, 0x63, 0x6f, 0x6c, 0x31, 0x20, 0x3d, 0x20, 0x76, + 0x65, 0x63, 0x34, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, + 0x31, 0x20, 0x3e, 0x3e, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x28, 0x31, 0x36, + 0x29, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x31, 0x20, 0x3e, 0x3e, + 0x20, 0x75, 0x69, 0x6e, 0x74, 0x28, 0x38, 0x29, 0x29, 0x20, 0x26, 0x20, + 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x28, 0x63, 0x31, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x31, 0x20, 0x3e, + 0x3e, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x28, 0x32, 0x34, 0x29, 0x29, 0x20, + 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x29, 0x20, 0x2f, 0x20, 0x76, + 0x65, 0x63, 0x34, 0x28, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x29, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x72, 0x61, 0x67, + 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x78, 0x28, + 0x63, 0x6f, 0x6c, 0x30, 0x2c, 0x20, 0x63, 0x6f, 0x6c, 0x31, 0x2c, 0x20, + 0x76, 0x65, 0x63, 0x34, 0x28, 0x66, 0x72, 0x61, 0x63, 0x29, 0x29, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x0a, 0x0a, 0x49, 0x53, + 0x52, 0x43, 0xe4, 0x0e, 0x00, 0x00, 0x2f, 0x2a, 0x0a, 0x20, 0x20, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x54, 0x68, + 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x69, 0x73, 0x20, 0x70, + 0x61, 0x72, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x59, + 0x55, 0x50, 0x20, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x2e, 0x0a, + 0x20, 0x20, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, + 0x20, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x32, 0x36, 0x20, 0x2d, 0x20, + 0x6b, 0x75, 0x6e, 0x69, 0x74, 0x6f, 0x6b, 0x69, 0x40, 0x67, 0x6d, 0x61, + 0x69, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x59, + 0x55, 0x50, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x6f, 0x70, 0x65, + 0x6e, 0x20, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x6c, 0x69, 0x62, + 0x72, 0x61, 0x72, 0x79, 0x20, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x20, 0x74, 0x6f, 0x20, 0x6f, 0x70, 0x65, 0x6e, 0x2d, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x20, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x69, 0x6e, + 0x67, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x54, 0x68, 0x65, 0x20, 0x63, + 0x6f, 0x64, 0x65, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, + 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, + 0x65, 0x20, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x64, 0x20, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x74, 0x65, 0x72, 0x6d, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x49, 0x53, 0x43, 0x20, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, + 0x0a, 0x20, 0x20, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, + 0x77, 0x77, 0x2e, 0x69, 0x73, 0x63, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x64, + 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x2f, 0x73, 0x6f, 0x66, + 0x74, 0x77, 0x61, 0x72, 0x65, 0x2d, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, + 0x74, 0x2d, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2f, 0x69, 0x73, 0x63, + 0x2d, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x2e, 0x20, 0x50, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, + 0x74, 0x6f, 0x20, 0x75, 0x73, 0x65, 0x2c, 0x20, 0x63, 0x6f, 0x70, 0x79, + 0x2c, 0x20, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x2c, 0x20, 0x61, 0x6e, + 0x64, 0x2f, 0x6f, 0x72, 0x20, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, + 0x75, 0x74, 0x65, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x73, 0x6f, 0x66, + 0x74, 0x77, 0x61, 0x72, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x6e, + 0x79, 0x20, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x20, 0x77, 0x69, + 0x74, 0x68, 0x20, 0x6f, 0x72, 0x0a, 0x20, 0x20, 0x20, 0x77, 0x69, 0x74, + 0x68, 0x6f, 0x75, 0x74, 0x20, 0x66, 0x65, 0x65, 0x20, 0x69, 0x73, 0x20, + 0x68, 0x65, 0x72, 0x65, 0x62, 0x79, 0x20, 0x67, 0x72, 0x61, 0x6e, 0x74, + 0x65, 0x64, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, + 0x74, 0x68, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x62, 0x6f, + 0x76, 0x65, 0x20, 0x63, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, + 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x0a, + 0x20, 0x20, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x70, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x63, + 0x65, 0x20, 0x61, 0x70, 0x70, 0x65, 0x61, 0x72, 0x20, 0x69, 0x6e, 0x20, + 0x61, 0x6c, 0x6c, 0x20, 0x63, 0x6f, 0x70, 0x69, 0x65, 0x73, 0x2e, 0x0a, + 0x0a, 0x20, 0x20, 0x20, 0x59, 0x55, 0x50, 0x20, 0x49, 0x53, 0x20, 0x50, + 0x52, 0x4f, 0x56, 0x49, 0x44, 0x45, 0x44, 0x20, 0x22, 0x41, 0x53, 0x20, + 0x49, 0x53, 0x22, 0x20, 0x57, 0x49, 0x54, 0x48, 0x4f, 0x55, 0x54, 0x20, + 0x41, 0x4e, 0x59, 0x20, 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x59, + 0x2c, 0x20, 0x41, 0x4e, 0x44, 0x20, 0x41, 0x4c, 0x4c, 0x20, 0x57, 0x41, + 0x52, 0x52, 0x41, 0x4e, 0x54, 0x49, 0x45, 0x53, 0x2c, 0x20, 0x57, 0x48, + 0x45, 0x54, 0x48, 0x45, 0x52, 0x0a, 0x20, 0x20, 0x20, 0x45, 0x58, 0x50, + 0x52, 0x45, 0x53, 0x53, 0x45, 0x44, 0x20, 0x4f, 0x52, 0x20, 0x49, 0x4d, + 0x50, 0x4c, 0x49, 0x45, 0x44, 0x2c, 0x20, 0x49, 0x4e, 0x43, 0x4c, 0x55, + 0x44, 0x49, 0x4e, 0x47, 0x20, 0x4d, 0x45, 0x52, 0x43, 0x48, 0x41, 0x4e, + 0x54, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x20, 0x41, 0x4e, 0x44, + 0x20, 0x46, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x20, 0x46, 0x4f, 0x52, + 0x20, 0x50, 0x55, 0x52, 0x50, 0x4f, 0x53, 0x45, 0x2c, 0x20, 0x41, 0x52, + 0x45, 0x0a, 0x20, 0x20, 0x20, 0x44, 0x49, 0x53, 0x43, 0x4c, 0x41, 0x49, + 0x4d, 0x45, 0x44, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x0a, 0x2a, 0x2f, 0x0a, 0x0a, 0x23, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x20, 0x34, 0x35, 0x30, 0x0a, 0x0a, 0x2f, 0x2f, 0x20, + 0x46, 0x75, 0x6c, 0x6c, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, + 0x61, 0x74, 0x65, 0x72, 0x66, 0x61, 0x6c, 0x6c, 0x20, 0x73, 0x68, 0x61, + 0x64, 0x65, 0x72, 0x2e, 0x20, 0x41, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, + 0x65, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, + 0x20, 0x74, 0x72, 0x69, 0x61, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x73, 0x63, + 0x72, 0x6f, 0x6c, 0x6c, 0x73, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x2f, 0x2f, + 0x20, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x20, 0x68, 0x69, + 0x73, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, + 0x65, 0x20, 0x64, 0x6f, 0x77, 0x6e, 0x20, 0x62, 0x79, 0x20, 0x6e, 0x75, + 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, 0x72, 0x6f, 0x77, 0x73, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, 0x6e, 0x65, + 0x77, 0x20, 0x72, 0x6f, 0x77, 0x73, 0x0a, 0x2f, 0x2f, 0x20, 0x61, 0x74, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x6f, 0x70, 0x2c, 0x20, 0x6d, 0x61, + 0x70, 0x70, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x61, 0x63, 0x68, 0x20, 0x62, + 0x69, 0x6e, 0x27, 0x73, 0x20, 0x72, 0x61, 0x77, 0x20, 0x6d, 0x61, 0x67, + 0x6e, 0x69, 0x74, 0x75, 0x64, 0x65, 0x20, 0x74, 0x68, 0x72, 0x6f, 0x75, + 0x67, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, + 0x20, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x20, 0x74, 0x61, 0x62, 0x6c, + 0x65, 0x0a, 0x2f, 0x2f, 0x20, 0x28, 0x61, 0x20, 0x6c, 0x65, 0x72, 0x70, + 0x20, 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x20, 0x61, 0x64, 0x6a, + 0x61, 0x63, 0x65, 0x6e, 0x74, 0x20, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, + 0x73, 0x2c, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x20, + 0x53, 0x70, 0x65, 0x63, 0x74, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x43, + 0x6f, 0x6c, 0x6f, 0x72, 0x4d, 0x61, 0x70, 0x3a, 0x3a, 0x6d, 0x61, 0x70, + 0x29, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x72, 0x65, 0x6c, 0x79, 0x0a, 0x2f, + 0x2f, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x50, 0x55, + 0x2e, 0x0a, 0x2f, 0x2f, 0x0a, 0x2f, 0x2f, 0x20, 0x42, 0x69, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x73, 0x3a, 0x0a, 0x2f, 0x2f, 0x20, 0x20, 0x20, 0x73, + 0x65, 0x74, 0x3d, 0x30, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x3d, 0x30, 0x20, 0x3a, 0x20, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, + 0x73, 0x20, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x74, 0x65, + 0x78, 0x74, 0x75, 0x72, 0x65, 0x20, 0x28, 0x55, 0x56, 0x2e, 0x79, 0x20, + 0x3d, 0x20, 0x30, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, + 0x6f, 0x70, 0x20, 0x72, 0x6f, 0x77, 0x29, 0x0a, 0x2f, 0x2f, 0x20, 0x20, + 0x20, 0x73, 0x65, 0x74, 0x3d, 0x30, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x3d, 0x31, 0x20, 0x3a, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, + 0x65, 0x72, 0x20, 0x28, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x63, 0x69, 0x74, + 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x2c, 0x20, 0x6c, 0x69, + 0x6b, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x61, 0x74, 0x74, 0x65, + 0x20, 0x70, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x29, 0x0a, 0x2f, + 0x2f, 0x20, 0x20, 0x20, 0x73, 0x65, 0x74, 0x3d, 0x30, 0x20, 0x62, 0x69, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x32, 0x20, 0x3a, 0x20, 0x57, 0x61, + 0x74, 0x65, 0x72, 0x66, 0x61, 0x6c, 0x6c, 0x50, 0x61, 0x72, 0x61, 0x6d, + 0x73, 0x20, 0x55, 0x42, 0x4f, 0x0a, 0x2f, 0x2f, 0x20, 0x20, 0x20, 0x73, + 0x65, 0x74, 0x3d, 0x30, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x3d, 0x33, 0x20, 0x3a, 0x20, 0x52, 0x6f, 0x77, 0x44, 0x61, 0x74, 0x61, + 0x20, 0x55, 0x42, 0x4f, 0x20, 0x28, 0x57, 0x20, 0x2a, 0x20, 0x6e, 0x75, + 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, 0x72, 0x61, 0x77, 0x20, 0x6d, 0x61, + 0x67, 0x6e, 0x69, 0x74, 0x75, 0x64, 0x65, 0x73, 0x2c, 0x20, 0x6f, 0x6c, + 0x64, 0x65, 0x73, 0x74, 0x20, 0x72, 0x6f, 0x77, 0x20, 0x66, 0x69, 0x72, + 0x73, 0x74, 0x29, 0x0a, 0x2f, 0x2f, 0x20, 0x20, 0x20, 0x73, 0x65, 0x74, + 0x3d, 0x30, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x34, + 0x20, 0x3a, 0x20, 0x4c, 0x75, 0x74, 0x44, 0x61, 0x74, 0x61, 0x20, 0x55, + 0x42, 0x4f, 0x20, 0x28, 0x32, 0x35, 0x36, 0x20, 0x41, 0x52, 0x47, 0x42, + 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x65, 0x6e, 0x74, 0x72, 0x69, + 0x65, 0x73, 0x29, 0x0a, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, + 0x73, 0x65, 0x74, 0x20, 0x3d, 0x20, 0x30, 0x2c, 0x20, 0x62, 0x69, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x20, 0x3d, 0x20, 0x30, 0x29, 0x20, 0x75, 0x6e, + 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, + 0x65, 0x32, 0x44, 0x20, 0x75, 0x5f, 0x70, 0x72, 0x65, 0x76, 0x3b, 0x0a, + 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, 0x73, 0x65, 0x74, 0x20, 0x3d, + 0x20, 0x30, 0x2c, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, + 0x3d, 0x20, 0x31, 0x29, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, + 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x20, 0x20, 0x20, 0x75, + 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x3b, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, + 0x74, 0x28, 0x73, 0x65, 0x74, 0x20, 0x3d, 0x20, 0x30, 0x2c, 0x20, 0x62, + 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x3d, 0x20, 0x32, 0x29, 0x20, + 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x57, 0x61, 0x74, 0x65, + 0x72, 0x66, 0x61, 0x6c, 0x6c, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x0a, + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, + 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, + 0x3b, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x77, 0x61, 0x74, 0x65, 0x72, + 0x66, 0x61, 0x6c, 0x6c, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, + 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x65, + 0x78, 0x65, 0x6c, 0x73, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x62, 0x69, 0x6e, + 0x73, 0x3b, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x6e, 0x75, 0x6d, + 0x62, 0x65, 0x72, 0x20, 0x6f, 0x66, 0x20, 0x6d, 0x61, 0x67, 0x6e, 0x69, + 0x74, 0x75, 0x64, 0x65, 0x20, 0x62, 0x69, 0x6e, 0x73, 0x20, 0x70, 0x65, + 0x72, 0x20, 0x72, 0x6f, 0x77, 0x20, 0x28, 0x3c, 0x3d, 0x20, 0x77, 0x69, + 0x64, 0x74, 0x68, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x20, 0x70, 0x61, 0x64, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, 0x61, 0x64, 0x31, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, + 0x61, 0x64, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x20, 0x70, 0x61, 0x64, 0x33, 0x3b, 0x0a, 0x7d, 0x20, 0x70, + 0x3b, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, 0x73, 0x65, 0x74, + 0x20, 0x3d, 0x20, 0x30, 0x2c, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x20, 0x3d, 0x20, 0x33, 0x29, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, + 0x72, 0x6d, 0x20, 0x52, 0x6f, 0x77, 0x44, 0x61, 0x74, 0x61, 0x0a, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x6d, 0x61, + 0x67, 0x73, 0x5b, 0x35, 0x31, 0x32, 0x5d, 0x3b, 0x20, 0x2f, 0x2f, 0x20, + 0x32, 0x30, 0x34, 0x38, 0x20, 0x72, 0x61, 0x77, 0x20, 0x6d, 0x61, 0x67, + 0x6e, 0x69, 0x74, 0x75, 0x64, 0x65, 0x73, 0x20, 0x70, 0x61, 0x63, 0x6b, + 0x65, 0x64, 0x20, 0x61, 0x73, 0x20, 0x76, 0x65, 0x63, 0x34, 0x73, 0x20, + 0x28, 0x73, 0x74, 0x64, 0x31, 0x34, 0x30, 0x20, 0x74, 0x69, 0x67, 0x68, + 0x74, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x57, 0x20, 0x69, 0x73, 0x20, + 0x61, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x6f, + 0x66, 0x20, 0x34, 0x29, 0x0a, 0x7d, 0x20, 0x72, 0x6f, 0x77, 0x73, 0x3b, + 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, 0x73, 0x65, 0x74, 0x20, + 0x3d, 0x20, 0x30, 0x2c, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x20, 0x3d, 0x20, 0x34, 0x29, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, + 0x6d, 0x20, 0x4c, 0x75, 0x74, 0x44, 0x61, 0x74, 0x61, 0x0a, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x75, 0x76, 0x65, 0x63, 0x34, 0x20, 0x6c, 0x75, + 0x74, 0x5b, 0x36, 0x34, 0x5d, 0x3b, 0x20, 0x2f, 0x2f, 0x20, 0x32, 0x35, + 0x36, 0x20, 0x41, 0x52, 0x47, 0x42, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, + 0x73, 0x20, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x20, 0x61, 0x73, 0x20, + 0x75, 0x76, 0x65, 0x63, 0x34, 0x73, 0x0a, 0x7d, 0x20, 0x6c, 0x75, 0x74, + 0x3b, 0x0a, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, 0x6c, 0x6f, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x30, 0x29, 0x20, + 0x6f, 0x75, 0x74, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x66, 0x72, 0x61, + 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x0a, 0x0a, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x20, 0x66, 0x65, 0x74, 0x63, 0x68, 0x4d, 0x61, 0x67, 0x28, + 0x69, 0x6e, 0x74, 0x20, 0x69, 0x64, 0x78, 0x29, 0x0a, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x72, 0x6f, + 0x77, 0x73, 0x2e, 0x6d, 0x61, 0x67, 0x73, 0x5b, 0x69, 0x64, 0x78, 0x20, + 0x3e, 0x3e, 0x20, 0x32, 0x5d, 0x5b, 0x69, 0x64, 0x78, 0x20, 0x26, 0x20, + 0x33, 0x5d, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x76, 0x6f, 0x69, 0x64, 0x20, + 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x75, 0x76, 0x20, 0x3d, 0x20, 0x67, + 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, + 0x78, 0x79, 0x20, 0x2f, 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, 0x70, 0x2e, + 0x77, 0x69, 0x64, 0x74, 0x68, 0x2c, 0x20, 0x70, 0x2e, 0x68, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, + 0x20, 0x28, 0x75, 0x76, 0x2e, 0x79, 0x20, 0x3e, 0x3d, 0x20, 0x70, 0x2e, + 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, 0x2f, 0x20, 0x70, 0x2e, + 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x72, + 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20, 0x74, 0x65, + 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, + 0x72, 0x32, 0x44, 0x28, 0x75, 0x5f, 0x70, 0x72, 0x65, 0x76, 0x2c, 0x20, + 0x75, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x29, 0x2c, 0x20, 0x76, 0x65, 0x63, + 0x32, 0x28, 0x75, 0x76, 0x2e, 0x78, 0x2c, 0x20, 0x75, 0x76, 0x2e, 0x79, + 0x20, 0x2d, 0x20, 0x70, 0x2e, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, + 0x20, 0x2f, 0x20, 0x70, 0x2e, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x29, + 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, + 0x78, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x67, 0x6c, 0x5f, 0x46, + 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x78, 0x29, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, + 0x20, 0x79, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x67, 0x6c, 0x5f, + 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x79, 0x29, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, + 0x20, 0x52, 0x6f, 0x77, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x75, 0x70, + 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x20, 0x6f, 0x6c, 0x64, 0x65, 0x73, + 0x74, 0x2d, 0x66, 0x69, 0x72, 0x73, 0x74, 0x3b, 0x20, 0x63, 0x61, 0x6e, + 0x76, 0x61, 0x73, 0x20, 0x72, 0x6f, 0x77, 0x20, 0x79, 0x20, 0x28, 0x66, + 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x6f, 0x70, 0x29, + 0x20, 0x73, 0x68, 0x6f, 0x77, 0x73, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x28, 0x6e, + 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, 0x2d, 0x20, 0x31, 0x20, 0x2d, + 0x20, 0x79, 0x29, 0x2d, 0x74, 0x68, 0x20, 0x70, 0x65, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x20, 0x72, 0x6f, 0x77, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x72, 0x6f, 0x77, 0x20, + 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x70, 0x2e, 0x6e, 0x75, 0x6d, 0x52, + 0x6f, 0x77, 0x73, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x20, 0x2d, 0x20, 0x79, + 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, + 0x2f, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x6f, 0x6c, 0x61, 0x74, + 0x65, 0x20, 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x20, 0x61, 0x64, + 0x6a, 0x61, 0x63, 0x65, 0x6e, 0x74, 0x20, 0x6d, 0x61, 0x67, 0x6e, 0x69, + 0x74, 0x75, 0x64, 0x65, 0x20, 0x62, 0x69, 0x6e, 0x73, 0x20, 0x73, 0x6f, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, + 0x63, 0x79, 0x20, 0x61, 0x78, 0x69, 0x73, 0x20, 0x69, 0x73, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x73, 0x6d, + 0x6f, 0x6f, 0x74, 0x68, 0x20, 0x28, 0x61, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x69, 0x61, 0x73, 0x65, 0x64, 0x29, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x20, + 0x74, 0x68, 0x6f, 0x75, 0x67, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, + 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x20, 0x69, 0x73, 0x20, 0x77, 0x69, + 0x64, 0x65, 0x72, 0x20, 0x74, 0x68, 0x61, 0x6e, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x62, 0x69, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x2f, 0x2f, 0x20, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x20, 0x62, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x20, 0x3d, 0x20, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x28, 0x78, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x70, 0x2e, + 0x62, 0x69, 0x6e, 0x73, 0x20, 0x2f, 0x20, 0x70, 0x2e, 0x77, 0x69, 0x64, + 0x74, 0x68, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x69, 0x6e, 0x74, 0x20, 0x62, 0x69, 0x6e, 0x30, 0x20, 0x3d, 0x20, + 0x69, 0x6e, 0x74, 0x28, 0x62, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x29, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, + 0x20, 0x62, 0x69, 0x6e, 0x31, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x6e, 0x28, + 0x62, 0x69, 0x6e, 0x30, 0x20, 0x2b, 0x20, 0x31, 0x2c, 0x20, 0x69, 0x6e, + 0x74, 0x28, 0x70, 0x2e, 0x62, 0x69, 0x6e, 0x73, 0x29, 0x20, 0x2d, 0x20, + 0x31, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x66, 0x72, 0x61, 0x63, 0x42, 0x69, + 0x6e, 0x20, 0x3d, 0x20, 0x62, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x20, 0x2d, + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x62, 0x69, 0x6e, 0x30, 0x29, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x20, 0x6d, 0x61, 0x67, 0x20, 0x3d, 0x20, 0x6d, 0x69, + 0x78, 0x28, 0x66, 0x65, 0x74, 0x63, 0x68, 0x4d, 0x61, 0x67, 0x28, 0x72, + 0x6f, 0x77, 0x20, 0x2a, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x70, 0x2e, 0x62, + 0x69, 0x6e, 0x73, 0x29, 0x20, 0x2b, 0x20, 0x62, 0x69, 0x6e, 0x30, 0x29, + 0x2c, 0x20, 0x66, 0x65, 0x74, 0x63, 0x68, 0x4d, 0x61, 0x67, 0x28, 0x72, + 0x6f, 0x77, 0x20, 0x2a, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x70, 0x2e, 0x62, + 0x69, 0x6e, 0x73, 0x29, 0x20, 0x2b, 0x20, 0x62, 0x69, 0x6e, 0x31, 0x29, + 0x2c, 0x20, 0x66, 0x72, 0x61, 0x63, 0x42, 0x69, 0x6e, 0x29, 0x3b, 0x0a, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x20, 0x74, 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x61, 0x6d, 0x70, + 0x28, 0x6d, 0x61, 0x67, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, + 0x2e, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, + 0x20, 0x69, 0x30, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x74, 0x29, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, + 0x74, 0x20, 0x69, 0x31, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x6e, 0x28, 0x69, + 0x30, 0x20, 0x2b, 0x20, 0x31, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x29, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x20, 0x66, 0x72, 0x61, 0x63, 0x20, 0x3d, 0x20, 0x74, 0x20, + 0x2d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x69, 0x30, 0x29, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x75, 0x69, 0x6e, + 0x74, 0x20, 0x63, 0x30, 0x20, 0x3d, 0x20, 0x6c, 0x75, 0x74, 0x2e, 0x6c, + 0x75, 0x74, 0x5b, 0x69, 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x5d, 0x5b, + 0x69, 0x30, 0x20, 0x26, 0x20, 0x33, 0x5d, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x20, 0x63, 0x31, + 0x20, 0x3d, 0x20, 0x6c, 0x75, 0x74, 0x2e, 0x6c, 0x75, 0x74, 0x5b, 0x69, + 0x31, 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x5d, 0x5b, 0x69, 0x31, 0x20, 0x26, + 0x20, 0x33, 0x5d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x63, 0x6f, 0x6c, 0x30, 0x20, 0x3d, + 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, + 0x28, 0x63, 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x31, 0x36, 0x29, 0x20, 0x26, + 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x28, 0x28, 0x63, 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x38, 0x29, 0x20, + 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x28, 0x63, 0x30, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, + 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x30, + 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x34, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, + 0x35, 0x75, 0x29, 0x29, 0x20, 0x2f, 0x20, 0x32, 0x35, 0x35, 0x2e, 0x30, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, + 0x63, 0x34, 0x20, 0x63, 0x6f, 0x6c, 0x31, 0x20, 0x3d, 0x20, 0x76, 0x65, + 0x63, 0x34, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x31, + 0x20, 0x3e, 0x3e, 0x20, 0x31, 0x36, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, + 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, + 0x63, 0x31, 0x20, 0x3e, 0x3e, 0x20, 0x38, 0x29, 0x20, 0x26, 0x20, 0x32, + 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, + 0x63, 0x31, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x31, 0x20, 0x3e, 0x3e, + 0x20, 0x32, 0x34, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, + 0x29, 0x20, 0x2f, 0x20, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x3b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x72, 0x61, 0x67, 0x43, + 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x78, 0x28, 0x63, + 0x6f, 0x6c, 0x30, 0x2c, 0x20, 0x63, 0x6f, 0x6c, 0x31, 0x2c, 0x20, 0x66, + 0x72, 0x61, 0x63, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, + 0x7d, 0x0a, 0x52, 0x45, 0x46, 0x4c, 0x11, 0x06, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, + 0x69, 0x6e, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x52, 0x6f, 0x77, 0x44, 0x61, 0x74, + 0x61, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x67, + 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0x0f, 0x00, 0x00, 0x00, 0x57, 0x61, 0x74, 0x65, 0x72, 0x66, 0x61, 0x6c, + 0x6c, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, + 0x00, 0x00, 0x00, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x77, 0x69, 0x64, 0x74, 0x68, 0x04, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x68, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x62, 0x69, 0x6e, + 0x73, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x70, 0x61, 0x64, + 0x30, 0x10, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x70, 0x61, 0x64, + 0x31, 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x70, 0x61, 0x64, + 0x32, 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x70, 0x61, 0x64, + 0x33, 0x1c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, + 0x4c, 0x75, 0x74, 0x44, 0x61, 0x74, 0x61, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x6c, 0x75, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x00, 0xb1, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x66, 0x72, 0x61, + 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x3f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x1a, 0x00, 0x00, 0x00, 0x79, 0x75, 0x70, 0x5f, 0x63, 0x6f, 0x6d, + 0x62, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x75, 0x5f, 0x70, 0x72, 0x65, 0x76, + 0x5f, 0x75, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, + 0x00, 0x00, 0x75, 0x5f, 0x70, 0x72, 0x65, 0x76, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, + 0x00, 0x00, 0x00, 0x75, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x0b, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x5f, 0x52, 0x45, 0x53, + 0x45, 0x52, 0x56, 0x45, 0x44, 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, 0x49, + 0x46, 0x49, 0x45, 0x52, 0x5f, 0x46, 0x49, 0x58, 0x55, 0x50, 0x5f, 0x67, + 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x28, 0x31, + 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x79, 0x75, 0x70, + 0x5f, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x75, 0x5f, + 0x70, 0x72, 0x65, 0x76, 0x5f, 0x75, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x56, 0x41, 0x52, 0x54, 0xb8, 0x1c, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, + 0xa2, 0x07, 0x00, 0x00, 0x23, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x20, 0x33, 0x30, 0x30, 0x20, 0x65, 0x73, 0x0a, 0x70, 0x72, 0x65, 0x63, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, + 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3b, 0x0a, 0x70, 0x72, 0x65, + 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, + 0x20, 0x69, 0x6e, 0x74, 0x3b, 0x0a, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, + 0x74, 0x28, 0x73, 0x74, 0x64, 0x31, 0x34, 0x30, 0x29, 0x20, 0x75, 0x6e, + 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x52, 0x6f, 0x77, 0x44, 0x61, 0x74, + 0x61, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, + 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x6d, 0x61, 0x67, 0x73, 0x5b, + 0x35, 0x31, 0x32, 0x5d, 0x3b, 0x0a, 0x7d, 0x20, 0x72, 0x6f, 0x77, 0x73, + 0x3b, 0x0a, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, 0x73, 0x74, + 0x64, 0x31, 0x34, 0x30, 0x29, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, + 0x6d, 0x20, 0x57, 0x61, 0x74, 0x65, 0x72, 0x66, 0x61, 0x6c, 0x6c, 0x50, + 0x61, 0x72, 0x61, 0x6d, 0x73, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, + 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, + 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, + 0x62, 0x69, 0x6e, 0x73, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, + 0x67, 0x68, 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, 0x61, + 0x64, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, + 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, 0x61, 0x64, 0x31, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, 0x61, 0x64, 0x32, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x20, 0x70, 0x61, 0x64, 0x33, 0x3b, 0x0a, 0x7d, 0x20, + 0x70, 0x3b, 0x0a, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, 0x73, + 0x74, 0x64, 0x31, 0x34, 0x30, 0x29, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, + 0x72, 0x6d, 0x20, 0x4c, 0x75, 0x74, 0x44, 0x61, 0x74, 0x61, 0x0a, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x75, 0x76, 0x65, 0x63, 0x34, 0x20, 0x6c, + 0x75, 0x74, 0x5b, 0x36, 0x34, 0x5d, 0x3b, 0x0a, 0x7d, 0x20, 0x6c, 0x75, + 0x74, 0x3b, 0x0a, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, + 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, + 0x72, 0x32, 0x44, 0x20, 0x79, 0x75, 0x70, 0x5f, 0x63, 0x6f, 0x6d, 0x62, + 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x75, 0x5f, 0x70, 0x72, 0x65, 0x76, 0x5f, + 0x75, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x3b, 0x0a, 0x0a, 0x6c, 0x61, 0x79, + 0x6f, 0x75, 0x74, 0x28, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x3d, 0x20, 0x30, 0x29, 0x20, 0x6f, 0x75, 0x74, 0x20, 0x68, 0x69, + 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x66, 0x72, 0x61, + 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x0a, 0x0a, 0x68, 0x69, 0x67, + 0x68, 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x66, 0x65, 0x74, + 0x63, 0x68, 0x4d, 0x61, 0x67, 0x28, 0x69, 0x6e, 0x74, 0x20, 0x69, 0x64, + 0x78, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, + 0x75, 0x72, 0x6e, 0x20, 0x72, 0x6f, 0x77, 0x73, 0x2e, 0x6d, 0x61, 0x67, + 0x73, 0x5b, 0x69, 0x64, 0x78, 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x5d, 0x5b, + 0x69, 0x64, 0x78, 0x20, 0x26, 0x20, 0x33, 0x5d, 0x3b, 0x0a, 0x7d, 0x0a, + 0x0a, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, + 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, + 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x75, 0x76, 0x20, 0x3d, 0x20, 0x67, + 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, + 0x78, 0x79, 0x20, 0x2f, 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, 0x70, 0x2e, + 0x77, 0x69, 0x64, 0x74, 0x68, 0x2c, 0x20, 0x70, 0x2e, 0x68, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, + 0x20, 0x28, 0x75, 0x76, 0x2e, 0x79, 0x20, 0x3e, 0x3d, 0x20, 0x28, 0x70, + 0x2e, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, 0x2f, 0x20, 0x70, + 0x2e, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x29, 0x29, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x66, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20, + 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x79, 0x75, 0x70, 0x5f, + 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x75, 0x5f, 0x70, + 0x72, 0x65, 0x76, 0x5f, 0x75, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x2c, 0x20, + 0x76, 0x65, 0x63, 0x32, 0x28, 0x75, 0x76, 0x2e, 0x78, 0x2c, 0x20, 0x75, + 0x76, 0x2e, 0x79, 0x20, 0x2d, 0x20, 0x28, 0x70, 0x2e, 0x6e, 0x75, 0x6d, + 0x52, 0x6f, 0x77, 0x73, 0x20, 0x2f, 0x20, 0x70, 0x2e, 0x68, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x29, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x69, 0x6e, 0x74, 0x20, 0x78, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, + 0x28, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, + 0x64, 0x2e, 0x78, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x79, 0x20, 0x3d, 0x20, 0x69, 0x6e, + 0x74, 0x28, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, + 0x72, 0x64, 0x2e, 0x79, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x72, 0x6f, 0x77, 0x20, 0x3d, + 0x20, 0x28, 0x69, 0x6e, 0x74, 0x28, 0x70, 0x2e, 0x6e, 0x75, 0x6d, 0x52, + 0x6f, 0x77, 0x73, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x29, 0x20, 0x2d, 0x20, + 0x79, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, + 0x69, 0x67, 0x68, 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x62, + 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x28, 0x78, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x70, 0x2e, 0x62, 0x69, + 0x6e, 0x73, 0x20, 0x2f, 0x20, 0x70, 0x2e, 0x77, 0x69, 0x64, 0x74, 0x68, + 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, + 0x6e, 0x74, 0x20, 0x62, 0x69, 0x6e, 0x30, 0x20, 0x3d, 0x20, 0x69, 0x6e, + 0x74, 0x28, 0x62, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x29, 0x3b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x62, + 0x69, 0x6e, 0x31, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x6e, 0x28, 0x28, 0x62, + 0x69, 0x6e, 0x30, 0x20, 0x2b, 0x20, 0x31, 0x29, 0x2c, 0x20, 0x28, 0x69, + 0x6e, 0x74, 0x28, 0x70, 0x2e, 0x62, 0x69, 0x6e, 0x73, 0x29, 0x20, 0x2d, + 0x20, 0x31, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x20, 0x66, 0x72, 0x61, 0x63, 0x42, 0x69, 0x6e, 0x20, 0x3d, 0x20, + 0x62, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x20, 0x2d, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x28, 0x62, 0x69, 0x6e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x70, 0x61, + 0x72, 0x61, 0x6d, 0x20, 0x3d, 0x20, 0x28, 0x72, 0x6f, 0x77, 0x20, 0x2a, + 0x20, 0x69, 0x6e, 0x74, 0x28, 0x70, 0x2e, 0x62, 0x69, 0x6e, 0x73, 0x29, + 0x29, 0x20, 0x2b, 0x20, 0x62, 0x69, 0x6e, 0x30, 0x3b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x70, 0x61, + 0x72, 0x61, 0x6d, 0x5f, 0x31, 0x20, 0x3d, 0x20, 0x28, 0x72, 0x6f, 0x77, + 0x20, 0x2a, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x70, 0x2e, 0x62, 0x69, 0x6e, + 0x73, 0x29, 0x29, 0x20, 0x2b, 0x20, 0x62, 0x69, 0x6e, 0x31, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, + 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x6d, 0x61, 0x67, 0x20, + 0x3d, 0x20, 0x6d, 0x69, 0x78, 0x28, 0x66, 0x65, 0x74, 0x63, 0x68, 0x4d, + 0x61, 0x67, 0x28, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x29, 0x2c, 0x20, 0x66, + 0x65, 0x74, 0x63, 0x68, 0x4d, 0x61, 0x67, 0x28, 0x70, 0x61, 0x72, 0x61, + 0x6d, 0x5f, 0x31, 0x29, 0x2c, 0x20, 0x66, 0x72, 0x61, 0x63, 0x42, 0x69, + 0x6e, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, + 0x74, 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x6d, 0x61, + 0x67, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, + 0x20, 0x2a, 0x20, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x3b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x69, 0x30, + 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x74, 0x29, 0x3b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x69, + 0x31, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x6e, 0x28, 0x28, 0x69, 0x30, 0x20, + 0x2b, 0x20, 0x31, 0x29, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x29, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, + 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x66, 0x72, 0x61, 0x63, + 0x20, 0x3d, 0x20, 0x74, 0x20, 0x2d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x28, 0x69, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x20, 0x63, 0x30, 0x20, 0x3d, 0x20, + 0x6c, 0x75, 0x74, 0x2e, 0x6c, 0x75, 0x74, 0x5b, 0x69, 0x30, 0x20, 0x3e, + 0x3e, 0x20, 0x32, 0x5d, 0x5b, 0x69, 0x30, 0x20, 0x26, 0x20, 0x33, 0x5d, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x75, 0x69, + 0x6e, 0x74, 0x20, 0x63, 0x31, 0x20, 0x3d, 0x20, 0x6c, 0x75, 0x74, 0x2e, + 0x6c, 0x75, 0x74, 0x5b, 0x69, 0x31, 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x5d, + 0x5b, 0x69, 0x31, 0x20, 0x26, 0x20, 0x33, 0x5d, 0x3b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, + 0x76, 0x65, 0x63, 0x34, 0x20, 0x63, 0x6f, 0x6c, 0x30, 0x20, 0x3d, 0x20, + 0x76, 0x65, 0x63, 0x34, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, + 0x63, 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x28, 0x31, + 0x36, 0x29, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x30, 0x20, 0x3e, + 0x3e, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x28, 0x38, 0x29, 0x29, 0x20, 0x26, + 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x28, 0x63, 0x30, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, + 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x30, 0x20, + 0x3e, 0x3e, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x28, 0x32, 0x34, 0x29, 0x29, + 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x29, 0x20, 0x2f, 0x20, + 0x76, 0x65, 0x63, 0x34, 0x28, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x29, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, + 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x63, 0x6f, 0x6c, 0x31, + 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x28, 0x28, 0x63, 0x31, 0x20, 0x3e, 0x3e, 0x20, 0x75, 0x69, 0x6e, + 0x74, 0x28, 0x31, 0x36, 0x29, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, + 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, + 0x31, 0x20, 0x3e, 0x3e, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x28, 0x38, 0x29, + 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x63, 0x31, 0x20, 0x26, 0x20, 0x32, 0x35, + 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, + 0x63, 0x31, 0x20, 0x3e, 0x3e, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x28, 0x32, + 0x34, 0x29, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x29, + 0x20, 0x2f, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x32, 0x35, 0x35, 0x2e, + 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x66, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20, + 0x6d, 0x69, 0x78, 0x28, 0x63, 0x6f, 0x6c, 0x30, 0x2c, 0x20, 0x63, 0x6f, + 0x6c, 0x31, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x66, 0x72, 0x61, + 0x63, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x7d, + 0x0a, 0x0a, 0x49, 0x53, 0x52, 0x43, 0xe4, 0x0e, 0x00, 0x00, 0x2f, 0x2a, + 0x0a, 0x20, 0x20, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x0a, 0x0a, 0x20, + 0x20, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, + 0x69, 0x73, 0x20, 0x70, 0x61, 0x72, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x59, 0x55, 0x50, 0x20, 0x6c, 0x69, 0x62, 0x72, 0x61, + 0x72, 0x79, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x72, + 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x32, + 0x36, 0x20, 0x2d, 0x20, 0x6b, 0x75, 0x6e, 0x69, 0x74, 0x6f, 0x6b, 0x69, + 0x40, 0x67, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x0a, 0x0a, + 0x20, 0x20, 0x20, 0x59, 0x55, 0x50, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6e, + 0x20, 0x6f, 0x70, 0x65, 0x6e, 0x20, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x20, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x20, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x6f, 0x70, 0x65, 0x6e, + 0x2d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x6c, 0x69, 0x63, 0x65, + 0x6e, 0x73, 0x69, 0x6e, 0x67, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x54, + 0x68, 0x65, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x69, 0x6e, 0x63, 0x6c, + 0x75, 0x64, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, + 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x74, 0x65, 0x72, 0x6d, 0x73, 0x20, 0x6f, 0x66, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x49, 0x53, 0x43, 0x20, 0x6c, 0x69, 0x63, + 0x65, 0x6e, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x68, 0x74, 0x74, 0x70, + 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x73, 0x63, 0x2e, 0x6f, + 0x72, 0x67, 0x2f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x73, + 0x2f, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x2d, 0x73, 0x75, + 0x70, 0x70, 0x6f, 0x72, 0x74, 0x2d, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x2f, 0x69, 0x73, 0x63, 0x2d, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, + 0x2e, 0x20, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x0a, 0x20, 0x20, 0x20, 0x74, 0x6f, 0x20, 0x75, 0x73, 0x65, 0x2c, 0x20, + 0x63, 0x6f, 0x70, 0x79, 0x2c, 0x20, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, + 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x2f, 0x6f, 0x72, 0x20, 0x64, 0x69, 0x73, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x74, 0x68, 0x69, 0x73, + 0x20, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x20, 0x66, 0x6f, + 0x72, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, + 0x65, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x72, 0x0a, 0x20, 0x20, + 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x66, 0x65, 0x65, + 0x20, 0x69, 0x73, 0x20, 0x68, 0x65, 0x72, 0x65, 0x62, 0x79, 0x20, 0x67, + 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x64, 0x65, 0x64, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, 0x20, 0x63, 0x6f, 0x70, 0x79, 0x72, + 0x69, 0x67, 0x68, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x20, + 0x61, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, + 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6e, + 0x6f, 0x74, 0x69, 0x63, 0x65, 0x20, 0x61, 0x70, 0x70, 0x65, 0x61, 0x72, + 0x20, 0x69, 0x6e, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x63, 0x6f, 0x70, 0x69, + 0x65, 0x73, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x59, 0x55, 0x50, 0x20, + 0x49, 0x53, 0x20, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x44, 0x45, 0x44, 0x20, + 0x22, 0x41, 0x53, 0x20, 0x49, 0x53, 0x22, 0x20, 0x57, 0x49, 0x54, 0x48, + 0x4f, 0x55, 0x54, 0x20, 0x41, 0x4e, 0x59, 0x20, 0x57, 0x41, 0x52, 0x52, + 0x41, 0x4e, 0x54, 0x59, 0x2c, 0x20, 0x41, 0x4e, 0x44, 0x20, 0x41, 0x4c, + 0x4c, 0x20, 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x49, 0x45, 0x53, + 0x2c, 0x20, 0x57, 0x48, 0x45, 0x54, 0x48, 0x45, 0x52, 0x0a, 0x20, 0x20, + 0x20, 0x45, 0x58, 0x50, 0x52, 0x45, 0x53, 0x53, 0x45, 0x44, 0x20, 0x4f, + 0x52, 0x20, 0x49, 0x4d, 0x50, 0x4c, 0x49, 0x45, 0x44, 0x2c, 0x20, 0x49, + 0x4e, 0x43, 0x4c, 0x55, 0x44, 0x49, 0x4e, 0x47, 0x20, 0x4d, 0x45, 0x52, + 0x43, 0x48, 0x41, 0x4e, 0x54, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, + 0x20, 0x41, 0x4e, 0x44, 0x20, 0x46, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, + 0x20, 0x46, 0x4f, 0x52, 0x20, 0x50, 0x55, 0x52, 0x50, 0x4f, 0x53, 0x45, + 0x2c, 0x20, 0x41, 0x52, 0x45, 0x0a, 0x20, 0x20, 0x20, 0x44, 0x49, 0x53, + 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x45, 0x44, 0x2e, 0x0a, 0x0a, 0x20, 0x20, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x0a, 0x2a, 0x2f, 0x0a, 0x0a, 0x23, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x34, 0x35, 0x30, 0x0a, + 0x0a, 0x2f, 0x2f, 0x20, 0x46, 0x75, 0x6c, 0x6c, 0x73, 0x63, 0x72, 0x65, + 0x65, 0x6e, 0x20, 0x77, 0x61, 0x74, 0x65, 0x72, 0x66, 0x61, 0x6c, 0x6c, + 0x20, 0x73, 0x68, 0x61, 0x64, 0x65, 0x72, 0x2e, 0x20, 0x41, 0x20, 0x73, + 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x73, 0x63, + 0x72, 0x65, 0x65, 0x6e, 0x20, 0x74, 0x72, 0x69, 0x61, 0x6e, 0x67, 0x6c, + 0x65, 0x20, 0x73, 0x63, 0x72, 0x6f, 0x6c, 0x6c, 0x73, 0x20, 0x74, 0x68, + 0x65, 0x0a, 0x2f, 0x2f, 0x20, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, + 0x73, 0x20, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x74, 0x65, + 0x78, 0x74, 0x75, 0x72, 0x65, 0x20, 0x64, 0x6f, 0x77, 0x6e, 0x20, 0x62, + 0x79, 0x20, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, 0x72, 0x6f, + 0x77, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, + 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, + 0x73, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x72, 0x6f, 0x77, 0x73, 0x0a, 0x2f, + 0x2f, 0x20, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x6f, 0x70, + 0x2c, 0x20, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x61, + 0x63, 0x68, 0x20, 0x62, 0x69, 0x6e, 0x27, 0x73, 0x20, 0x72, 0x61, 0x77, + 0x20, 0x6d, 0x61, 0x67, 0x6e, 0x69, 0x74, 0x75, 0x64, 0x65, 0x20, 0x74, + 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, + 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x20, + 0x74, 0x61, 0x62, 0x6c, 0x65, 0x0a, 0x2f, 0x2f, 0x20, 0x28, 0x61, 0x20, + 0x6c, 0x65, 0x72, 0x70, 0x20, 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, + 0x20, 0x61, 0x64, 0x6a, 0x61, 0x63, 0x65, 0x6e, 0x74, 0x20, 0x65, 0x6e, + 0x74, 0x72, 0x69, 0x65, 0x73, 0x2c, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, + 0x69, 0x6e, 0x67, 0x20, 0x53, 0x70, 0x65, 0x63, 0x74, 0x72, 0x6f, 0x67, + 0x72, 0x61, 0x6d, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x4d, 0x61, 0x70, 0x3a, + 0x3a, 0x6d, 0x61, 0x70, 0x29, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x72, 0x65, + 0x6c, 0x79, 0x0a, 0x2f, 0x2f, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x47, 0x50, 0x55, 0x2e, 0x0a, 0x2f, 0x2f, 0x0a, 0x2f, 0x2f, 0x20, + 0x42, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x3a, 0x0a, 0x2f, 0x2f, + 0x20, 0x20, 0x20, 0x73, 0x65, 0x74, 0x3d, 0x30, 0x20, 0x62, 0x69, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x30, 0x20, 0x3a, 0x20, 0x70, 0x72, 0x65, + 0x76, 0x69, 0x6f, 0x75, 0x73, 0x20, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, + 0x79, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x20, 0x28, 0x55, + 0x56, 0x2e, 0x79, 0x20, 0x3d, 0x20, 0x30, 0x20, 0x69, 0x73, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x74, 0x6f, 0x70, 0x20, 0x72, 0x6f, 0x77, 0x29, 0x0a, + 0x2f, 0x2f, 0x20, 0x20, 0x20, 0x73, 0x65, 0x74, 0x3d, 0x30, 0x20, 0x62, + 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x31, 0x20, 0x3a, 0x20, 0x73, + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x20, 0x28, 0x69, 0x6d, 0x70, 0x6c, + 0x69, 0x63, 0x69, 0x74, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, + 0x2c, 0x20, 0x6c, 0x69, 0x6b, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, + 0x61, 0x74, 0x74, 0x65, 0x20, 0x70, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, + 0x65, 0x29, 0x0a, 0x2f, 0x2f, 0x20, 0x20, 0x20, 0x73, 0x65, 0x74, 0x3d, + 0x30, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x32, 0x20, + 0x3a, 0x20, 0x57, 0x61, 0x74, 0x65, 0x72, 0x66, 0x61, 0x6c, 0x6c, 0x50, + 0x61, 0x72, 0x61, 0x6d, 0x73, 0x20, 0x55, 0x42, 0x4f, 0x0a, 0x2f, 0x2f, + 0x20, 0x20, 0x20, 0x73, 0x65, 0x74, 0x3d, 0x30, 0x20, 0x62, 0x69, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x33, 0x20, 0x3a, 0x20, 0x52, 0x6f, 0x77, + 0x44, 0x61, 0x74, 0x61, 0x20, 0x55, 0x42, 0x4f, 0x20, 0x28, 0x57, 0x20, + 0x2a, 0x20, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, 0x72, 0x61, + 0x77, 0x20, 0x6d, 0x61, 0x67, 0x6e, 0x69, 0x74, 0x75, 0x64, 0x65, 0x73, + 0x2c, 0x20, 0x6f, 0x6c, 0x64, 0x65, 0x73, 0x74, 0x20, 0x72, 0x6f, 0x77, + 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x29, 0x0a, 0x2f, 0x2f, 0x20, 0x20, + 0x20, 0x73, 0x65, 0x74, 0x3d, 0x30, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x3d, 0x34, 0x20, 0x3a, 0x20, 0x4c, 0x75, 0x74, 0x44, 0x61, + 0x74, 0x61, 0x20, 0x55, 0x42, 0x4f, 0x20, 0x28, 0x32, 0x35, 0x36, 0x20, + 0x41, 0x52, 0x47, 0x42, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x65, + 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x29, 0x0a, 0x0a, 0x6c, 0x61, 0x79, + 0x6f, 0x75, 0x74, 0x28, 0x73, 0x65, 0x74, 0x20, 0x3d, 0x20, 0x30, 0x2c, + 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x3d, 0x20, 0x30, + 0x29, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x74, 0x65, + 0x78, 0x74, 0x75, 0x72, 0x65, 0x32, 0x44, 0x20, 0x75, 0x5f, 0x70, 0x72, + 0x65, 0x76, 0x3b, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, 0x73, + 0x65, 0x74, 0x20, 0x3d, 0x20, 0x30, 0x2c, 0x20, 0x62, 0x69, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x20, 0x3d, 0x20, 0x31, 0x29, 0x20, 0x75, 0x6e, 0x69, + 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, + 0x20, 0x20, 0x20, 0x75, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x3b, 0x0a, 0x6c, + 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, 0x73, 0x65, 0x74, 0x20, 0x3d, 0x20, + 0x30, 0x2c, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x3d, + 0x20, 0x32, 0x29, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, + 0x57, 0x61, 0x74, 0x65, 0x72, 0x66, 0x61, 0x6c, 0x6c, 0x50, 0x61, 0x72, + 0x61, 0x6d, 0x73, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x20, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x77, + 0x69, 0x64, 0x74, 0x68, 0x3b, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x77, + 0x61, 0x74, 0x65, 0x72, 0x66, 0x61, 0x6c, 0x6c, 0x20, 0x74, 0x65, 0x78, + 0x74, 0x75, 0x72, 0x65, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x20, 0x69, + 0x6e, 0x20, 0x74, 0x65, 0x78, 0x65, 0x6c, 0x73, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x20, 0x62, 0x69, 0x6e, 0x73, 0x3b, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, + 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x20, 0x6f, 0x66, 0x20, 0x6d, + 0x61, 0x67, 0x6e, 0x69, 0x74, 0x75, 0x64, 0x65, 0x20, 0x62, 0x69, 0x6e, + 0x73, 0x20, 0x70, 0x65, 0x72, 0x20, 0x72, 0x6f, 0x77, 0x20, 0x28, 0x3c, + 0x3d, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x29, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, 0x61, 0x64, 0x30, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, + 0x61, 0x64, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x20, 0x70, 0x61, 0x64, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, 0x61, 0x64, 0x33, 0x3b, + 0x0a, 0x7d, 0x20, 0x70, 0x3b, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, + 0x28, 0x73, 0x65, 0x74, 0x20, 0x3d, 0x20, 0x30, 0x2c, 0x20, 0x62, 0x69, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x3d, 0x20, 0x33, 0x29, 0x20, 0x75, + 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x52, 0x6f, 0x77, 0x44, 0x61, + 0x74, 0x61, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, + 0x34, 0x20, 0x6d, 0x61, 0x67, 0x73, 0x5b, 0x35, 0x31, 0x32, 0x5d, 0x3b, + 0x20, 0x2f, 0x2f, 0x20, 0x32, 0x30, 0x34, 0x38, 0x20, 0x72, 0x61, 0x77, + 0x20, 0x6d, 0x61, 0x67, 0x6e, 0x69, 0x74, 0x75, 0x64, 0x65, 0x73, 0x20, + 0x70, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x20, 0x61, 0x73, 0x20, 0x76, 0x65, + 0x63, 0x34, 0x73, 0x20, 0x28, 0x73, 0x74, 0x64, 0x31, 0x34, 0x30, 0x20, + 0x74, 0x69, 0x67, 0x68, 0x74, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x57, + 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, + 0x6c, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x34, 0x29, 0x0a, 0x7d, 0x20, 0x72, + 0x6f, 0x77, 0x73, 0x3b, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, + 0x73, 0x65, 0x74, 0x20, 0x3d, 0x20, 0x30, 0x2c, 0x20, 0x62, 0x69, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x20, 0x3d, 0x20, 0x34, 0x29, 0x20, 0x75, 0x6e, + 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x4c, 0x75, 0x74, 0x44, 0x61, 0x74, + 0x61, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x75, 0x76, 0x65, 0x63, + 0x34, 0x20, 0x6c, 0x75, 0x74, 0x5b, 0x36, 0x34, 0x5d, 0x3b, 0x20, 0x2f, + 0x2f, 0x20, 0x32, 0x35, 0x36, 0x20, 0x41, 0x52, 0x47, 0x42, 0x20, 0x63, + 0x6f, 0x6c, 0x6f, 0x72, 0x73, 0x20, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x64, + 0x20, 0x61, 0x73, 0x20, 0x75, 0x76, 0x65, 0x63, 0x34, 0x73, 0x0a, 0x7d, + 0x20, 0x6c, 0x75, 0x74, 0x3b, 0x0a, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, + 0x74, 0x28, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3d, + 0x20, 0x30, 0x29, 0x20, 0x6f, 0x75, 0x74, 0x20, 0x76, 0x65, 0x63, 0x34, + 0x20, 0x66, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x0a, + 0x0a, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x66, 0x65, 0x74, 0x63, 0x68, + 0x4d, 0x61, 0x67, 0x28, 0x69, 0x6e, 0x74, 0x20, 0x69, 0x64, 0x78, 0x29, + 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, + 0x6e, 0x20, 0x72, 0x6f, 0x77, 0x73, 0x2e, 0x6d, 0x61, 0x67, 0x73, 0x5b, + 0x69, 0x64, 0x78, 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x5d, 0x5b, 0x69, 0x64, + 0x78, 0x20, 0x26, 0x20, 0x33, 0x5d, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x76, + 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x75, 0x76, + 0x20, 0x3d, 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, + 0x6f, 0x72, 0x64, 0x2e, 0x78, 0x79, 0x20, 0x2f, 0x20, 0x76, 0x65, 0x63, + 0x32, 0x28, 0x70, 0x2e, 0x77, 0x69, 0x64, 0x74, 0x68, 0x2c, 0x20, 0x70, + 0x2e, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x29, 0x3b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, 0x76, 0x2e, 0x79, 0x20, 0x3e, + 0x3d, 0x20, 0x70, 0x2e, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, + 0x2f, 0x20, 0x70, 0x2e, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x29, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x66, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, + 0x3d, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x73, 0x61, + 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, 0x44, 0x28, 0x75, 0x5f, 0x70, 0x72, + 0x65, 0x76, 0x2c, 0x20, 0x75, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x29, 0x2c, + 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, 0x75, 0x76, 0x2e, 0x78, 0x2c, 0x20, + 0x75, 0x76, 0x2e, 0x79, 0x20, 0x2d, 0x20, 0x70, 0x2e, 0x6e, 0x75, 0x6d, + 0x52, 0x6f, 0x77, 0x73, 0x20, 0x2f, 0x20, 0x70, 0x2e, 0x68, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x69, 0x6e, 0x74, 0x20, 0x78, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, + 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, + 0x2e, 0x78, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x69, 0x6e, 0x74, 0x20, 0x79, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, + 0x28, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, + 0x64, 0x2e, 0x79, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x52, 0x6f, 0x77, 0x73, 0x20, 0x61, 0x72, + 0x65, 0x20, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x20, 0x6f, + 0x6c, 0x64, 0x65, 0x73, 0x74, 0x2d, 0x66, 0x69, 0x72, 0x73, 0x74, 0x3b, + 0x20, 0x63, 0x61, 0x6e, 0x76, 0x61, 0x73, 0x20, 0x72, 0x6f, 0x77, 0x20, + 0x79, 0x20, 0x28, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x74, 0x6f, 0x70, 0x29, 0x20, 0x73, 0x68, 0x6f, 0x77, 0x73, 0x20, 0x74, + 0x68, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, + 0x2f, 0x20, 0x28, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, 0x2d, + 0x20, 0x31, 0x20, 0x2d, 0x20, 0x79, 0x29, 0x2d, 0x74, 0x68, 0x20, 0x70, + 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x72, 0x6f, 0x77, 0x2e, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, + 0x72, 0x6f, 0x77, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x70, 0x2e, + 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x29, 0x20, 0x2d, 0x20, 0x31, + 0x20, 0x2d, 0x20, 0x79, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x70, + 0x6f, 0x6c, 0x61, 0x74, 0x65, 0x20, 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, + 0x6e, 0x20, 0x61, 0x64, 0x6a, 0x61, 0x63, 0x65, 0x6e, 0x74, 0x20, 0x6d, + 0x61, 0x67, 0x6e, 0x69, 0x74, 0x75, 0x64, 0x65, 0x20, 0x62, 0x69, 0x6e, + 0x73, 0x20, 0x73, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x20, 0x61, 0x78, 0x69, 0x73, 0x20, + 0x69, 0x73, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, + 0x2f, 0x20, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x20, 0x28, 0x61, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x64, 0x29, 0x20, 0x65, + 0x76, 0x65, 0x6e, 0x20, 0x74, 0x68, 0x6f, 0x75, 0x67, 0x68, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x20, 0x69, + 0x73, 0x20, 0x77, 0x69, 0x64, 0x65, 0x72, 0x20, 0x74, 0x68, 0x61, 0x6e, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x69, 0x6e, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x62, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x20, + 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x78, 0x29, 0x20, 0x2a, + 0x20, 0x28, 0x70, 0x2e, 0x62, 0x69, 0x6e, 0x73, 0x20, 0x2f, 0x20, 0x70, + 0x2e, 0x77, 0x69, 0x64, 0x74, 0x68, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x62, 0x69, 0x6e, + 0x30, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x62, 0x69, 0x6e, 0x50, + 0x6f, 0x73, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x69, 0x6e, 0x74, 0x20, 0x62, 0x69, 0x6e, 0x31, 0x20, 0x3d, 0x20, + 0x6d, 0x69, 0x6e, 0x28, 0x62, 0x69, 0x6e, 0x30, 0x20, 0x2b, 0x20, 0x31, + 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x70, 0x2e, 0x62, 0x69, 0x6e, 0x73, + 0x29, 0x20, 0x2d, 0x20, 0x31, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x66, 0x72, + 0x61, 0x63, 0x42, 0x69, 0x6e, 0x20, 0x3d, 0x20, 0x62, 0x69, 0x6e, 0x50, + 0x6f, 0x73, 0x20, 0x2d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x62, + 0x69, 0x6e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x6d, 0x61, 0x67, 0x20, + 0x3d, 0x20, 0x6d, 0x69, 0x78, 0x28, 0x66, 0x65, 0x74, 0x63, 0x68, 0x4d, + 0x61, 0x67, 0x28, 0x72, 0x6f, 0x77, 0x20, 0x2a, 0x20, 0x69, 0x6e, 0x74, + 0x28, 0x70, 0x2e, 0x62, 0x69, 0x6e, 0x73, 0x29, 0x20, 0x2b, 0x20, 0x62, + 0x69, 0x6e, 0x30, 0x29, 0x2c, 0x20, 0x66, 0x65, 0x74, 0x63, 0x68, 0x4d, + 0x61, 0x67, 0x28, 0x72, 0x6f, 0x77, 0x20, 0x2a, 0x20, 0x69, 0x6e, 0x74, + 0x28, 0x70, 0x2e, 0x62, 0x69, 0x6e, 0x73, 0x29, 0x20, 0x2b, 0x20, 0x62, + 0x69, 0x6e, 0x31, 0x29, 0x2c, 0x20, 0x66, 0x72, 0x61, 0x63, 0x42, 0x69, + 0x6e, 0x29, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x74, 0x20, 0x3d, 0x20, 0x63, + 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x6d, 0x61, 0x67, 0x2c, 0x20, 0x30, 0x2e, + 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x32, 0x35, + 0x35, 0x2e, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x69, 0x6e, 0x74, 0x20, 0x69, 0x30, 0x20, 0x3d, 0x20, 0x69, 0x6e, + 0x74, 0x28, 0x74, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x69, 0x31, 0x20, 0x3d, 0x20, 0x6d, + 0x69, 0x6e, 0x28, 0x69, 0x30, 0x20, 0x2b, 0x20, 0x31, 0x2c, 0x20, 0x32, + 0x35, 0x35, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x66, 0x72, 0x61, 0x63, 0x20, + 0x3d, 0x20, 0x74, 0x20, 0x2d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, + 0x69, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x75, 0x69, 0x6e, 0x74, 0x20, 0x63, 0x30, 0x20, 0x3d, 0x20, 0x6c, + 0x75, 0x74, 0x2e, 0x6c, 0x75, 0x74, 0x5b, 0x69, 0x30, 0x20, 0x3e, 0x3e, + 0x20, 0x32, 0x5d, 0x5b, 0x69, 0x30, 0x20, 0x26, 0x20, 0x33, 0x5d, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x75, 0x69, 0x6e, + 0x74, 0x20, 0x63, 0x31, 0x20, 0x3d, 0x20, 0x6c, 0x75, 0x74, 0x2e, 0x6c, + 0x75, 0x74, 0x5b, 0x69, 0x31, 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x5d, 0x5b, + 0x69, 0x31, 0x20, 0x26, 0x20, 0x33, 0x5d, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x63, 0x6f, + 0x6c, 0x30, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x31, + 0x36, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x30, 0x20, 0x3e, 0x3e, + 0x20, 0x38, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x63, 0x30, 0x20, 0x26, 0x20, + 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x28, 0x28, 0x63, 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x34, 0x29, 0x20, + 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x29, 0x20, 0x2f, 0x20, 0x32, + 0x35, 0x35, 0x2e, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x63, 0x6f, 0x6c, 0x31, 0x20, + 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x28, 0x28, 0x63, 0x31, 0x20, 0x3e, 0x3e, 0x20, 0x31, 0x36, 0x29, 0x20, + 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x28, 0x28, 0x63, 0x31, 0x20, 0x3e, 0x3e, 0x20, 0x38, 0x29, + 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x28, 0x63, 0x31, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, + 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, + 0x31, 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x34, 0x29, 0x20, 0x26, 0x20, 0x32, + 0x35, 0x35, 0x75, 0x29, 0x29, 0x20, 0x2f, 0x20, 0x32, 0x35, 0x35, 0x2e, + 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, + 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20, 0x6d, + 0x69, 0x78, 0x28, 0x63, 0x6f, 0x6c, 0x30, 0x2c, 0x20, 0x63, 0x6f, 0x6c, + 0x31, 0x2c, 0x20, 0x66, 0x72, 0x61, 0x63, 0x29, 0x3b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x0a, 0x52, 0x45, 0x46, 0x4c, 0x11, 0x06, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x52, 0x6f, + 0x77, 0x44, 0x61, 0x74, 0x61, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x6d, 0x61, 0x67, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x20, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x57, 0x61, 0x74, 0x65, + 0x72, 0x66, 0x61, 0x6c, 0x6c, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x6e, 0x75, 0x6d, 0x52, 0x6f, + 0x77, 0x73, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x77, 0x69, + 0x64, 0x74, 0x68, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x62, 0x69, 0x6e, 0x73, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x70, 0x61, 0x64, 0x30, 0x10, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x70, 0x61, 0x64, 0x31, 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x70, 0x61, 0x64, 0x32, 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x70, 0x61, 0x64, 0x33, 0x1c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x29, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0x07, 0x00, 0x00, 0x00, 0x4c, 0x75, 0x74, 0x44, 0x61, 0x74, 0x61, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x6c, 0x75, 0x74, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0xb1, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, + 0x00, 0x66, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x79, 0x75, 0x70, + 0x5f, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x75, 0x5f, + 0x70, 0x72, 0x65, 0x76, 0x5f, 0x75, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x07, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x75, 0x5f, 0x70, 0x72, 0x65, 0x76, + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x75, 0x5f, 0x73, 0x61, 0x6d, + 0x70, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x5f, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x45, 0x44, 0x5f, 0x49, 0x44, + 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x45, 0x52, 0x5f, 0x46, 0x49, 0x58, + 0x55, 0x50, 0x5f, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, + 0x6f, 0x72, 0x64, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, + 0x74, 0x79, 0x28, 0x31, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, + 0x00, 0x79, 0x75, 0x70, 0x5f, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, + 0x64, 0x5f, 0x75, 0x5f, 0x70, 0x72, 0x65, 0x76, 0x5f, 0x75, 0x5f, 0x73, + 0x61, 0x6d, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x41, 0x52, 0x54, + 0xfe, 0x1d, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x6d, 0x61, 0x69, 0x6e, 0x78, 0x09, 0x00, 0x00, 0x63, 0x62, 0x75, 0x66, + 0x66, 0x65, 0x72, 0x20, 0x52, 0x6f, 0x77, 0x44, 0x61, 0x74, 0x61, 0x20, + 0x3a, 0x20, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x28, 0x62, + 0x33, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x34, 0x20, 0x72, 0x6f, 0x77, 0x73, 0x5f, 0x6d, 0x61, 0x67, + 0x73, 0x5b, 0x35, 0x31, 0x32, 0x5d, 0x20, 0x3a, 0x20, 0x70, 0x61, 0x63, + 0x6b, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x28, 0x63, 0x30, 0x29, 0x3b, + 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x63, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, + 0x20, 0x57, 0x61, 0x74, 0x65, 0x72, 0x66, 0x61, 0x6c, 0x6c, 0x50, 0x61, + 0x72, 0x61, 0x6d, 0x73, 0x20, 0x3a, 0x20, 0x72, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x65, 0x72, 0x28, 0x62, 0x32, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, 0x5f, 0x6e, 0x75, + 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, 0x3a, 0x20, 0x70, 0x61, 0x63, 0x6b, + 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x28, 0x63, 0x30, 0x29, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, 0x5f, + 0x77, 0x69, 0x64, 0x74, 0x68, 0x20, 0x3a, 0x20, 0x70, 0x61, 0x63, 0x6b, + 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x28, 0x63, 0x30, 0x2e, 0x79, 0x29, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, + 0x70, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x20, 0x3a, 0x20, 0x70, + 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x28, 0x63, 0x30, + 0x2e, 0x7a, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x20, 0x70, 0x5f, 0x62, 0x69, 0x6e, 0x73, 0x20, 0x3a, 0x20, + 0x70, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x28, 0x63, + 0x30, 0x2e, 0x77, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x20, 0x70, 0x5f, 0x70, 0x61, 0x64, 0x30, 0x20, 0x3a, + 0x20, 0x70, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x28, + 0x63, 0x31, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x20, 0x70, 0x5f, 0x70, 0x61, 0x64, 0x31, 0x20, 0x3a, 0x20, + 0x70, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x28, 0x63, + 0x31, 0x2e, 0x79, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x20, 0x70, 0x5f, 0x70, 0x61, 0x64, 0x32, 0x20, 0x3a, + 0x20, 0x70, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x28, + 0x63, 0x31, 0x2e, 0x7a, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, 0x5f, 0x70, 0x61, 0x64, 0x33, 0x20, + 0x3a, 0x20, 0x70, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, + 0x28, 0x63, 0x31, 0x2e, 0x77, 0x29, 0x3b, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, + 0x63, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x20, 0x4c, 0x75, 0x74, 0x44, + 0x61, 0x74, 0x61, 0x20, 0x3a, 0x20, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x65, 0x72, 0x28, 0x62, 0x34, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x75, 0x69, 0x6e, 0x74, 0x34, 0x20, 0x6c, 0x75, 0x74, 0x5f, 0x6c, + 0x75, 0x74, 0x5b, 0x36, 0x34, 0x5d, 0x20, 0x3a, 0x20, 0x70, 0x61, 0x63, + 0x6b, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x28, 0x63, 0x30, 0x29, 0x3b, + 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, + 0x32, 0x44, 0x3c, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x3e, 0x20, 0x75, + 0x5f, 0x70, 0x72, 0x65, 0x76, 0x20, 0x3a, 0x20, 0x72, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x65, 0x72, 0x28, 0x74, 0x30, 0x29, 0x3b, 0x0a, 0x53, 0x61, + 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x20, 0x75, + 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x20, 0x3a, 0x20, 0x72, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x65, 0x72, 0x28, 0x73, 0x31, 0x29, 0x3b, 0x0a, 0x0a, 0x73, + 0x74, 0x61, 0x74, 0x69, 0x63, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, + 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, + 0x64, 0x3b, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x20, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x34, 0x20, 0x66, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, + 0x6f, 0x72, 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, + 0x53, 0x50, 0x49, 0x52, 0x56, 0x5f, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x5f, + 0x49, 0x6e, 0x70, 0x75, 0x74, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, + 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x20, 0x3a, 0x20, 0x53, 0x56, + 0x5f, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3b, 0x0a, 0x7d, + 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x53, 0x50, + 0x49, 0x52, 0x56, 0x5f, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x5f, 0x4f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x66, 0x72, 0x61, 0x67, 0x43, 0x6f, + 0x6c, 0x6f, 0x72, 0x20, 0x3a, 0x20, 0x53, 0x56, 0x5f, 0x54, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x30, 0x3b, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x20, 0x66, 0x65, 0x74, 0x63, 0x68, 0x4d, 0x61, 0x67, + 0x28, 0x69, 0x6e, 0x74, 0x20, 0x69, 0x64, 0x78, 0x29, 0x0a, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x72, + 0x6f, 0x77, 0x73, 0x5f, 0x6d, 0x61, 0x67, 0x73, 0x5b, 0x69, 0x64, 0x78, + 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x5d, 0x5b, 0x69, 0x64, 0x78, 0x20, 0x26, + 0x20, 0x33, 0x5d, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x76, 0x6f, 0x69, 0x64, + 0x20, 0x66, 0x72, 0x61, 0x67, 0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, + 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x32, 0x20, 0x75, 0x76, 0x20, 0x3d, 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, + 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x78, 0x79, 0x20, 0x2f, + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x28, 0x70, 0x5f, 0x77, 0x69, + 0x64, 0x74, 0x68, 0x2c, 0x20, 0x70, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, + 0x75, 0x76, 0x2e, 0x79, 0x20, 0x3e, 0x3d, 0x20, 0x28, 0x70, 0x5f, 0x6e, + 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, 0x2f, 0x20, 0x70, 0x5f, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x72, + 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20, 0x75, 0x5f, + 0x70, 0x72, 0x65, 0x76, 0x2e, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x28, + 0x75, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x32, 0x28, 0x75, 0x76, 0x2e, 0x78, 0x2c, 0x20, 0x75, 0x76, 0x2e, + 0x79, 0x20, 0x2d, 0x20, 0x28, 0x70, 0x5f, 0x6e, 0x75, 0x6d, 0x52, 0x6f, + 0x77, 0x73, 0x20, 0x2f, 0x20, 0x70, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x29, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, + 0x6e, 0x74, 0x20, 0x78, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x67, + 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, + 0x78, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x69, 0x6e, 0x74, 0x20, 0x79, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, + 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, + 0x2e, 0x79, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x69, 0x6e, 0x74, 0x20, 0x72, 0x6f, 0x77, 0x20, 0x3d, 0x20, 0x28, + 0x69, 0x6e, 0x74, 0x28, 0x70, 0x5f, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, + 0x73, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x29, 0x20, 0x2d, 0x20, 0x79, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x20, 0x62, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x20, 0x3d, 0x20, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x78, 0x29, 0x20, 0x2a, 0x20, 0x28, + 0x70, 0x5f, 0x62, 0x69, 0x6e, 0x73, 0x20, 0x2f, 0x20, 0x70, 0x5f, 0x77, + 0x69, 0x64, 0x74, 0x68, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x62, 0x69, 0x6e, 0x30, 0x20, + 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x62, 0x69, 0x6e, 0x50, 0x6f, 0x73, + 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, + 0x6e, 0x74, 0x20, 0x62, 0x69, 0x6e, 0x31, 0x20, 0x3d, 0x20, 0x6d, 0x69, + 0x6e, 0x28, 0x28, 0x62, 0x69, 0x6e, 0x30, 0x20, 0x2b, 0x20, 0x31, 0x29, + 0x2c, 0x20, 0x28, 0x69, 0x6e, 0x74, 0x28, 0x70, 0x5f, 0x62, 0x69, 0x6e, + 0x73, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, + 0x66, 0x72, 0x61, 0x63, 0x42, 0x69, 0x6e, 0x20, 0x3d, 0x20, 0x62, 0x69, + 0x6e, 0x50, 0x6f, 0x73, 0x20, 0x2d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x28, 0x62, 0x69, 0x6e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x70, 0x61, 0x72, 0x61, + 0x6d, 0x20, 0x3d, 0x20, 0x28, 0x72, 0x6f, 0x77, 0x20, 0x2a, 0x20, 0x69, + 0x6e, 0x74, 0x28, 0x70, 0x5f, 0x62, 0x69, 0x6e, 0x73, 0x29, 0x29, 0x20, + 0x2b, 0x20, 0x62, 0x69, 0x6e, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x70, 0x61, 0x72, 0x61, + 0x6d, 0x5f, 0x31, 0x20, 0x3d, 0x20, 0x28, 0x72, 0x6f, 0x77, 0x20, 0x2a, + 0x20, 0x69, 0x6e, 0x74, 0x28, 0x70, 0x5f, 0x62, 0x69, 0x6e, 0x73, 0x29, + 0x29, 0x20, 0x2b, 0x20, 0x62, 0x69, 0x6e, 0x31, 0x3b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, + 0x6d, 0x61, 0x67, 0x20, 0x3d, 0x20, 0x6c, 0x65, 0x72, 0x70, 0x28, 0x66, + 0x65, 0x74, 0x63, 0x68, 0x4d, 0x61, 0x67, 0x28, 0x70, 0x61, 0x72, 0x61, + 0x6d, 0x29, 0x2c, 0x20, 0x66, 0x65, 0x74, 0x63, 0x68, 0x4d, 0x61, 0x67, + 0x28, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x5f, 0x31, 0x29, 0x2c, 0x20, 0x66, + 0x72, 0x61, 0x63, 0x42, 0x69, 0x6e, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x74, + 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x6d, 0x61, 0x67, + 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x66, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x66, + 0x29, 0x20, 0x2a, 0x20, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x66, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, + 0x69, 0x30, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x74, 0x29, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, + 0x20, 0x69, 0x31, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x6e, 0x28, 0x28, 0x69, + 0x30, 0x20, 0x2b, 0x20, 0x31, 0x29, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x29, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x20, 0x66, 0x72, 0x61, 0x63, 0x20, 0x3d, 0x20, 0x74, + 0x20, 0x2d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x69, 0x30, 0x29, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x75, 0x69, + 0x6e, 0x74, 0x20, 0x63, 0x30, 0x20, 0x3d, 0x20, 0x6c, 0x75, 0x74, 0x5f, + 0x6c, 0x75, 0x74, 0x5b, 0x69, 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x5d, + 0x5b, 0x69, 0x30, 0x20, 0x26, 0x20, 0x33, 0x5d, 0x3b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x20, 0x63, + 0x31, 0x20, 0x3d, 0x20, 0x6c, 0x75, 0x74, 0x5f, 0x6c, 0x75, 0x74, 0x5b, + 0x69, 0x31, 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x5d, 0x5b, 0x69, 0x31, 0x20, + 0x26, 0x20, 0x33, 0x5d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x63, 0x6f, 0x6c, + 0x30, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x28, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x30, 0x20, 0x3e, 0x3e, 0x20, + 0x75, 0x69, 0x6e, 0x74, 0x28, 0x31, 0x36, 0x29, 0x29, 0x20, 0x26, 0x20, + 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x28, 0x28, 0x63, 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x75, 0x69, 0x6e, 0x74, + 0x28, 0x38, 0x29, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, + 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x63, 0x30, 0x20, 0x26, + 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x28, 0x28, 0x63, 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x75, 0x69, 0x6e, + 0x74, 0x28, 0x32, 0x34, 0x29, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, + 0x75, 0x29, 0x29, 0x20, 0x2f, 0x20, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x66, + 0x2e, 0x78, 0x78, 0x78, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x63, 0x6f, + 0x6c, 0x31, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x28, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x31, 0x20, 0x3e, 0x3e, + 0x20, 0x75, 0x69, 0x6e, 0x74, 0x28, 0x31, 0x36, 0x29, 0x29, 0x20, 0x26, + 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x28, 0x28, 0x63, 0x31, 0x20, 0x3e, 0x3e, 0x20, 0x75, 0x69, 0x6e, + 0x74, 0x28, 0x38, 0x29, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, + 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x63, 0x31, 0x20, + 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x28, 0x28, 0x63, 0x31, 0x20, 0x3e, 0x3e, 0x20, 0x75, 0x69, + 0x6e, 0x74, 0x28, 0x32, 0x34, 0x29, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, + 0x35, 0x75, 0x29, 0x29, 0x20, 0x2f, 0x20, 0x32, 0x35, 0x35, 0x2e, 0x30, + 0x66, 0x2e, 0x78, 0x78, 0x78, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x66, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, + 0x72, 0x20, 0x3d, 0x20, 0x6c, 0x65, 0x72, 0x70, 0x28, 0x63, 0x6f, 0x6c, + 0x30, 0x2c, 0x20, 0x63, 0x6f, 0x6c, 0x31, 0x2c, 0x20, 0x66, 0x72, 0x61, + 0x63, 0x2e, 0x78, 0x78, 0x78, 0x78, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x7d, 0x0a, 0x7d, 0x0a, 0x0a, 0x53, 0x50, 0x49, 0x52, 0x56, 0x5f, + 0x43, 0x72, 0x6f, 0x73, 0x73, 0x5f, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x53, 0x50, 0x49, 0x52, 0x56, 0x5f, + 0x43, 0x72, 0x6f, 0x73, 0x73, 0x5f, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x20, + 0x73, 0x74, 0x61, 0x67, 0x65, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x29, + 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, + 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x20, 0x3d, 0x20, 0x73, 0x74, + 0x61, 0x67, 0x65, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2e, 0x67, 0x6c, + 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, + 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x77, 0x20, 0x3d, 0x20, 0x31, 0x2e, 0x30, + 0x20, 0x2f, 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, + 0x6f, 0x72, 0x64, 0x2e, 0x77, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, + 0x72, 0x61, 0x67, 0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x53, 0x50, 0x49, 0x52, 0x56, 0x5f, 0x43, 0x72, + 0x6f, 0x73, 0x73, 0x5f, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x20, 0x73, + 0x74, 0x61, 0x67, 0x65, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x61, 0x67, 0x65, 0x5f, 0x6f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x2e, 0x66, 0x72, 0x61, 0x67, 0x43, 0x6f, + 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20, 0x66, 0x72, 0x61, 0x67, 0x43, 0x6f, + 0x6c, 0x6f, 0x72, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, + 0x75, 0x72, 0x6e, 0x20, 0x73, 0x74, 0x61, 0x67, 0x65, 0x5f, 0x6f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x3b, 0x0a, 0x7d, 0x0a, 0x49, 0x53, 0x52, 0x43, + 0xe4, 0x0e, 0x00, 0x00, 0x2f, 0x2a, 0x0a, 0x20, 0x20, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x54, 0x68, 0x69, 0x73, + 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x69, 0x73, 0x20, 0x70, 0x61, 0x72, + 0x74, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x59, 0x55, 0x50, + 0x20, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x2e, 0x0a, 0x20, 0x20, + 0x20, 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, + 0x63, 0x29, 0x20, 0x32, 0x30, 0x32, 0x36, 0x20, 0x2d, 0x20, 0x6b, 0x75, + 0x6e, 0x69, 0x74, 0x6f, 0x6b, 0x69, 0x40, 0x67, 0x6d, 0x61, 0x69, 0x6c, + 0x2e, 0x63, 0x6f, 0x6d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x59, 0x55, 0x50, + 0x20, 0x69, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x6f, 0x70, 0x65, 0x6e, 0x20, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x6c, 0x69, 0x62, 0x72, 0x61, + 0x72, 0x79, 0x20, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x74, + 0x6f, 0x20, 0x6f, 0x70, 0x65, 0x6e, 0x2d, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x20, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x69, 0x6e, 0x67, 0x2e, + 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x54, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x64, + 0x65, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x20, 0x69, + 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, + 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, + 0x75, 0x6e, 0x64, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x65, + 0x72, 0x6d, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x49, + 0x53, 0x43, 0x20, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a, 0x20, + 0x20, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, + 0x2e, 0x69, 0x73, 0x63, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x64, 0x6f, 0x77, + 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x2f, 0x73, 0x6f, 0x66, 0x74, 0x77, + 0x61, 0x72, 0x65, 0x2d, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x2d, + 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2f, 0x69, 0x73, 0x63, 0x2d, 0x6c, + 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x2e, 0x20, 0x50, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x74, 0x6f, + 0x20, 0x75, 0x73, 0x65, 0x2c, 0x20, 0x63, 0x6f, 0x70, 0x79, 0x2c, 0x20, + 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x2f, + 0x6f, 0x72, 0x20, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, + 0x65, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x73, 0x6f, 0x66, 0x74, 0x77, + 0x61, 0x72, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x6e, 0x79, 0x20, + 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x20, 0x77, 0x69, 0x74, 0x68, + 0x20, 0x6f, 0x72, 0x0a, 0x20, 0x20, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, + 0x75, 0x74, 0x20, 0x66, 0x65, 0x65, 0x20, 0x69, 0x73, 0x20, 0x68, 0x65, + 0x72, 0x65, 0x62, 0x79, 0x20, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, + 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x74, 0x68, + 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, + 0x20, 0x63, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x6e, + 0x6f, 0x74, 0x69, 0x63, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x0a, 0x20, 0x20, + 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x20, + 0x61, 0x70, 0x70, 0x65, 0x61, 0x72, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x6c, + 0x6c, 0x20, 0x63, 0x6f, 0x70, 0x69, 0x65, 0x73, 0x2e, 0x0a, 0x0a, 0x20, + 0x20, 0x20, 0x59, 0x55, 0x50, 0x20, 0x49, 0x53, 0x20, 0x50, 0x52, 0x4f, + 0x56, 0x49, 0x44, 0x45, 0x44, 0x20, 0x22, 0x41, 0x53, 0x20, 0x49, 0x53, + 0x22, 0x20, 0x57, 0x49, 0x54, 0x48, 0x4f, 0x55, 0x54, 0x20, 0x41, 0x4e, + 0x59, 0x20, 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x59, 0x2c, 0x20, + 0x41, 0x4e, 0x44, 0x20, 0x41, 0x4c, 0x4c, 0x20, 0x57, 0x41, 0x52, 0x52, + 0x41, 0x4e, 0x54, 0x49, 0x45, 0x53, 0x2c, 0x20, 0x57, 0x48, 0x45, 0x54, + 0x48, 0x45, 0x52, 0x0a, 0x20, 0x20, 0x20, 0x45, 0x58, 0x50, 0x52, 0x45, + 0x53, 0x53, 0x45, 0x44, 0x20, 0x4f, 0x52, 0x20, 0x49, 0x4d, 0x50, 0x4c, + 0x49, 0x45, 0x44, 0x2c, 0x20, 0x49, 0x4e, 0x43, 0x4c, 0x55, 0x44, 0x49, + 0x4e, 0x47, 0x20, 0x4d, 0x45, 0x52, 0x43, 0x48, 0x41, 0x4e, 0x54, 0x41, + 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x20, 0x41, 0x4e, 0x44, 0x20, 0x46, + 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x20, 0x46, 0x4f, 0x52, 0x20, 0x50, + 0x55, 0x52, 0x50, 0x4f, 0x53, 0x45, 0x2c, 0x20, 0x41, 0x52, 0x45, 0x0a, + 0x20, 0x20, 0x20, 0x44, 0x49, 0x53, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x45, + 0x44, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x0a, 0x2a, 0x2f, 0x0a, 0x0a, 0x23, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x20, 0x34, 0x35, 0x30, 0x0a, 0x0a, 0x2f, 0x2f, 0x20, 0x46, 0x75, + 0x6c, 0x6c, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x61, 0x74, + 0x65, 0x72, 0x66, 0x61, 0x6c, 0x6c, 0x20, 0x73, 0x68, 0x61, 0x64, 0x65, + 0x72, 0x2e, 0x20, 0x41, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, + 0x66, 0x75, 0x6c, 0x6c, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x74, + 0x72, 0x69, 0x61, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x73, 0x63, 0x72, 0x6f, + 0x6c, 0x6c, 0x73, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x2f, 0x2f, 0x20, 0x70, + 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x20, 0x68, 0x69, 0x73, 0x74, + 0x6f, 0x72, 0x79, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x20, + 0x64, 0x6f, 0x77, 0x6e, 0x20, 0x62, 0x79, 0x20, 0x6e, 0x75, 0x6d, 0x52, + 0x6f, 0x77, 0x73, 0x20, 0x72, 0x6f, 0x77, 0x73, 0x20, 0x61, 0x6e, 0x64, + 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, 0x6e, 0x65, 0x77, 0x20, + 0x72, 0x6f, 0x77, 0x73, 0x0a, 0x2f, 0x2f, 0x20, 0x61, 0x74, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x74, 0x6f, 0x70, 0x2c, 0x20, 0x6d, 0x61, 0x70, 0x70, + 0x69, 0x6e, 0x67, 0x20, 0x65, 0x61, 0x63, 0x68, 0x20, 0x62, 0x69, 0x6e, + 0x27, 0x73, 0x20, 0x72, 0x61, 0x77, 0x20, 0x6d, 0x61, 0x67, 0x6e, 0x69, + 0x74, 0x75, 0x64, 0x65, 0x20, 0x74, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x6c, + 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x20, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x0a, + 0x2f, 0x2f, 0x20, 0x28, 0x61, 0x20, 0x6c, 0x65, 0x72, 0x70, 0x20, 0x62, + 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x20, 0x61, 0x64, 0x6a, 0x61, 0x63, + 0x65, 0x6e, 0x74, 0x20, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x2c, + 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x53, 0x70, + 0x65, 0x63, 0x74, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x43, 0x6f, 0x6c, + 0x6f, 0x72, 0x4d, 0x61, 0x70, 0x3a, 0x3a, 0x6d, 0x61, 0x70, 0x29, 0x20, + 0x65, 0x6e, 0x74, 0x69, 0x72, 0x65, 0x6c, 0x79, 0x0a, 0x2f, 0x2f, 0x20, + 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x50, 0x55, 0x2e, 0x0a, + 0x2f, 0x2f, 0x0a, 0x2f, 0x2f, 0x20, 0x42, 0x69, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x73, 0x3a, 0x0a, 0x2f, 0x2f, 0x20, 0x20, 0x20, 0x73, 0x65, 0x74, + 0x3d, 0x30, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x30, + 0x20, 0x3a, 0x20, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x20, + 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x74, 0x65, 0x78, 0x74, + 0x75, 0x72, 0x65, 0x20, 0x28, 0x55, 0x56, 0x2e, 0x79, 0x20, 0x3d, 0x20, + 0x30, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x6f, 0x70, + 0x20, 0x72, 0x6f, 0x77, 0x29, 0x0a, 0x2f, 0x2f, 0x20, 0x20, 0x20, 0x73, + 0x65, 0x74, 0x3d, 0x30, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x3d, 0x31, 0x20, 0x3a, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, + 0x20, 0x28, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x63, 0x69, 0x74, 0x20, 0x64, + 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x2c, 0x20, 0x6c, 0x69, 0x6b, 0x65, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x61, 0x74, 0x74, 0x65, 0x20, 0x70, + 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x29, 0x0a, 0x2f, 0x2f, 0x20, + 0x20, 0x20, 0x73, 0x65, 0x74, 0x3d, 0x30, 0x20, 0x62, 0x69, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x3d, 0x32, 0x20, 0x3a, 0x20, 0x57, 0x61, 0x74, 0x65, + 0x72, 0x66, 0x61, 0x6c, 0x6c, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x20, + 0x55, 0x42, 0x4f, 0x0a, 0x2f, 0x2f, 0x20, 0x20, 0x20, 0x73, 0x65, 0x74, + 0x3d, 0x30, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x33, + 0x20, 0x3a, 0x20, 0x52, 0x6f, 0x77, 0x44, 0x61, 0x74, 0x61, 0x20, 0x55, + 0x42, 0x4f, 0x20, 0x28, 0x57, 0x20, 0x2a, 0x20, 0x6e, 0x75, 0x6d, 0x52, + 0x6f, 0x77, 0x73, 0x20, 0x72, 0x61, 0x77, 0x20, 0x6d, 0x61, 0x67, 0x6e, + 0x69, 0x74, 0x75, 0x64, 0x65, 0x73, 0x2c, 0x20, 0x6f, 0x6c, 0x64, 0x65, + 0x73, 0x74, 0x20, 0x72, 0x6f, 0x77, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, + 0x29, 0x0a, 0x2f, 0x2f, 0x20, 0x20, 0x20, 0x73, 0x65, 0x74, 0x3d, 0x30, + 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x34, 0x20, 0x3a, + 0x20, 0x4c, 0x75, 0x74, 0x44, 0x61, 0x74, 0x61, 0x20, 0x55, 0x42, 0x4f, + 0x20, 0x28, 0x32, 0x35, 0x36, 0x20, 0x41, 0x52, 0x47, 0x42, 0x20, 0x63, + 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, + 0x29, 0x0a, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, 0x73, 0x65, + 0x74, 0x20, 0x3d, 0x20, 0x30, 0x2c, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x20, 0x3d, 0x20, 0x30, 0x29, 0x20, 0x75, 0x6e, 0x69, 0x66, + 0x6f, 0x72, 0x6d, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x32, + 0x44, 0x20, 0x75, 0x5f, 0x70, 0x72, 0x65, 0x76, 0x3b, 0x0a, 0x6c, 0x61, + 0x79, 0x6f, 0x75, 0x74, 0x28, 0x73, 0x65, 0x74, 0x20, 0x3d, 0x20, 0x30, + 0x2c, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x3d, 0x20, + 0x31, 0x29, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x73, + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x20, 0x20, 0x20, 0x75, 0x5f, 0x73, + 0x61, 0x6d, 0x70, 0x3b, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, + 0x73, 0x65, 0x74, 0x20, 0x3d, 0x20, 0x30, 0x2c, 0x20, 0x62, 0x69, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x20, 0x3d, 0x20, 0x32, 0x29, 0x20, 0x75, 0x6e, + 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x57, 0x61, 0x74, 0x65, 0x72, 0x66, + 0x61, 0x6c, 0x6c, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x0a, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x6e, 0x75, + 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3b, 0x20, + 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x77, 0x61, 0x74, 0x65, 0x72, 0x66, 0x61, + 0x6c, 0x6c, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x20, 0x77, + 0x69, 0x64, 0x74, 0x68, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x65, 0x78, 0x65, + 0x6c, 0x73, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x62, 0x69, 0x6e, 0x73, 0x3b, + 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, + 0x72, 0x20, 0x6f, 0x66, 0x20, 0x6d, 0x61, 0x67, 0x6e, 0x69, 0x74, 0x75, + 0x64, 0x65, 0x20, 0x62, 0x69, 0x6e, 0x73, 0x20, 0x70, 0x65, 0x72, 0x20, + 0x72, 0x6f, 0x77, 0x20, 0x28, 0x3c, 0x3d, 0x20, 0x77, 0x69, 0x64, 0x74, + 0x68, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x20, 0x70, 0x61, 0x64, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, 0x61, 0x64, 0x31, 0x3b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, 0x61, 0x64, + 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x20, 0x70, 0x61, 0x64, 0x33, 0x3b, 0x0a, 0x7d, 0x20, 0x70, 0x3b, 0x0a, + 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, 0x73, 0x65, 0x74, 0x20, 0x3d, + 0x20, 0x30, 0x2c, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, + 0x3d, 0x20, 0x33, 0x29, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, + 0x20, 0x52, 0x6f, 0x77, 0x44, 0x61, 0x74, 0x61, 0x0a, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x6d, 0x61, 0x67, 0x73, + 0x5b, 0x35, 0x31, 0x32, 0x5d, 0x3b, 0x20, 0x2f, 0x2f, 0x20, 0x32, 0x30, + 0x34, 0x38, 0x20, 0x72, 0x61, 0x77, 0x20, 0x6d, 0x61, 0x67, 0x6e, 0x69, + 0x74, 0x75, 0x64, 0x65, 0x73, 0x20, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x64, + 0x20, 0x61, 0x73, 0x20, 0x76, 0x65, 0x63, 0x34, 0x73, 0x20, 0x28, 0x73, + 0x74, 0x64, 0x31, 0x34, 0x30, 0x20, 0x74, 0x69, 0x67, 0x68, 0x74, 0x20, + 0x77, 0x68, 0x65, 0x6e, 0x20, 0x57, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, + 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x6f, 0x66, 0x20, + 0x34, 0x29, 0x0a, 0x7d, 0x20, 0x72, 0x6f, 0x77, 0x73, 0x3b, 0x0a, 0x6c, + 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, 0x73, 0x65, 0x74, 0x20, 0x3d, 0x20, + 0x30, 0x2c, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x3d, + 0x20, 0x34, 0x29, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, + 0x4c, 0x75, 0x74, 0x44, 0x61, 0x74, 0x61, 0x0a, 0x7b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x75, 0x76, 0x65, 0x63, 0x34, 0x20, 0x6c, 0x75, 0x74, 0x5b, + 0x36, 0x34, 0x5d, 0x3b, 0x20, 0x2f, 0x2f, 0x20, 0x32, 0x35, 0x36, 0x20, + 0x41, 0x52, 0x47, 0x42, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x73, 0x20, + 0x70, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x20, 0x61, 0x73, 0x20, 0x75, 0x76, + 0x65, 0x63, 0x34, 0x73, 0x0a, 0x7d, 0x20, 0x6c, 0x75, 0x74, 0x3b, 0x0a, + 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, 0x6c, 0x6f, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x30, 0x29, 0x20, 0x6f, 0x75, + 0x74, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x66, 0x72, 0x61, 0x67, 0x43, + 0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x0a, 0x0a, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x20, 0x66, 0x65, 0x74, 0x63, 0x68, 0x4d, 0x61, 0x67, 0x28, 0x69, 0x6e, + 0x74, 0x20, 0x69, 0x64, 0x78, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x72, 0x6f, 0x77, 0x73, + 0x2e, 0x6d, 0x61, 0x67, 0x73, 0x5b, 0x69, 0x64, 0x78, 0x20, 0x3e, 0x3e, + 0x20, 0x32, 0x5d, 0x5b, 0x69, 0x64, 0x78, 0x20, 0x26, 0x20, 0x33, 0x5d, + 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, + 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, + 0x65, 0x63, 0x32, 0x20, 0x75, 0x76, 0x20, 0x3d, 0x20, 0x67, 0x6c, 0x5f, + 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x78, 0x79, + 0x20, 0x2f, 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, 0x70, 0x2e, 0x77, 0x69, + 0x64, 0x74, 0x68, 0x2c, 0x20, 0x70, 0x2e, 0x68, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, + 0x75, 0x76, 0x2e, 0x79, 0x20, 0x3e, 0x3d, 0x20, 0x70, 0x2e, 0x6e, 0x75, + 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, 0x2f, 0x20, 0x70, 0x2e, 0x68, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x72, 0x61, 0x67, + 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20, 0x74, 0x65, 0x78, 0x74, + 0x75, 0x72, 0x65, 0x28, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, + 0x44, 0x28, 0x75, 0x5f, 0x70, 0x72, 0x65, 0x76, 0x2c, 0x20, 0x75, 0x5f, + 0x73, 0x61, 0x6d, 0x70, 0x29, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, + 0x75, 0x76, 0x2e, 0x78, 0x2c, 0x20, 0x75, 0x76, 0x2e, 0x79, 0x20, 0x2d, + 0x20, 0x70, 0x2e, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, 0x2f, + 0x20, 0x70, 0x2e, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x29, 0x29, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, + 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x78, 0x20, + 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, + 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x78, 0x29, 0x3b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x79, + 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x67, 0x6c, 0x5f, 0x46, 0x72, + 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x79, 0x29, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x52, + 0x6f, 0x77, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x75, 0x70, 0x6c, 0x6f, + 0x61, 0x64, 0x65, 0x64, 0x20, 0x6f, 0x6c, 0x64, 0x65, 0x73, 0x74, 0x2d, + 0x66, 0x69, 0x72, 0x73, 0x74, 0x3b, 0x20, 0x63, 0x61, 0x6e, 0x76, 0x61, + 0x73, 0x20, 0x72, 0x6f, 0x77, 0x20, 0x79, 0x20, 0x28, 0x66, 0x72, 0x6f, + 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x6f, 0x70, 0x29, 0x20, 0x73, + 0x68, 0x6f, 0x77, 0x73, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x28, 0x6e, 0x75, 0x6d, + 0x52, 0x6f, 0x77, 0x73, 0x20, 0x2d, 0x20, 0x31, 0x20, 0x2d, 0x20, 0x79, + 0x29, 0x2d, 0x74, 0x68, 0x20, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x20, 0x72, 0x6f, 0x77, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x72, 0x6f, 0x77, 0x20, 0x3d, 0x20, + 0x69, 0x6e, 0x74, 0x28, 0x70, 0x2e, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, + 0x73, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x20, 0x2d, 0x20, 0x79, 0x3b, 0x0a, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, + 0x49, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x6f, 0x6c, 0x61, 0x74, 0x65, 0x20, + 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x20, 0x61, 0x64, 0x6a, 0x61, + 0x63, 0x65, 0x6e, 0x74, 0x20, 0x6d, 0x61, 0x67, 0x6e, 0x69, 0x74, 0x75, + 0x64, 0x65, 0x20, 0x62, 0x69, 0x6e, 0x73, 0x20, 0x73, 0x6f, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, + 0x20, 0x61, 0x78, 0x69, 0x73, 0x20, 0x69, 0x73, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x73, 0x6d, 0x6f, 0x6f, + 0x74, 0x68, 0x20, 0x28, 0x61, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x61, + 0x73, 0x65, 0x64, 0x29, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x20, 0x74, 0x68, + 0x6f, 0x75, 0x67, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x65, 0x78, + 0x74, 0x75, 0x72, 0x65, 0x20, 0x69, 0x73, 0x20, 0x77, 0x69, 0x64, 0x65, + 0x72, 0x20, 0x74, 0x68, 0x61, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, + 0x69, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, + 0x2f, 0x20, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x62, + 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x28, 0x78, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x70, 0x2e, 0x62, 0x69, + 0x6e, 0x73, 0x20, 0x2f, 0x20, 0x70, 0x2e, 0x77, 0x69, 0x64, 0x74, 0x68, + 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, + 0x6e, 0x74, 0x20, 0x62, 0x69, 0x6e, 0x30, 0x20, 0x3d, 0x20, 0x69, 0x6e, + 0x74, 0x28, 0x62, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x29, 0x3b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x62, + 0x69, 0x6e, 0x31, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x6e, 0x28, 0x62, 0x69, + 0x6e, 0x30, 0x20, 0x2b, 0x20, 0x31, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x28, + 0x70, 0x2e, 0x62, 0x69, 0x6e, 0x73, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x29, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x20, 0x66, 0x72, 0x61, 0x63, 0x42, 0x69, 0x6e, 0x20, + 0x3d, 0x20, 0x62, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x20, 0x2d, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x62, 0x69, 0x6e, 0x30, 0x29, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x20, 0x6d, 0x61, 0x67, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x78, 0x28, + 0x66, 0x65, 0x74, 0x63, 0x68, 0x4d, 0x61, 0x67, 0x28, 0x72, 0x6f, 0x77, + 0x20, 0x2a, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x70, 0x2e, 0x62, 0x69, 0x6e, + 0x73, 0x29, 0x20, 0x2b, 0x20, 0x62, 0x69, 0x6e, 0x30, 0x29, 0x2c, 0x20, + 0x66, 0x65, 0x74, 0x63, 0x68, 0x4d, 0x61, 0x67, 0x28, 0x72, 0x6f, 0x77, + 0x20, 0x2a, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x70, 0x2e, 0x62, 0x69, 0x6e, + 0x73, 0x29, 0x20, 0x2b, 0x20, 0x62, 0x69, 0x6e, 0x31, 0x29, 0x2c, 0x20, + 0x66, 0x72, 0x61, 0x63, 0x42, 0x69, 0x6e, 0x29, 0x3b, 0x0a, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x20, 0x74, 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x6d, + 0x61, 0x67, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, + 0x29, 0x20, 0x2a, 0x20, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x3b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x69, + 0x30, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x74, 0x29, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, + 0x69, 0x31, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x6e, 0x28, 0x69, 0x30, 0x20, + 0x2b, 0x20, 0x31, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x29, 0x3b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x20, 0x66, 0x72, 0x61, 0x63, 0x20, 0x3d, 0x20, 0x74, 0x20, 0x2d, 0x20, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x69, 0x30, 0x29, 0x3b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x20, + 0x63, 0x30, 0x20, 0x3d, 0x20, 0x6c, 0x75, 0x74, 0x2e, 0x6c, 0x75, 0x74, + 0x5b, 0x69, 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x5d, 0x5b, 0x69, 0x30, + 0x20, 0x26, 0x20, 0x33, 0x5d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x20, 0x63, 0x31, 0x20, 0x3d, + 0x20, 0x6c, 0x75, 0x74, 0x2e, 0x6c, 0x75, 0x74, 0x5b, 0x69, 0x31, 0x20, + 0x3e, 0x3e, 0x20, 0x32, 0x5d, 0x5b, 0x69, 0x31, 0x20, 0x26, 0x20, 0x33, + 0x5d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, + 0x65, 0x63, 0x34, 0x20, 0x63, 0x6f, 0x6c, 0x30, 0x20, 0x3d, 0x20, 0x76, + 0x65, 0x63, 0x34, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, + 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x31, 0x36, 0x29, 0x20, 0x26, 0x20, 0x32, + 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, + 0x28, 0x63, 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x38, 0x29, 0x20, 0x26, 0x20, + 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x28, 0x63, 0x30, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x30, 0x20, 0x3e, + 0x3e, 0x20, 0x32, 0x34, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, + 0x29, 0x29, 0x20, 0x2f, 0x20, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, + 0x20, 0x63, 0x6f, 0x6c, 0x31, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, + 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x31, 0x20, 0x3e, + 0x3e, 0x20, 0x31, 0x36, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, + 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x31, + 0x20, 0x3e, 0x3e, 0x20, 0x38, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, + 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x63, 0x31, + 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x31, 0x20, 0x3e, 0x3e, 0x20, 0x32, + 0x34, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x29, 0x20, + 0x2f, 0x20, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, + 0x6f, 0x72, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x78, 0x28, 0x63, 0x6f, 0x6c, + 0x30, 0x2c, 0x20, 0x63, 0x6f, 0x6c, 0x31, 0x2c, 0x20, 0x66, 0x72, 0x61, + 0x63, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x0a, + 0x52, 0x45, 0x46, 0x4c, 0x82, 0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x52, 0x6f, 0x77, 0x44, 0x61, 0x74, 0x61, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x67, 0x73, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x13, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x00, + 0x00, 0x00, 0x57, 0x61, 0x74, 0x65, 0x72, 0x66, 0x61, 0x6c, 0x6c, 0x50, + 0x61, 0x72, 0x61, 0x6d, 0x73, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, + 0x00, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x77, 0x69, 0x64, 0x74, 0x68, 0x04, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x00, 0x00, 0x00, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x08, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x62, 0x69, 0x6e, 0x73, 0x0c, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x70, 0x61, 0x64, 0x30, 0x10, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x70, 0x61, 0x64, 0x31, 0x14, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x70, 0x61, 0x64, 0x32, 0x18, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x70, 0x61, 0x64, 0x33, 0x1c, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x4c, 0x75, + 0x74, 0x44, 0x61, 0x74, 0x61, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x6c, 0x75, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0xb1, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, + 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x66, 0x72, 0x61, 0x67, 0x43, + 0x6f, 0x6c, 0x6f, 0x72, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x75, + 0x5f, 0x70, 0x72, 0x65, 0x76, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x75, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x46, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, + 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x27, 0x00, 0x00, 0x00, 0x5f, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, + 0x45, 0x44, 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x45, + 0x52, 0x5f, 0x46, 0x49, 0x58, 0x55, 0x50, 0x5f, 0x67, 0x6c, 0x5f, 0x46, + 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x43, 0x61, 0x70, + 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x28, 0x31, 0x29, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x56, 0x41, 0x52, 0x54, 0x21, 0x1d, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x9b, 0x08, + 0x00, 0x00, 0x23, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x20, 0x63, 0x6c, + 0x61, 0x6e, 0x67, 0x20, 0x64, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, + 0x69, 0x63, 0x20, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x20, 0x22, + 0x2d, 0x57, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x2d, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x22, 0x0a, 0x0a, 0x23, + 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x20, 0x3c, 0x6d, 0x65, 0x74, + 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x64, 0x6c, 0x69, 0x62, 0x3e, 0x0a, 0x23, + 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x20, 0x3c, 0x73, 0x69, 0x6d, + 0x64, 0x2f, 0x73, 0x69, 0x6d, 0x64, 0x2e, 0x68, 0x3e, 0x0a, 0x0a, 0x75, + 0x73, 0x69, 0x6e, 0x67, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x6c, 0x3b, 0x0a, 0x0a, 0x73, + 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x52, 0x6f, 0x77, 0x44, 0x61, 0x74, + 0x61, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x34, 0x20, 0x6d, 0x61, 0x67, 0x73, 0x5b, 0x35, 0x31, 0x32, 0x5d, + 0x3b, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, + 0x20, 0x57, 0x61, 0x74, 0x65, 0x72, 0x66, 0x61, 0x6c, 0x6c, 0x50, 0x61, + 0x72, 0x61, 0x6d, 0x73, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, + 0x77, 0x69, 0x64, 0x74, 0x68, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x62, + 0x69, 0x6e, 0x73, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x20, 0x70, 0x61, 0x64, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, 0x61, 0x64, 0x31, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, + 0x61, 0x64, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x20, 0x70, 0x61, 0x64, 0x33, 0x3b, 0x0a, 0x7d, 0x3b, 0x0a, + 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x4c, 0x75, 0x74, 0x44, + 0x61, 0x74, 0x61, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x75, 0x69, + 0x6e, 0x74, 0x34, 0x20, 0x6c, 0x75, 0x74, 0x5b, 0x36, 0x34, 0x5d, 0x3b, + 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, + 0x6d, 0x61, 0x69, 0x6e, 0x30, 0x5f, 0x6f, 0x75, 0x74, 0x0a, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x66, + 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x5b, 0x5b, 0x63, + 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x30, 0x29, 0x5d, 0x5d, 0x3b, 0x0a, 0x7d, + 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x20, 0x69, 0x6e, + 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x5f, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x65, 0x5f, 0x5f, 0x28, 0x28, 0x61, 0x6c, 0x77, 0x61, + 0x79, 0x73, 0x5f, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x29, 0x29, 0x0a, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x66, 0x65, 0x74, 0x63, 0x68, 0x4d, + 0x61, 0x67, 0x28, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x20, 0x63, 0x6f, + 0x6e, 0x73, 0x74, 0x20, 0x69, 0x6e, 0x74, 0x26, 0x20, 0x69, 0x64, 0x78, + 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x20, 0x52, + 0x6f, 0x77, 0x44, 0x61, 0x74, 0x61, 0x26, 0x20, 0x72, 0x6f, 0x77, 0x73, + 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, + 0x72, 0x6e, 0x20, 0x72, 0x6f, 0x77, 0x73, 0x2e, 0x6d, 0x61, 0x67, 0x73, + 0x5b, 0x69, 0x64, 0x78, 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x5d, 0x5b, 0x69, + 0x64, 0x78, 0x20, 0x26, 0x20, 0x33, 0x5d, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, + 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x6d, 0x61, 0x69, + 0x6e, 0x30, 0x5f, 0x6f, 0x75, 0x74, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x30, + 0x28, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x20, 0x57, 0x61, + 0x74, 0x65, 0x72, 0x66, 0x61, 0x6c, 0x6c, 0x50, 0x61, 0x72, 0x61, 0x6d, + 0x73, 0x26, 0x20, 0x70, 0x20, 0x5b, 0x5b, 0x62, 0x75, 0x66, 0x66, 0x65, + 0x72, 0x28, 0x32, 0x29, 0x5d, 0x5d, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x73, + 0x74, 0x61, 0x6e, 0x74, 0x20, 0x52, 0x6f, 0x77, 0x44, 0x61, 0x74, 0x61, + 0x26, 0x20, 0x72, 0x6f, 0x77, 0x73, 0x20, 0x5b, 0x5b, 0x62, 0x75, 0x66, + 0x66, 0x65, 0x72, 0x28, 0x33, 0x29, 0x5d, 0x5d, 0x2c, 0x20, 0x63, 0x6f, + 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x20, 0x4c, 0x75, 0x74, 0x44, 0x61, + 0x74, 0x61, 0x26, 0x20, 0x6c, 0x75, 0x74, 0x20, 0x5b, 0x5b, 0x62, 0x75, + 0x66, 0x66, 0x65, 0x72, 0x28, 0x34, 0x29, 0x5d, 0x5d, 0x2c, 0x20, 0x74, + 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x32, 0x64, 0x3c, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x3e, 0x20, 0x75, 0x5f, 0x70, 0x72, 0x65, 0x76, 0x20, 0x5b, + 0x5b, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x30, 0x29, 0x5d, + 0x5d, 0x2c, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x20, 0x75, + 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x20, 0x5b, 0x5b, 0x73, 0x61, 0x6d, 0x70, + 0x6c, 0x65, 0x72, 0x28, 0x31, 0x29, 0x5d, 0x5d, 0x2c, 0x20, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x34, 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, + 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x20, 0x5b, 0x5b, 0x70, 0x6f, 0x73, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x5d, 0x5d, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x30, 0x5f, 0x6f, 0x75, 0x74, 0x20, + 0x6f, 0x75, 0x74, 0x20, 0x3d, 0x20, 0x7b, 0x7d, 0x3b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x20, 0x75, 0x76, 0x20, + 0x3d, 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, + 0x72, 0x64, 0x2e, 0x78, 0x79, 0x20, 0x2f, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x32, 0x28, 0x70, 0x2e, 0x77, 0x69, 0x64, 0x74, 0x68, 0x2c, 0x20, + 0x70, 0x2e, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x29, 0x3b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, 0x76, 0x2e, 0x79, 0x20, + 0x3e, 0x3d, 0x20, 0x28, 0x70, 0x2e, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, + 0x73, 0x20, 0x2f, 0x20, 0x70, 0x2e, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x75, 0x74, 0x2e, 0x66, 0x72, 0x61, + 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20, 0x75, 0x5f, 0x70, + 0x72, 0x65, 0x76, 0x2e, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x28, 0x75, + 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x32, 0x28, 0x75, 0x76, 0x2e, 0x78, 0x2c, 0x20, 0x75, 0x76, 0x2e, 0x79, + 0x20, 0x2d, 0x20, 0x28, 0x70, 0x2e, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, + 0x73, 0x20, 0x2f, 0x20, 0x70, 0x2e, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x29, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, + 0x74, 0x20, 0x78, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x67, 0x6c, + 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x78, + 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, + 0x6e, 0x74, 0x20, 0x79, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x67, + 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, + 0x79, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x69, 0x6e, 0x74, 0x20, 0x72, 0x6f, 0x77, 0x20, 0x3d, 0x20, 0x28, 0x69, + 0x6e, 0x74, 0x28, 0x70, 0x2e, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, + 0x29, 0x20, 0x2d, 0x20, 0x31, 0x29, 0x20, 0x2d, 0x20, 0x79, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x20, 0x62, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x20, 0x3d, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x78, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x70, + 0x2e, 0x62, 0x69, 0x6e, 0x73, 0x20, 0x2f, 0x20, 0x70, 0x2e, 0x77, 0x69, + 0x64, 0x74, 0x68, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x62, 0x69, 0x6e, 0x30, 0x20, 0x3d, + 0x20, 0x69, 0x6e, 0x74, 0x28, 0x62, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x29, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, + 0x74, 0x20, 0x62, 0x69, 0x6e, 0x31, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x6e, + 0x28, 0x28, 0x62, 0x69, 0x6e, 0x30, 0x20, 0x2b, 0x20, 0x31, 0x29, 0x2c, + 0x20, 0x28, 0x69, 0x6e, 0x74, 0x28, 0x70, 0x2e, 0x62, 0x69, 0x6e, 0x73, + 0x29, 0x20, 0x2d, 0x20, 0x31, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x66, + 0x72, 0x61, 0x63, 0x42, 0x69, 0x6e, 0x20, 0x3d, 0x20, 0x62, 0x69, 0x6e, + 0x50, 0x6f, 0x73, 0x20, 0x2d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, + 0x62, 0x69, 0x6e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x70, 0x61, 0x72, 0x61, 0x6d, + 0x20, 0x3d, 0x20, 0x28, 0x72, 0x6f, 0x77, 0x20, 0x2a, 0x20, 0x69, 0x6e, + 0x74, 0x28, 0x70, 0x2e, 0x62, 0x69, 0x6e, 0x73, 0x29, 0x29, 0x20, 0x2b, + 0x20, 0x62, 0x69, 0x6e, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x70, 0x61, 0x72, 0x61, 0x6d, + 0x5f, 0x31, 0x20, 0x3d, 0x20, 0x28, 0x72, 0x6f, 0x77, 0x20, 0x2a, 0x20, + 0x69, 0x6e, 0x74, 0x28, 0x70, 0x2e, 0x62, 0x69, 0x6e, 0x73, 0x29, 0x29, + 0x20, 0x2b, 0x20, 0x62, 0x69, 0x6e, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x6d, + 0x61, 0x67, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x78, 0x28, 0x66, 0x65, 0x74, + 0x63, 0x68, 0x4d, 0x61, 0x67, 0x28, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x2c, + 0x20, 0x72, 0x6f, 0x77, 0x73, 0x29, 0x2c, 0x20, 0x66, 0x65, 0x74, 0x63, + 0x68, 0x4d, 0x61, 0x67, 0x28, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x5f, 0x31, + 0x2c, 0x20, 0x72, 0x6f, 0x77, 0x73, 0x29, 0x2c, 0x20, 0x66, 0x72, 0x61, + 0x63, 0x42, 0x69, 0x6e, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x74, 0x20, 0x3d, + 0x20, 0x66, 0x61, 0x73, 0x74, 0x3a, 0x3a, 0x63, 0x6c, 0x61, 0x6d, 0x70, + 0x28, 0x6d, 0x61, 0x67, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, + 0x2e, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, + 0x20, 0x69, 0x30, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x74, 0x29, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, + 0x74, 0x20, 0x69, 0x31, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x6e, 0x28, 0x28, + 0x69, 0x30, 0x20, 0x2b, 0x20, 0x31, 0x29, 0x2c, 0x20, 0x32, 0x35, 0x35, + 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x66, 0x72, 0x61, 0x63, 0x20, 0x3d, 0x20, + 0x74, 0x20, 0x2d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x69, 0x30, + 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x75, + 0x69, 0x6e, 0x74, 0x20, 0x63, 0x30, 0x20, 0x3d, 0x20, 0x6c, 0x75, 0x74, + 0x2e, 0x6c, 0x75, 0x74, 0x5b, 0x69, 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x32, + 0x5d, 0x5b, 0x69, 0x30, 0x20, 0x26, 0x20, 0x33, 0x5d, 0x3b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x20, + 0x63, 0x31, 0x20, 0x3d, 0x20, 0x6c, 0x75, 0x74, 0x2e, 0x6c, 0x75, 0x74, + 0x5b, 0x69, 0x31, 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x5d, 0x5b, 0x69, 0x31, + 0x20, 0x26, 0x20, 0x33, 0x5d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x63, 0x6f, + 0x6c, 0x30, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x28, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x30, 0x20, 0x3e, 0x3e, + 0x20, 0x75, 0x69, 0x6e, 0x74, 0x28, 0x31, 0x36, 0x29, 0x29, 0x20, 0x26, + 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x28, 0x28, 0x63, 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x75, 0x69, 0x6e, + 0x74, 0x28, 0x38, 0x29, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, + 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x63, 0x30, 0x20, + 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x28, 0x28, 0x63, 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x75, 0x69, + 0x6e, 0x74, 0x28, 0x32, 0x34, 0x29, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, + 0x35, 0x75, 0x29, 0x29, 0x20, 0x2f, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x34, 0x28, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, + 0x20, 0x63, 0x6f, 0x6c, 0x31, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x34, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x31, + 0x20, 0x3e, 0x3e, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x28, 0x31, 0x36, 0x29, + 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x31, 0x20, 0x3e, 0x3e, 0x20, + 0x75, 0x69, 0x6e, 0x74, 0x28, 0x38, 0x29, 0x29, 0x20, 0x26, 0x20, 0x32, + 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, + 0x63, 0x31, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x31, 0x20, 0x3e, 0x3e, + 0x20, 0x75, 0x69, 0x6e, 0x74, 0x28, 0x32, 0x34, 0x29, 0x29, 0x20, 0x26, + 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x29, 0x20, 0x2f, 0x20, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x34, 0x28, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x29, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x75, 0x74, + 0x2e, 0x66, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d, + 0x20, 0x6d, 0x69, 0x78, 0x28, 0x63, 0x6f, 0x6c, 0x30, 0x2c, 0x20, 0x63, + 0x6f, 0x6c, 0x31, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x28, + 0x66, 0x72, 0x61, 0x63, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, + 0x20, 0x6f, 0x75, 0x74, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x49, 0x53, 0x52, + 0x43, 0xe4, 0x0e, 0x00, 0x00, 0x2f, 0x2a, 0x0a, 0x20, 0x20, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x54, 0x68, 0x69, + 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x69, 0x73, 0x20, 0x70, 0x61, + 0x72, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x59, 0x55, + 0x50, 0x20, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x2e, 0x0a, 0x20, + 0x20, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, + 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x32, 0x36, 0x20, 0x2d, 0x20, 0x6b, + 0x75, 0x6e, 0x69, 0x74, 0x6f, 0x6b, 0x69, 0x40, 0x67, 0x6d, 0x61, 0x69, + 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x59, 0x55, + 0x50, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x6f, 0x70, 0x65, 0x6e, + 0x20, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x6c, 0x69, 0x62, 0x72, + 0x61, 0x72, 0x79, 0x20, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, + 0x74, 0x6f, 0x20, 0x6f, 0x70, 0x65, 0x6e, 0x2d, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x20, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x69, 0x6e, 0x67, + 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x54, 0x68, 0x65, 0x20, 0x63, 0x6f, + 0x64, 0x65, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x20, + 0x69, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, + 0x20, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, + 0x20, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, + 0x65, 0x72, 0x6d, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x49, 0x53, 0x43, 0x20, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a, + 0x20, 0x20, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, + 0x77, 0x2e, 0x69, 0x73, 0x63, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x64, 0x6f, + 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x2f, 0x73, 0x6f, 0x66, 0x74, + 0x77, 0x61, 0x72, 0x65, 0x2d, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, + 0x2d, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2f, 0x69, 0x73, 0x63, 0x2d, + 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x2e, 0x20, 0x50, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x74, + 0x6f, 0x20, 0x75, 0x73, 0x65, 0x2c, 0x20, 0x63, 0x6f, 0x70, 0x79, 0x2c, + 0x20, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x2c, 0x20, 0x61, 0x6e, 0x64, + 0x2f, 0x6f, 0x72, 0x20, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x73, 0x6f, 0x66, 0x74, + 0x77, 0x61, 0x72, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x6e, 0x79, + 0x20, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x20, 0x77, 0x69, 0x74, + 0x68, 0x20, 0x6f, 0x72, 0x0a, 0x20, 0x20, 0x20, 0x77, 0x69, 0x74, 0x68, + 0x6f, 0x75, 0x74, 0x20, 0x66, 0x65, 0x65, 0x20, 0x69, 0x73, 0x20, 0x68, + 0x65, 0x72, 0x65, 0x62, 0x79, 0x20, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, + 0x64, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x74, + 0x68, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x62, 0x6f, 0x76, + 0x65, 0x20, 0x63, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, + 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x0a, 0x20, + 0x20, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, + 0x20, 0x61, 0x70, 0x70, 0x65, 0x61, 0x72, 0x20, 0x69, 0x6e, 0x20, 0x61, + 0x6c, 0x6c, 0x20, 0x63, 0x6f, 0x70, 0x69, 0x65, 0x73, 0x2e, 0x0a, 0x0a, + 0x20, 0x20, 0x20, 0x59, 0x55, 0x50, 0x20, 0x49, 0x53, 0x20, 0x50, 0x52, + 0x4f, 0x56, 0x49, 0x44, 0x45, 0x44, 0x20, 0x22, 0x41, 0x53, 0x20, 0x49, + 0x53, 0x22, 0x20, 0x57, 0x49, 0x54, 0x48, 0x4f, 0x55, 0x54, 0x20, 0x41, + 0x4e, 0x59, 0x20, 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x59, 0x2c, + 0x20, 0x41, 0x4e, 0x44, 0x20, 0x41, 0x4c, 0x4c, 0x20, 0x57, 0x41, 0x52, + 0x52, 0x41, 0x4e, 0x54, 0x49, 0x45, 0x53, 0x2c, 0x20, 0x57, 0x48, 0x45, + 0x54, 0x48, 0x45, 0x52, 0x0a, 0x20, 0x20, 0x20, 0x45, 0x58, 0x50, 0x52, + 0x45, 0x53, 0x53, 0x45, 0x44, 0x20, 0x4f, 0x52, 0x20, 0x49, 0x4d, 0x50, + 0x4c, 0x49, 0x45, 0x44, 0x2c, 0x20, 0x49, 0x4e, 0x43, 0x4c, 0x55, 0x44, + 0x49, 0x4e, 0x47, 0x20, 0x4d, 0x45, 0x52, 0x43, 0x48, 0x41, 0x4e, 0x54, + 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x20, 0x41, 0x4e, 0x44, 0x20, + 0x46, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x20, 0x46, 0x4f, 0x52, 0x20, + 0x50, 0x55, 0x52, 0x50, 0x4f, 0x53, 0x45, 0x2c, 0x20, 0x41, 0x52, 0x45, + 0x0a, 0x20, 0x20, 0x20, 0x44, 0x49, 0x53, 0x43, 0x4c, 0x41, 0x49, 0x4d, + 0x45, 0x44, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x0a, 0x2a, 0x2f, 0x0a, 0x0a, 0x23, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x20, 0x34, 0x35, 0x30, 0x0a, 0x0a, 0x2f, 0x2f, 0x20, 0x46, + 0x75, 0x6c, 0x6c, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x61, + 0x74, 0x65, 0x72, 0x66, 0x61, 0x6c, 0x6c, 0x20, 0x73, 0x68, 0x61, 0x64, + 0x65, 0x72, 0x2e, 0x20, 0x41, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, + 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, + 0x74, 0x72, 0x69, 0x61, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x73, 0x63, 0x72, + 0x6f, 0x6c, 0x6c, 0x73, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x2f, 0x2f, 0x20, + 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x20, 0x68, 0x69, 0x73, + 0x74, 0x6f, 0x72, 0x79, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, + 0x20, 0x64, 0x6f, 0x77, 0x6e, 0x20, 0x62, 0x79, 0x20, 0x6e, 0x75, 0x6d, + 0x52, 0x6f, 0x77, 0x73, 0x20, 0x72, 0x6f, 0x77, 0x73, 0x20, 0x61, 0x6e, + 0x64, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, 0x6e, 0x65, 0x77, + 0x20, 0x72, 0x6f, 0x77, 0x73, 0x0a, 0x2f, 0x2f, 0x20, 0x61, 0x74, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x74, 0x6f, 0x70, 0x2c, 0x20, 0x6d, 0x61, 0x70, + 0x70, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x61, 0x63, 0x68, 0x20, 0x62, 0x69, + 0x6e, 0x27, 0x73, 0x20, 0x72, 0x61, 0x77, 0x20, 0x6d, 0x61, 0x67, 0x6e, + 0x69, 0x74, 0x75, 0x64, 0x65, 0x20, 0x74, 0x68, 0x72, 0x6f, 0x75, 0x67, + 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20, + 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x20, 0x74, 0x61, 0x62, 0x6c, 0x65, + 0x0a, 0x2f, 0x2f, 0x20, 0x28, 0x61, 0x20, 0x6c, 0x65, 0x72, 0x70, 0x20, + 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x20, 0x61, 0x64, 0x6a, 0x61, + 0x63, 0x65, 0x6e, 0x74, 0x20, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, + 0x2c, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x53, + 0x70, 0x65, 0x63, 0x74, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x43, 0x6f, + 0x6c, 0x6f, 0x72, 0x4d, 0x61, 0x70, 0x3a, 0x3a, 0x6d, 0x61, 0x70, 0x29, + 0x20, 0x65, 0x6e, 0x74, 0x69, 0x72, 0x65, 0x6c, 0x79, 0x0a, 0x2f, 0x2f, + 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x50, 0x55, 0x2e, + 0x0a, 0x2f, 0x2f, 0x0a, 0x2f, 0x2f, 0x20, 0x42, 0x69, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x73, 0x3a, 0x0a, 0x2f, 0x2f, 0x20, 0x20, 0x20, 0x73, 0x65, + 0x74, 0x3d, 0x30, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x3d, + 0x30, 0x20, 0x3a, 0x20, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, + 0x20, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x74, 0x65, 0x78, + 0x74, 0x75, 0x72, 0x65, 0x20, 0x28, 0x55, 0x56, 0x2e, 0x79, 0x20, 0x3d, + 0x20, 0x30, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x6f, + 0x70, 0x20, 0x72, 0x6f, 0x77, 0x29, 0x0a, 0x2f, 0x2f, 0x20, 0x20, 0x20, + 0x73, 0x65, 0x74, 0x3d, 0x30, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x3d, 0x31, 0x20, 0x3a, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, + 0x72, 0x20, 0x28, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x63, 0x69, 0x74, 0x20, + 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x2c, 0x20, 0x6c, 0x69, 0x6b, + 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x61, 0x74, 0x74, 0x65, 0x20, + 0x70, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x29, 0x0a, 0x2f, 0x2f, + 0x20, 0x20, 0x20, 0x73, 0x65, 0x74, 0x3d, 0x30, 0x20, 0x62, 0x69, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x32, 0x20, 0x3a, 0x20, 0x57, 0x61, 0x74, + 0x65, 0x72, 0x66, 0x61, 0x6c, 0x6c, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, + 0x20, 0x55, 0x42, 0x4f, 0x0a, 0x2f, 0x2f, 0x20, 0x20, 0x20, 0x73, 0x65, + 0x74, 0x3d, 0x30, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x3d, + 0x33, 0x20, 0x3a, 0x20, 0x52, 0x6f, 0x77, 0x44, 0x61, 0x74, 0x61, 0x20, + 0x55, 0x42, 0x4f, 0x20, 0x28, 0x57, 0x20, 0x2a, 0x20, 0x6e, 0x75, 0x6d, + 0x52, 0x6f, 0x77, 0x73, 0x20, 0x72, 0x61, 0x77, 0x20, 0x6d, 0x61, 0x67, + 0x6e, 0x69, 0x74, 0x75, 0x64, 0x65, 0x73, 0x2c, 0x20, 0x6f, 0x6c, 0x64, + 0x65, 0x73, 0x74, 0x20, 0x72, 0x6f, 0x77, 0x20, 0x66, 0x69, 0x72, 0x73, + 0x74, 0x29, 0x0a, 0x2f, 0x2f, 0x20, 0x20, 0x20, 0x73, 0x65, 0x74, 0x3d, + 0x30, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x34, 0x20, + 0x3a, 0x20, 0x4c, 0x75, 0x74, 0x44, 0x61, 0x74, 0x61, 0x20, 0x55, 0x42, + 0x4f, 0x20, 0x28, 0x32, 0x35, 0x36, 0x20, 0x41, 0x52, 0x47, 0x42, 0x20, + 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, + 0x73, 0x29, 0x0a, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, 0x73, + 0x65, 0x74, 0x20, 0x3d, 0x20, 0x30, 0x2c, 0x20, 0x62, 0x69, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x20, 0x3d, 0x20, 0x30, 0x29, 0x20, 0x75, 0x6e, 0x69, + 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, + 0x32, 0x44, 0x20, 0x75, 0x5f, 0x70, 0x72, 0x65, 0x76, 0x3b, 0x0a, 0x6c, + 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, 0x73, 0x65, 0x74, 0x20, 0x3d, 0x20, + 0x30, 0x2c, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x3d, + 0x20, 0x31, 0x29, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, + 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x20, 0x20, 0x20, 0x75, 0x5f, + 0x73, 0x61, 0x6d, 0x70, 0x3b, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, + 0x28, 0x73, 0x65, 0x74, 0x20, 0x3d, 0x20, 0x30, 0x2c, 0x20, 0x62, 0x69, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x3d, 0x20, 0x32, 0x29, 0x20, 0x75, + 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x57, 0x61, 0x74, 0x65, 0x72, + 0x66, 0x61, 0x6c, 0x6c, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x0a, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x6e, + 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3b, + 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x77, 0x61, 0x74, 0x65, 0x72, 0x66, + 0x61, 0x6c, 0x6c, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x20, + 0x77, 0x69, 0x64, 0x74, 0x68, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x65, 0x78, + 0x65, 0x6c, 0x73, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x62, 0x69, 0x6e, 0x73, + 0x3b, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x6e, 0x75, 0x6d, 0x62, + 0x65, 0x72, 0x20, 0x6f, 0x66, 0x20, 0x6d, 0x61, 0x67, 0x6e, 0x69, 0x74, + 0x75, 0x64, 0x65, 0x20, 0x62, 0x69, 0x6e, 0x73, 0x20, 0x70, 0x65, 0x72, + 0x20, 0x72, 0x6f, 0x77, 0x20, 0x28, 0x3c, 0x3d, 0x20, 0x77, 0x69, 0x64, + 0x74, 0x68, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x20, 0x70, 0x61, 0x64, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, 0x61, 0x64, 0x31, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, 0x61, + 0x64, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x20, 0x70, 0x61, 0x64, 0x33, 0x3b, 0x0a, 0x7d, 0x20, 0x70, 0x3b, + 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, 0x73, 0x65, 0x74, 0x20, + 0x3d, 0x20, 0x30, 0x2c, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x20, 0x3d, 0x20, 0x33, 0x29, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, + 0x6d, 0x20, 0x52, 0x6f, 0x77, 0x44, 0x61, 0x74, 0x61, 0x0a, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x6d, 0x61, 0x67, + 0x73, 0x5b, 0x35, 0x31, 0x32, 0x5d, 0x3b, 0x20, 0x2f, 0x2f, 0x20, 0x32, + 0x30, 0x34, 0x38, 0x20, 0x72, 0x61, 0x77, 0x20, 0x6d, 0x61, 0x67, 0x6e, + 0x69, 0x74, 0x75, 0x64, 0x65, 0x73, 0x20, 0x70, 0x61, 0x63, 0x6b, 0x65, + 0x64, 0x20, 0x61, 0x73, 0x20, 0x76, 0x65, 0x63, 0x34, 0x73, 0x20, 0x28, + 0x73, 0x74, 0x64, 0x31, 0x34, 0x30, 0x20, 0x74, 0x69, 0x67, 0x68, 0x74, + 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x57, 0x20, 0x69, 0x73, 0x20, 0x61, + 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x6f, 0x66, + 0x20, 0x34, 0x29, 0x0a, 0x7d, 0x20, 0x72, 0x6f, 0x77, 0x73, 0x3b, 0x0a, + 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, 0x73, 0x65, 0x74, 0x20, 0x3d, + 0x20, 0x30, 0x2c, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, + 0x3d, 0x20, 0x34, 0x29, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, + 0x20, 0x4c, 0x75, 0x74, 0x44, 0x61, 0x74, 0x61, 0x0a, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x75, 0x76, 0x65, 0x63, 0x34, 0x20, 0x6c, 0x75, 0x74, + 0x5b, 0x36, 0x34, 0x5d, 0x3b, 0x20, 0x2f, 0x2f, 0x20, 0x32, 0x35, 0x36, + 0x20, 0x41, 0x52, 0x47, 0x42, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x73, + 0x20, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x20, 0x61, 0x73, 0x20, 0x75, + 0x76, 0x65, 0x63, 0x34, 0x73, 0x0a, 0x7d, 0x20, 0x6c, 0x75, 0x74, 0x3b, + 0x0a, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, 0x6c, 0x6f, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x30, 0x29, 0x20, 0x6f, + 0x75, 0x74, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x66, 0x72, 0x61, 0x67, + 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x0a, 0x0a, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x20, 0x66, 0x65, 0x74, 0x63, 0x68, 0x4d, 0x61, 0x67, 0x28, 0x69, + 0x6e, 0x74, 0x20, 0x69, 0x64, 0x78, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x72, 0x6f, 0x77, + 0x73, 0x2e, 0x6d, 0x61, 0x67, 0x73, 0x5b, 0x69, 0x64, 0x78, 0x20, 0x3e, + 0x3e, 0x20, 0x32, 0x5d, 0x5b, 0x69, 0x64, 0x78, 0x20, 0x26, 0x20, 0x33, + 0x5d, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x6d, + 0x61, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x76, 0x65, 0x63, 0x32, 0x20, 0x75, 0x76, 0x20, 0x3d, 0x20, 0x67, 0x6c, + 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x78, + 0x79, 0x20, 0x2f, 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, 0x70, 0x2e, 0x77, + 0x69, 0x64, 0x74, 0x68, 0x2c, 0x20, 0x70, 0x2e, 0x68, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, + 0x28, 0x75, 0x76, 0x2e, 0x79, 0x20, 0x3e, 0x3d, 0x20, 0x70, 0x2e, 0x6e, + 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, 0x2f, 0x20, 0x70, 0x2e, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x72, 0x61, + 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20, 0x74, 0x65, 0x78, + 0x74, 0x75, 0x72, 0x65, 0x28, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, + 0x32, 0x44, 0x28, 0x75, 0x5f, 0x70, 0x72, 0x65, 0x76, 0x2c, 0x20, 0x75, + 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x29, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x32, + 0x28, 0x75, 0x76, 0x2e, 0x78, 0x2c, 0x20, 0x75, 0x76, 0x2e, 0x79, 0x20, + 0x2d, 0x20, 0x70, 0x2e, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, + 0x2f, 0x20, 0x70, 0x2e, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x29, 0x29, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x78, + 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x67, 0x6c, 0x5f, 0x46, 0x72, + 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x78, 0x29, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, + 0x79, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x67, 0x6c, 0x5f, 0x46, + 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x79, 0x29, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, + 0x52, 0x6f, 0x77, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x75, 0x70, 0x6c, + 0x6f, 0x61, 0x64, 0x65, 0x64, 0x20, 0x6f, 0x6c, 0x64, 0x65, 0x73, 0x74, + 0x2d, 0x66, 0x69, 0x72, 0x73, 0x74, 0x3b, 0x20, 0x63, 0x61, 0x6e, 0x76, + 0x61, 0x73, 0x20, 0x72, 0x6f, 0x77, 0x20, 0x79, 0x20, 0x28, 0x66, 0x72, + 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x6f, 0x70, 0x29, 0x20, + 0x73, 0x68, 0x6f, 0x77, 0x73, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x28, 0x6e, 0x75, + 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, 0x2d, 0x20, 0x31, 0x20, 0x2d, 0x20, + 0x79, 0x29, 0x2d, 0x74, 0x68, 0x20, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x20, 0x72, 0x6f, 0x77, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x72, 0x6f, 0x77, 0x20, 0x3d, + 0x20, 0x69, 0x6e, 0x74, 0x28, 0x70, 0x2e, 0x6e, 0x75, 0x6d, 0x52, 0x6f, + 0x77, 0x73, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x20, 0x2d, 0x20, 0x79, 0x3b, + 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, + 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x6f, 0x6c, 0x61, 0x74, 0x65, + 0x20, 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x20, 0x61, 0x64, 0x6a, + 0x61, 0x63, 0x65, 0x6e, 0x74, 0x20, 0x6d, 0x61, 0x67, 0x6e, 0x69, 0x74, + 0x75, 0x64, 0x65, 0x20, 0x62, 0x69, 0x6e, 0x73, 0x20, 0x73, 0x6f, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, + 0x79, 0x20, 0x61, 0x78, 0x69, 0x73, 0x20, 0x69, 0x73, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x73, 0x6d, 0x6f, + 0x6f, 0x74, 0x68, 0x20, 0x28, 0x61, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x69, + 0x61, 0x73, 0x65, 0x64, 0x29, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x20, 0x74, + 0x68, 0x6f, 0x75, 0x67, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x65, + 0x78, 0x74, 0x75, 0x72, 0x65, 0x20, 0x69, 0x73, 0x20, 0x77, 0x69, 0x64, + 0x65, 0x72, 0x20, 0x74, 0x68, 0x61, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x62, 0x69, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x2f, 0x2f, 0x20, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, + 0x62, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x28, 0x78, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x70, 0x2e, 0x62, + 0x69, 0x6e, 0x73, 0x20, 0x2f, 0x20, 0x70, 0x2e, 0x77, 0x69, 0x64, 0x74, + 0x68, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x69, 0x6e, 0x74, 0x20, 0x62, 0x69, 0x6e, 0x30, 0x20, 0x3d, 0x20, 0x69, + 0x6e, 0x74, 0x28, 0x62, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x29, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, + 0x62, 0x69, 0x6e, 0x31, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x6e, 0x28, 0x62, + 0x69, 0x6e, 0x30, 0x20, 0x2b, 0x20, 0x31, 0x2c, 0x20, 0x69, 0x6e, 0x74, + 0x28, 0x70, 0x2e, 0x62, 0x69, 0x6e, 0x73, 0x29, 0x20, 0x2d, 0x20, 0x31, + 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x66, 0x72, 0x61, 0x63, 0x42, 0x69, 0x6e, + 0x20, 0x3d, 0x20, 0x62, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x20, 0x2d, 0x20, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x62, 0x69, 0x6e, 0x30, 0x29, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x20, 0x6d, 0x61, 0x67, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x78, + 0x28, 0x66, 0x65, 0x74, 0x63, 0x68, 0x4d, 0x61, 0x67, 0x28, 0x72, 0x6f, + 0x77, 0x20, 0x2a, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x70, 0x2e, 0x62, 0x69, + 0x6e, 0x73, 0x29, 0x20, 0x2b, 0x20, 0x62, 0x69, 0x6e, 0x30, 0x29, 0x2c, + 0x20, 0x66, 0x65, 0x74, 0x63, 0x68, 0x4d, 0x61, 0x67, 0x28, 0x72, 0x6f, + 0x77, 0x20, 0x2a, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x70, 0x2e, 0x62, 0x69, + 0x6e, 0x73, 0x29, 0x20, 0x2b, 0x20, 0x62, 0x69, 0x6e, 0x31, 0x29, 0x2c, + 0x20, 0x66, 0x72, 0x61, 0x63, 0x42, 0x69, 0x6e, 0x29, 0x3b, 0x0a, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x20, 0x74, 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, + 0x6d, 0x61, 0x67, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, + 0x30, 0x29, 0x20, 0x2a, 0x20, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, + 0x69, 0x30, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x74, 0x29, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, + 0x20, 0x69, 0x31, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x6e, 0x28, 0x69, 0x30, + 0x20, 0x2b, 0x20, 0x31, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x29, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x20, 0x66, 0x72, 0x61, 0x63, 0x20, 0x3d, 0x20, 0x74, 0x20, 0x2d, + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x69, 0x30, 0x29, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x75, 0x69, 0x6e, 0x74, + 0x20, 0x63, 0x30, 0x20, 0x3d, 0x20, 0x6c, 0x75, 0x74, 0x2e, 0x6c, 0x75, + 0x74, 0x5b, 0x69, 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x5d, 0x5b, 0x69, + 0x30, 0x20, 0x26, 0x20, 0x33, 0x5d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x20, 0x63, 0x31, 0x20, + 0x3d, 0x20, 0x6c, 0x75, 0x74, 0x2e, 0x6c, 0x75, 0x74, 0x5b, 0x69, 0x31, + 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x5d, 0x5b, 0x69, 0x31, 0x20, 0x26, 0x20, + 0x33, 0x5d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x76, 0x65, 0x63, 0x34, 0x20, 0x63, 0x6f, 0x6c, 0x30, 0x20, 0x3d, 0x20, + 0x76, 0x65, 0x63, 0x34, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, + 0x63, 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x31, 0x36, 0x29, 0x20, 0x26, 0x20, + 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x28, 0x28, 0x63, 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x38, 0x29, 0x20, 0x26, + 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x28, 0x63, 0x30, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, + 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x30, 0x20, + 0x3e, 0x3e, 0x20, 0x32, 0x34, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, + 0x75, 0x29, 0x29, 0x20, 0x2f, 0x20, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, + 0x34, 0x20, 0x63, 0x6f, 0x6c, 0x31, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, + 0x34, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x31, 0x20, + 0x3e, 0x3e, 0x20, 0x31, 0x36, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, + 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, + 0x31, 0x20, 0x3e, 0x3e, 0x20, 0x38, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, + 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x63, + 0x31, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x31, 0x20, 0x3e, 0x3e, 0x20, + 0x32, 0x34, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x29, + 0x20, 0x2f, 0x20, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x3b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x72, 0x61, 0x67, 0x43, 0x6f, + 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x78, 0x28, 0x63, 0x6f, + 0x6c, 0x30, 0x2c, 0x20, 0x63, 0x6f, 0x6c, 0x31, 0x2c, 0x20, 0x66, 0x72, + 0x61, 0x63, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x7d, + 0x0a, 0x52, 0x45, 0x46, 0x4c, 0x82, 0x05, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, + 0x6e, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x07, 0x00, 0x00, 0x00, 0x52, 0x6f, 0x77, 0x44, 0x61, 0x74, 0x61, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x67, 0x73, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x13, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x0f, + 0x00, 0x00, 0x00, 0x57, 0x61, 0x74, 0x65, 0x72, 0x66, 0x61, 0x6c, 0x6c, + 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, + 0x00, 0x00, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x05, 0x00, 0x00, 0x00, 0x77, 0x69, 0x64, 0x74, 0x68, 0x04, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x62, 0x69, 0x6e, 0x73, + 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x70, 0x61, 0x64, 0x30, + 0x10, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x70, 0x61, 0x64, 0x31, + 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x70, 0x61, 0x64, 0x32, + 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x70, 0x61, 0x64, 0x33, + 0x1c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x4c, + 0x75, 0x74, 0x44, 0x61, 0x74, 0x61, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x6c, 0x75, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0xb1, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x66, 0x72, 0x61, 0x67, + 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x75, 0x5f, 0x70, 0x72, 0x65, 0x76, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, + 0x00, 0x75, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x0b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, + 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x5f, 0x52, 0x45, 0x53, 0x45, 0x52, + 0x56, 0x45, 0x44, 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, + 0x45, 0x52, 0x5f, 0x46, 0x49, 0x58, 0x55, 0x50, 0x5f, 0x67, 0x6c, 0x5f, + 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x43, 0x61, + 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x28, 0x31, 0x29, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x56, 0x41, 0x52, 0x54, 0x6d, 0x1e, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, + 0x01, 0x0a, 0x00, 0x00, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x57, + 0x61, 0x74, 0x65, 0x72, 0x66, 0x61, 0x6c, 0x6c, 0x50, 0x61, 0x72, 0x61, + 0x6d, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6e, 0x75, 0x6d, + 0x52, 0x6f, 0x77, 0x73, 0x3a, 0x20, 0x66, 0x33, 0x32, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x66, 0x33, + 0x32, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x3a, 0x20, 0x66, 0x33, 0x32, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x62, 0x69, 0x6e, 0x73, 0x3a, 0x20, 0x66, 0x33, 0x32, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x30, 0x3a, 0x20, 0x66, 0x33, 0x32, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x31, 0x3a, 0x20, + 0x66, 0x33, 0x32, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, + 0x32, 0x3a, 0x20, 0x66, 0x33, 0x32, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x70, 0x61, 0x64, 0x33, 0x3a, 0x20, 0x66, 0x33, 0x32, 0x2c, 0x0a, 0x7d, + 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x52, 0x6f, 0x77, + 0x44, 0x61, 0x74, 0x61, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, + 0x61, 0x67, 0x73, 0x3a, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x76, + 0x65, 0x63, 0x34, 0x3c, 0x66, 0x33, 0x32, 0x3e, 0x2c, 0x20, 0x35, 0x31, + 0x32, 0x3e, 0x2c, 0x0a, 0x7d, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, + 0x74, 0x20, 0x4c, 0x75, 0x74, 0x44, 0x61, 0x74, 0x61, 0x20, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x6c, 0x75, 0x74, 0x3a, 0x20, 0x61, 0x72, 0x72, + 0x61, 0x79, 0x3c, 0x76, 0x65, 0x63, 0x34, 0x3c, 0x75, 0x33, 0x32, 0x3e, + 0x2c, 0x20, 0x36, 0x34, 0x3e, 0x2c, 0x0a, 0x7d, 0x0a, 0x0a, 0x40, 0x67, + 0x72, 0x6f, 0x75, 0x70, 0x28, 0x30, 0x29, 0x20, 0x40, 0x62, 0x69, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x28, 0x30, 0x29, 0x20, 0x76, 0x61, 0x72, 0x20, + 0x75, 0x5f, 0x70, 0x72, 0x65, 0x76, 0x3a, 0x20, 0x74, 0x65, 0x78, 0x74, + 0x75, 0x72, 0x65, 0x5f, 0x32, 0x64, 0x3c, 0x66, 0x33, 0x32, 0x3e, 0x3b, + 0x0a, 0x40, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x28, 0x30, 0x29, 0x20, 0x40, + 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x28, 0x31, 0x29, 0x20, 0x76, + 0x61, 0x72, 0x20, 0x75, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x3a, 0x20, 0x73, + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x3b, 0x0a, 0x40, 0x67, 0x72, 0x6f, + 0x75, 0x70, 0x28, 0x30, 0x29, 0x20, 0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x28, 0x32, 0x29, 0x20, 0x76, 0x61, 0x72, 0x3c, 0x75, 0x6e, + 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x3e, 0x20, 0x70, 0x3a, 0x20, 0x57, 0x61, + 0x74, 0x65, 0x72, 0x66, 0x61, 0x6c, 0x6c, 0x50, 0x61, 0x72, 0x61, 0x6d, + 0x73, 0x3b, 0x0a, 0x40, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x28, 0x30, 0x29, + 0x20, 0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x28, 0x33, 0x29, + 0x20, 0x76, 0x61, 0x72, 0x3c, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, + 0x3e, 0x20, 0x72, 0x6f, 0x77, 0x73, 0x3a, 0x20, 0x52, 0x6f, 0x77, 0x44, + 0x61, 0x74, 0x61, 0x3b, 0x0a, 0x40, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x28, + 0x30, 0x29, 0x20, 0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x28, + 0x34, 0x29, 0x20, 0x76, 0x61, 0x72, 0x3c, 0x75, 0x6e, 0x69, 0x66, 0x6f, + 0x72, 0x6d, 0x3e, 0x20, 0x6c, 0x75, 0x74, 0x3a, 0x20, 0x4c, 0x75, 0x74, + 0x44, 0x61, 0x74, 0x61, 0x3b, 0x0a, 0x76, 0x61, 0x72, 0x3c, 0x70, 0x72, + 0x69, 0x76, 0x61, 0x74, 0x65, 0x3e, 0x20, 0x66, 0x72, 0x61, 0x67, 0x43, + 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x34, 0x3c, 0x66, + 0x33, 0x32, 0x3e, 0x3b, 0x0a, 0x76, 0x61, 0x72, 0x3c, 0x70, 0x72, 0x69, + 0x76, 0x61, 0x74, 0x65, 0x3e, 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, + 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x34, + 0x3c, 0x66, 0x33, 0x32, 0x3e, 0x3b, 0x0a, 0x76, 0x61, 0x72, 0x3c, 0x70, + 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x3e, 0x20, 0x67, 0x6c, 0x5f, 0x46, + 0x72, 0x6f, 0x6e, 0x74, 0x46, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, + 0x62, 0x6f, 0x6f, 0x6c, 0x3b, 0x0a, 0x66, 0x6e, 0x20, 0x66, 0x65, 0x74, + 0x63, 0x68, 0x4d, 0x61, 0x67, 0x28, 0x69, 0x64, 0x78, 0x3a, 0x20, 0x69, + 0x33, 0x32, 0x29, 0x20, 0x2d, 0x3e, 0x20, 0x66, 0x33, 0x32, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, + 0x72, 0x6f, 0x77, 0x73, 0x2e, 0x6d, 0x61, 0x67, 0x73, 0x5b, 0x28, 0x69, + 0x64, 0x78, 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x29, 0x5d, 0x5b, 0x28, 0x69, + 0x64, 0x78, 0x20, 0x26, 0x20, 0x33, 0x29, 0x5d, 0x3b, 0x0a, 0x7d, 0x0a, + 0x0a, 0x66, 0x6e, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x6e, 0x6e, + 0x65, 0x72, 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, + 0x61, 0x72, 0x20, 0x75, 0x76, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32, 0x3c, + 0x66, 0x33, 0x32, 0x3e, 0x20, 0x3d, 0x20, 0x28, 0x67, 0x6c, 0x5f, 0x46, + 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x78, 0x79, 0x20, + 0x2f, 0x20, 0x76, 0x65, 0x63, 0x32, 0x3c, 0x66, 0x33, 0x32, 0x3e, 0x28, + 0x70, 0x2e, 0x77, 0x69, 0x64, 0x74, 0x68, 0x2c, 0x20, 0x70, 0x2e, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x69, 0x66, 0x20, 0x28, 0x28, 0x75, 0x76, 0x2e, 0x79, 0x20, 0x3e, + 0x3d, 0x20, 0x28, 0x70, 0x2e, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, + 0x20, 0x2f, 0x20, 0x70, 0x2e, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x29, + 0x29, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x66, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, + 0x20, 0x3d, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x53, 0x61, + 0x6d, 0x70, 0x6c, 0x65, 0x28, 0x75, 0x5f, 0x70, 0x72, 0x65, 0x76, 0x2c, + 0x20, 0x75, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x2c, 0x20, 0x76, 0x65, 0x63, + 0x32, 0x3c, 0x66, 0x33, 0x32, 0x3e, 0x28, 0x75, 0x76, 0x2e, 0x78, 0x2c, + 0x20, 0x28, 0x75, 0x76, 0x2e, 0x79, 0x20, 0x2d, 0x20, 0x28, 0x70, 0x2e, + 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, 0x2f, 0x20, 0x70, 0x2e, + 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x29, 0x29, 0x29, 0x29, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x76, 0x61, 0x72, 0x20, 0x78, 0x3a, 0x20, 0x69, 0x33, 0x32, 0x20, + 0x3d, 0x20, 0x69, 0x33, 0x32, 0x28, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, + 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x78, 0x29, 0x3b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, + 0x61, 0x72, 0x20, 0x79, 0x3a, 0x20, 0x69, 0x33, 0x32, 0x20, 0x3d, 0x20, + 0x69, 0x33, 0x32, 0x28, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, + 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x79, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, + 0x20, 0x72, 0x6f, 0x77, 0x3a, 0x20, 0x69, 0x33, 0x32, 0x20, 0x3d, 0x20, + 0x28, 0x28, 0x69, 0x33, 0x32, 0x28, 0x70, 0x2e, 0x6e, 0x75, 0x6d, 0x52, + 0x6f, 0x77, 0x73, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x29, 0x20, 0x2d, 0x20, + 0x79, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x62, 0x69, 0x6e, 0x50, + 0x6f, 0x73, 0x3a, 0x20, 0x66, 0x33, 0x32, 0x20, 0x3d, 0x20, 0x28, 0x66, + 0x33, 0x32, 0x28, 0x78, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x28, 0x70, 0x2e, + 0x62, 0x69, 0x6e, 0x73, 0x20, 0x2f, 0x20, 0x70, 0x2e, 0x77, 0x69, 0x64, + 0x74, 0x68, 0x29, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x62, + 0x69, 0x6e, 0x30, 0x3a, 0x20, 0x69, 0x33, 0x32, 0x20, 0x3d, 0x20, 0x69, + 0x33, 0x32, 0x28, 0x62, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x29, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x76, 0x61, 0x72, 0x20, 0x62, 0x69, 0x6e, 0x31, 0x3a, 0x20, 0x69, 0x33, + 0x32, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x6e, 0x28, 0x28, 0x62, 0x69, 0x6e, + 0x30, 0x20, 0x2b, 0x20, 0x31, 0x29, 0x2c, 0x20, 0x28, 0x69, 0x33, 0x32, + 0x28, 0x70, 0x2e, 0x62, 0x69, 0x6e, 0x73, 0x29, 0x20, 0x2d, 0x20, 0x31, + 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x66, 0x72, 0x61, 0x63, + 0x42, 0x69, 0x6e, 0x3a, 0x20, 0x66, 0x33, 0x32, 0x20, 0x3d, 0x20, 0x28, + 0x62, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x20, 0x2d, 0x20, 0x66, 0x33, 0x32, + 0x28, 0x62, 0x69, 0x6e, 0x30, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, + 0x20, 0x6d, 0x61, 0x67, 0x3a, 0x20, 0x66, 0x33, 0x32, 0x20, 0x3d, 0x20, + 0x6d, 0x69, 0x78, 0x28, 0x66, 0x65, 0x74, 0x63, 0x68, 0x4d, 0x61, 0x67, + 0x28, 0x28, 0x28, 0x72, 0x6f, 0x77, 0x20, 0x2a, 0x20, 0x69, 0x33, 0x32, + 0x28, 0x70, 0x2e, 0x62, 0x69, 0x6e, 0x73, 0x29, 0x29, 0x20, 0x2b, 0x20, + 0x62, 0x69, 0x6e, 0x30, 0x29, 0x29, 0x2c, 0x20, 0x66, 0x65, 0x74, 0x63, + 0x68, 0x4d, 0x61, 0x67, 0x28, 0x28, 0x28, 0x72, 0x6f, 0x77, 0x20, 0x2a, + 0x20, 0x69, 0x33, 0x32, 0x28, 0x70, 0x2e, 0x62, 0x69, 0x6e, 0x73, 0x29, + 0x29, 0x20, 0x2b, 0x20, 0x62, 0x69, 0x6e, 0x31, 0x29, 0x29, 0x2c, 0x20, + 0x66, 0x72, 0x61, 0x63, 0x42, 0x69, 0x6e, 0x29, 0x3b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, + 0x72, 0x20, 0x74, 0x3a, 0x20, 0x66, 0x33, 0x32, 0x20, 0x3d, 0x20, 0x28, + 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x6d, 0x61, 0x67, 0x2c, 0x20, 0x30, + 0x2e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x32, 0x35, 0x35, + 0x2e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, + 0x72, 0x20, 0x69, 0x30, 0x3a, 0x20, 0x69, 0x33, 0x32, 0x20, 0x3d, 0x20, + 0x69, 0x33, 0x32, 0x28, 0x74, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, + 0x69, 0x31, 0x3a, 0x20, 0x69, 0x33, 0x32, 0x20, 0x3d, 0x20, 0x6d, 0x69, + 0x6e, 0x28, 0x28, 0x69, 0x30, 0x20, 0x2b, 0x20, 0x31, 0x29, 0x2c, 0x20, + 0x32, 0x35, 0x35, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x66, 0x72, + 0x61, 0x63, 0x3a, 0x20, 0x66, 0x33, 0x32, 0x20, 0x3d, 0x20, 0x28, 0x74, + 0x20, 0x2d, 0x20, 0x66, 0x33, 0x32, 0x28, 0x69, 0x30, 0x29, 0x29, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x76, 0x61, 0x72, 0x20, 0x63, 0x30, 0x3a, 0x20, 0x75, 0x33, 0x32, + 0x20, 0x3d, 0x20, 0x6c, 0x75, 0x74, 0x2e, 0x6c, 0x75, 0x74, 0x5b, 0x28, + 0x69, 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x29, 0x5d, 0x5b, 0x28, 0x69, + 0x30, 0x20, 0x26, 0x20, 0x33, 0x29, 0x5d, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, + 0x20, 0x63, 0x31, 0x3a, 0x20, 0x75, 0x33, 0x32, 0x20, 0x3d, 0x20, 0x6c, + 0x75, 0x74, 0x2e, 0x6c, 0x75, 0x74, 0x5b, 0x28, 0x69, 0x31, 0x20, 0x3e, + 0x3e, 0x20, 0x32, 0x29, 0x5d, 0x5b, 0x28, 0x69, 0x31, 0x20, 0x26, 0x20, + 0x33, 0x29, 0x5d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x63, 0x6f, 0x6c, + 0x30, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x34, 0x3c, 0x66, 0x33, 0x32, 0x3e, + 0x20, 0x3d, 0x20, 0x28, 0x76, 0x65, 0x63, 0x34, 0x3c, 0x66, 0x33, 0x32, + 0x3e, 0x28, 0x66, 0x33, 0x32, 0x28, 0x28, 0x28, 0x28, 0x63, 0x30, 0x20, + 0x3e, 0x3e, 0x20, 0x31, 0x36, 0x29, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, + 0x35, 0x75, 0x29, 0x29, 0x2c, 0x20, 0x66, 0x33, 0x32, 0x28, 0x28, 0x28, + 0x28, 0x63, 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x38, 0x29, 0x29, 0x20, 0x26, + 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x29, 0x2c, 0x20, 0x66, 0x33, 0x32, + 0x28, 0x28, 0x63, 0x30, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, + 0x29, 0x2c, 0x20, 0x66, 0x33, 0x32, 0x28, 0x28, 0x28, 0x28, 0x63, 0x30, + 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x34, 0x29, 0x29, 0x20, 0x26, 0x20, 0x32, + 0x35, 0x35, 0x75, 0x29, 0x29, 0x29, 0x20, 0x2f, 0x20, 0x32, 0x35, 0x35, + 0x2e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, + 0x72, 0x20, 0x63, 0x6f, 0x6c, 0x31, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x34, + 0x3c, 0x66, 0x33, 0x32, 0x3e, 0x20, 0x3d, 0x20, 0x28, 0x76, 0x65, 0x63, + 0x34, 0x3c, 0x66, 0x33, 0x32, 0x3e, 0x28, 0x66, 0x33, 0x32, 0x28, 0x28, + 0x28, 0x28, 0x63, 0x31, 0x20, 0x3e, 0x3e, 0x20, 0x31, 0x36, 0x29, 0x29, + 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x29, 0x2c, 0x20, 0x66, + 0x33, 0x32, 0x28, 0x28, 0x28, 0x28, 0x63, 0x31, 0x20, 0x3e, 0x3e, 0x20, + 0x38, 0x29, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x29, + 0x2c, 0x20, 0x66, 0x33, 0x32, 0x28, 0x28, 0x63, 0x31, 0x20, 0x26, 0x20, + 0x32, 0x35, 0x35, 0x75, 0x29, 0x29, 0x2c, 0x20, 0x66, 0x33, 0x32, 0x28, + 0x28, 0x28, 0x28, 0x63, 0x31, 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x34, 0x29, + 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x29, 0x29, 0x20, + 0x2f, 0x20, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x66, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, + 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x78, 0x28, 0x63, 0x6f, 0x6c, 0x30, 0x2c, + 0x20, 0x63, 0x6f, 0x6c, 0x31, 0x2c, 0x20, 0x66, 0x72, 0x61, 0x63, 0x29, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x0a, 0x0a, 0x73, 0x74, 0x72, + 0x75, 0x63, 0x74, 0x20, 0x46, 0x53, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x20, + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x40, 0x62, 0x75, 0x69, 0x6c, 0x74, + 0x69, 0x6e, 0x28, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x29, + 0x20, 0x66, 0x72, 0x61, 0x67, 0x5f, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x3a, + 0x20, 0x76, 0x65, 0x63, 0x34, 0x3c, 0x66, 0x33, 0x32, 0x3e, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x40, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, + 0x28, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x5f, 0x66, 0x61, 0x63, 0x69, 0x6e, + 0x67, 0x29, 0x20, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x5f, 0x66, 0x61, 0x63, + 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x62, 0x6f, 0x6f, 0x6c, 0x2c, 0x0a, 0x7d, + 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x46, 0x53, 0x4f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x40, + 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x30, 0x29, 0x20, + 0x66, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, + 0x65, 0x63, 0x34, 0x3c, 0x66, 0x33, 0x32, 0x3e, 0x2c, 0x0a, 0x7d, 0x0a, + 0x40, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x0a, 0x66, 0x6e, + 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x3a, + 0x20, 0x46, 0x53, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x29, 0x20, 0x2d, 0x3e, + 0x20, 0x46, 0x53, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x20, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, + 0x6f, 0x6f, 0x72, 0x64, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, + 0x2e, 0x66, 0x72, 0x61, 0x67, 0x5f, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x6f, 0x6e, + 0x74, 0x46, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x20, 0x3d, 0x20, 0x69, 0x6e, + 0x70, 0x75, 0x74, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x5f, 0x66, 0x61, + 0x63, 0x69, 0x6e, 0x67, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, + 0x69, 0x6e, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x28, 0x29, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x6f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x3a, 0x20, 0x46, 0x53, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x2e, 0x66, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d, + 0x20, 0x66, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x3b, 0x0a, 0x7d, 0x0a, 0x49, 0x53, 0x52, + 0x43, 0xe4, 0x0e, 0x00, 0x00, 0x2f, 0x2a, 0x0a, 0x20, 0x20, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x54, 0x68, 0x69, + 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x69, 0x73, 0x20, 0x70, 0x61, + 0x72, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x59, 0x55, + 0x50, 0x20, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x2e, 0x0a, 0x20, + 0x20, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, + 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x32, 0x36, 0x20, 0x2d, 0x20, 0x6b, + 0x75, 0x6e, 0x69, 0x74, 0x6f, 0x6b, 0x69, 0x40, 0x67, 0x6d, 0x61, 0x69, + 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x59, 0x55, + 0x50, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x6f, 0x70, 0x65, 0x6e, + 0x20, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x6c, 0x69, 0x62, 0x72, + 0x61, 0x72, 0x79, 0x20, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, + 0x74, 0x6f, 0x20, 0x6f, 0x70, 0x65, 0x6e, 0x2d, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x20, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x69, 0x6e, 0x67, + 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x54, 0x68, 0x65, 0x20, 0x63, 0x6f, + 0x64, 0x65, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x20, + 0x69, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, + 0x20, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, + 0x20, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, + 0x65, 0x72, 0x6d, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x49, 0x53, 0x43, 0x20, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a, + 0x20, 0x20, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, + 0x77, 0x2e, 0x69, 0x73, 0x63, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x64, 0x6f, + 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x2f, 0x73, 0x6f, 0x66, 0x74, + 0x77, 0x61, 0x72, 0x65, 0x2d, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, + 0x2d, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2f, 0x69, 0x73, 0x63, 0x2d, + 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x2e, 0x20, 0x50, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x74, + 0x6f, 0x20, 0x75, 0x73, 0x65, 0x2c, 0x20, 0x63, 0x6f, 0x70, 0x79, 0x2c, + 0x20, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x2c, 0x20, 0x61, 0x6e, 0x64, + 0x2f, 0x6f, 0x72, 0x20, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x73, 0x6f, 0x66, 0x74, + 0x77, 0x61, 0x72, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x6e, 0x79, + 0x20, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x20, 0x77, 0x69, 0x74, + 0x68, 0x20, 0x6f, 0x72, 0x0a, 0x20, 0x20, 0x20, 0x77, 0x69, 0x74, 0x68, + 0x6f, 0x75, 0x74, 0x20, 0x66, 0x65, 0x65, 0x20, 0x69, 0x73, 0x20, 0x68, + 0x65, 0x72, 0x65, 0x62, 0x79, 0x20, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, + 0x64, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x74, + 0x68, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x62, 0x6f, 0x76, + 0x65, 0x20, 0x63, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, + 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x0a, 0x20, + 0x20, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, + 0x20, 0x61, 0x70, 0x70, 0x65, 0x61, 0x72, 0x20, 0x69, 0x6e, 0x20, 0x61, + 0x6c, 0x6c, 0x20, 0x63, 0x6f, 0x70, 0x69, 0x65, 0x73, 0x2e, 0x0a, 0x0a, + 0x20, 0x20, 0x20, 0x59, 0x55, 0x50, 0x20, 0x49, 0x53, 0x20, 0x50, 0x52, + 0x4f, 0x56, 0x49, 0x44, 0x45, 0x44, 0x20, 0x22, 0x41, 0x53, 0x20, 0x49, + 0x53, 0x22, 0x20, 0x57, 0x49, 0x54, 0x48, 0x4f, 0x55, 0x54, 0x20, 0x41, + 0x4e, 0x59, 0x20, 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x59, 0x2c, + 0x20, 0x41, 0x4e, 0x44, 0x20, 0x41, 0x4c, 0x4c, 0x20, 0x57, 0x41, 0x52, + 0x52, 0x41, 0x4e, 0x54, 0x49, 0x45, 0x53, 0x2c, 0x20, 0x57, 0x48, 0x45, + 0x54, 0x48, 0x45, 0x52, 0x0a, 0x20, 0x20, 0x20, 0x45, 0x58, 0x50, 0x52, + 0x45, 0x53, 0x53, 0x45, 0x44, 0x20, 0x4f, 0x52, 0x20, 0x49, 0x4d, 0x50, + 0x4c, 0x49, 0x45, 0x44, 0x2c, 0x20, 0x49, 0x4e, 0x43, 0x4c, 0x55, 0x44, + 0x49, 0x4e, 0x47, 0x20, 0x4d, 0x45, 0x52, 0x43, 0x48, 0x41, 0x4e, 0x54, + 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x20, 0x41, 0x4e, 0x44, 0x20, + 0x46, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x20, 0x46, 0x4f, 0x52, 0x20, + 0x50, 0x55, 0x52, 0x50, 0x4f, 0x53, 0x45, 0x2c, 0x20, 0x41, 0x52, 0x45, + 0x0a, 0x20, 0x20, 0x20, 0x44, 0x49, 0x53, 0x43, 0x4c, 0x41, 0x49, 0x4d, + 0x45, 0x44, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x0a, 0x2a, 0x2f, 0x0a, 0x0a, 0x23, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x20, 0x34, 0x35, 0x30, 0x0a, 0x0a, 0x2f, 0x2f, 0x20, 0x46, + 0x75, 0x6c, 0x6c, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x61, + 0x74, 0x65, 0x72, 0x66, 0x61, 0x6c, 0x6c, 0x20, 0x73, 0x68, 0x61, 0x64, + 0x65, 0x72, 0x2e, 0x20, 0x41, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, + 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, + 0x74, 0x72, 0x69, 0x61, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x73, 0x63, 0x72, + 0x6f, 0x6c, 0x6c, 0x73, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x2f, 0x2f, 0x20, + 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x20, 0x68, 0x69, 0x73, + 0x74, 0x6f, 0x72, 0x79, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, + 0x20, 0x64, 0x6f, 0x77, 0x6e, 0x20, 0x62, 0x79, 0x20, 0x6e, 0x75, 0x6d, + 0x52, 0x6f, 0x77, 0x73, 0x20, 0x72, 0x6f, 0x77, 0x73, 0x20, 0x61, 0x6e, + 0x64, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, 0x6e, 0x65, 0x77, + 0x20, 0x72, 0x6f, 0x77, 0x73, 0x0a, 0x2f, 0x2f, 0x20, 0x61, 0x74, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x74, 0x6f, 0x70, 0x2c, 0x20, 0x6d, 0x61, 0x70, + 0x70, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x61, 0x63, 0x68, 0x20, 0x62, 0x69, + 0x6e, 0x27, 0x73, 0x20, 0x72, 0x61, 0x77, 0x20, 0x6d, 0x61, 0x67, 0x6e, + 0x69, 0x74, 0x75, 0x64, 0x65, 0x20, 0x74, 0x68, 0x72, 0x6f, 0x75, 0x67, + 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20, + 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x20, 0x74, 0x61, 0x62, 0x6c, 0x65, + 0x0a, 0x2f, 0x2f, 0x20, 0x28, 0x61, 0x20, 0x6c, 0x65, 0x72, 0x70, 0x20, + 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x20, 0x61, 0x64, 0x6a, 0x61, + 0x63, 0x65, 0x6e, 0x74, 0x20, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, + 0x2c, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x53, + 0x70, 0x65, 0x63, 0x74, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x43, 0x6f, + 0x6c, 0x6f, 0x72, 0x4d, 0x61, 0x70, 0x3a, 0x3a, 0x6d, 0x61, 0x70, 0x29, + 0x20, 0x65, 0x6e, 0x74, 0x69, 0x72, 0x65, 0x6c, 0x79, 0x0a, 0x2f, 0x2f, + 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x50, 0x55, 0x2e, + 0x0a, 0x2f, 0x2f, 0x0a, 0x2f, 0x2f, 0x20, 0x42, 0x69, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x73, 0x3a, 0x0a, 0x2f, 0x2f, 0x20, 0x20, 0x20, 0x73, 0x65, + 0x74, 0x3d, 0x30, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x3d, + 0x30, 0x20, 0x3a, 0x20, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, + 0x20, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x74, 0x65, 0x78, + 0x74, 0x75, 0x72, 0x65, 0x20, 0x28, 0x55, 0x56, 0x2e, 0x79, 0x20, 0x3d, + 0x20, 0x30, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x6f, + 0x70, 0x20, 0x72, 0x6f, 0x77, 0x29, 0x0a, 0x2f, 0x2f, 0x20, 0x20, 0x20, + 0x73, 0x65, 0x74, 0x3d, 0x30, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x3d, 0x31, 0x20, 0x3a, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, + 0x72, 0x20, 0x28, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x63, 0x69, 0x74, 0x20, + 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x2c, 0x20, 0x6c, 0x69, 0x6b, + 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x61, 0x74, 0x74, 0x65, 0x20, + 0x70, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x29, 0x0a, 0x2f, 0x2f, + 0x20, 0x20, 0x20, 0x73, 0x65, 0x74, 0x3d, 0x30, 0x20, 0x62, 0x69, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x32, 0x20, 0x3a, 0x20, 0x57, 0x61, 0x74, + 0x65, 0x72, 0x66, 0x61, 0x6c, 0x6c, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, + 0x20, 0x55, 0x42, 0x4f, 0x0a, 0x2f, 0x2f, 0x20, 0x20, 0x20, 0x73, 0x65, + 0x74, 0x3d, 0x30, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x3d, + 0x33, 0x20, 0x3a, 0x20, 0x52, 0x6f, 0x77, 0x44, 0x61, 0x74, 0x61, 0x20, + 0x55, 0x42, 0x4f, 0x20, 0x28, 0x57, 0x20, 0x2a, 0x20, 0x6e, 0x75, 0x6d, + 0x52, 0x6f, 0x77, 0x73, 0x20, 0x72, 0x61, 0x77, 0x20, 0x6d, 0x61, 0x67, + 0x6e, 0x69, 0x74, 0x75, 0x64, 0x65, 0x73, 0x2c, 0x20, 0x6f, 0x6c, 0x64, + 0x65, 0x73, 0x74, 0x20, 0x72, 0x6f, 0x77, 0x20, 0x66, 0x69, 0x72, 0x73, + 0x74, 0x29, 0x0a, 0x2f, 0x2f, 0x20, 0x20, 0x20, 0x73, 0x65, 0x74, 0x3d, + 0x30, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x34, 0x20, + 0x3a, 0x20, 0x4c, 0x75, 0x74, 0x44, 0x61, 0x74, 0x61, 0x20, 0x55, 0x42, + 0x4f, 0x20, 0x28, 0x32, 0x35, 0x36, 0x20, 0x41, 0x52, 0x47, 0x42, 0x20, + 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, + 0x73, 0x29, 0x0a, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, 0x73, + 0x65, 0x74, 0x20, 0x3d, 0x20, 0x30, 0x2c, 0x20, 0x62, 0x69, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x20, 0x3d, 0x20, 0x30, 0x29, 0x20, 0x75, 0x6e, 0x69, + 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, + 0x32, 0x44, 0x20, 0x75, 0x5f, 0x70, 0x72, 0x65, 0x76, 0x3b, 0x0a, 0x6c, + 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, 0x73, 0x65, 0x74, 0x20, 0x3d, 0x20, + 0x30, 0x2c, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x3d, + 0x20, 0x31, 0x29, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, + 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x20, 0x20, 0x20, 0x75, 0x5f, + 0x73, 0x61, 0x6d, 0x70, 0x3b, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, + 0x28, 0x73, 0x65, 0x74, 0x20, 0x3d, 0x20, 0x30, 0x2c, 0x20, 0x62, 0x69, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x3d, 0x20, 0x32, 0x29, 0x20, 0x75, + 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x57, 0x61, 0x74, 0x65, 0x72, + 0x66, 0x61, 0x6c, 0x6c, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x0a, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x6e, + 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3b, + 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x77, 0x61, 0x74, 0x65, 0x72, 0x66, + 0x61, 0x6c, 0x6c, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x20, + 0x77, 0x69, 0x64, 0x74, 0x68, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x65, 0x78, + 0x65, 0x6c, 0x73, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x62, 0x69, 0x6e, 0x73, + 0x3b, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x6e, 0x75, 0x6d, 0x62, + 0x65, 0x72, 0x20, 0x6f, 0x66, 0x20, 0x6d, 0x61, 0x67, 0x6e, 0x69, 0x74, + 0x75, 0x64, 0x65, 0x20, 0x62, 0x69, 0x6e, 0x73, 0x20, 0x70, 0x65, 0x72, + 0x20, 0x72, 0x6f, 0x77, 0x20, 0x28, 0x3c, 0x3d, 0x20, 0x77, 0x69, 0x64, + 0x74, 0x68, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x20, 0x70, 0x61, 0x64, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, 0x61, 0x64, 0x31, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, 0x61, + 0x64, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x20, 0x70, 0x61, 0x64, 0x33, 0x3b, 0x0a, 0x7d, 0x20, 0x70, 0x3b, + 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, 0x73, 0x65, 0x74, 0x20, + 0x3d, 0x20, 0x30, 0x2c, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x20, 0x3d, 0x20, 0x33, 0x29, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, + 0x6d, 0x20, 0x52, 0x6f, 0x77, 0x44, 0x61, 0x74, 0x61, 0x0a, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x6d, 0x61, 0x67, + 0x73, 0x5b, 0x35, 0x31, 0x32, 0x5d, 0x3b, 0x20, 0x2f, 0x2f, 0x20, 0x32, + 0x30, 0x34, 0x38, 0x20, 0x72, 0x61, 0x77, 0x20, 0x6d, 0x61, 0x67, 0x6e, + 0x69, 0x74, 0x75, 0x64, 0x65, 0x73, 0x20, 0x70, 0x61, 0x63, 0x6b, 0x65, + 0x64, 0x20, 0x61, 0x73, 0x20, 0x76, 0x65, 0x63, 0x34, 0x73, 0x20, 0x28, + 0x73, 0x74, 0x64, 0x31, 0x34, 0x30, 0x20, 0x74, 0x69, 0x67, 0x68, 0x74, + 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x57, 0x20, 0x69, 0x73, 0x20, 0x61, + 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x6f, 0x66, + 0x20, 0x34, 0x29, 0x0a, 0x7d, 0x20, 0x72, 0x6f, 0x77, 0x73, 0x3b, 0x0a, + 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, 0x73, 0x65, 0x74, 0x20, 0x3d, + 0x20, 0x30, 0x2c, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, + 0x3d, 0x20, 0x34, 0x29, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, + 0x20, 0x4c, 0x75, 0x74, 0x44, 0x61, 0x74, 0x61, 0x0a, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x75, 0x76, 0x65, 0x63, 0x34, 0x20, 0x6c, 0x75, 0x74, + 0x5b, 0x36, 0x34, 0x5d, 0x3b, 0x20, 0x2f, 0x2f, 0x20, 0x32, 0x35, 0x36, + 0x20, 0x41, 0x52, 0x47, 0x42, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x73, + 0x20, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x20, 0x61, 0x73, 0x20, 0x75, + 0x76, 0x65, 0x63, 0x34, 0x73, 0x0a, 0x7d, 0x20, 0x6c, 0x75, 0x74, 0x3b, + 0x0a, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, 0x6c, 0x6f, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x30, 0x29, 0x20, 0x6f, + 0x75, 0x74, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x66, 0x72, 0x61, 0x67, + 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x0a, 0x0a, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x20, 0x66, 0x65, 0x74, 0x63, 0x68, 0x4d, 0x61, 0x67, 0x28, 0x69, + 0x6e, 0x74, 0x20, 0x69, 0x64, 0x78, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x72, 0x6f, 0x77, + 0x73, 0x2e, 0x6d, 0x61, 0x67, 0x73, 0x5b, 0x69, 0x64, 0x78, 0x20, 0x3e, + 0x3e, 0x20, 0x32, 0x5d, 0x5b, 0x69, 0x64, 0x78, 0x20, 0x26, 0x20, 0x33, + 0x5d, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x6d, + 0x61, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x76, 0x65, 0x63, 0x32, 0x20, 0x75, 0x76, 0x20, 0x3d, 0x20, 0x67, 0x6c, + 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x78, + 0x79, 0x20, 0x2f, 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, 0x70, 0x2e, 0x77, + 0x69, 0x64, 0x74, 0x68, 0x2c, 0x20, 0x70, 0x2e, 0x68, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, + 0x28, 0x75, 0x76, 0x2e, 0x79, 0x20, 0x3e, 0x3d, 0x20, 0x70, 0x2e, 0x6e, + 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, 0x2f, 0x20, 0x70, 0x2e, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x72, 0x61, + 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20, 0x74, 0x65, 0x78, + 0x74, 0x75, 0x72, 0x65, 0x28, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, + 0x32, 0x44, 0x28, 0x75, 0x5f, 0x70, 0x72, 0x65, 0x76, 0x2c, 0x20, 0x75, + 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x29, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x32, + 0x28, 0x75, 0x76, 0x2e, 0x78, 0x2c, 0x20, 0x75, 0x76, 0x2e, 0x79, 0x20, + 0x2d, 0x20, 0x70, 0x2e, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, + 0x2f, 0x20, 0x70, 0x2e, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x29, 0x29, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x78, + 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x67, 0x6c, 0x5f, 0x46, 0x72, + 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x78, 0x29, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, + 0x79, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x67, 0x6c, 0x5f, 0x46, + 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x79, 0x29, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, + 0x52, 0x6f, 0x77, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x75, 0x70, 0x6c, + 0x6f, 0x61, 0x64, 0x65, 0x64, 0x20, 0x6f, 0x6c, 0x64, 0x65, 0x73, 0x74, + 0x2d, 0x66, 0x69, 0x72, 0x73, 0x74, 0x3b, 0x20, 0x63, 0x61, 0x6e, 0x76, + 0x61, 0x73, 0x20, 0x72, 0x6f, 0x77, 0x20, 0x79, 0x20, 0x28, 0x66, 0x72, + 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x6f, 0x70, 0x29, 0x20, + 0x73, 0x68, 0x6f, 0x77, 0x73, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x28, 0x6e, 0x75, + 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x20, 0x2d, 0x20, 0x31, 0x20, 0x2d, 0x20, + 0x79, 0x29, 0x2d, 0x74, 0x68, 0x20, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x20, 0x72, 0x6f, 0x77, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x72, 0x6f, 0x77, 0x20, 0x3d, + 0x20, 0x69, 0x6e, 0x74, 0x28, 0x70, 0x2e, 0x6e, 0x75, 0x6d, 0x52, 0x6f, + 0x77, 0x73, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x20, 0x2d, 0x20, 0x79, 0x3b, + 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, + 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x6f, 0x6c, 0x61, 0x74, 0x65, + 0x20, 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x20, 0x61, 0x64, 0x6a, + 0x61, 0x63, 0x65, 0x6e, 0x74, 0x20, 0x6d, 0x61, 0x67, 0x6e, 0x69, 0x74, + 0x75, 0x64, 0x65, 0x20, 0x62, 0x69, 0x6e, 0x73, 0x20, 0x73, 0x6f, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, + 0x79, 0x20, 0x61, 0x78, 0x69, 0x73, 0x20, 0x69, 0x73, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x73, 0x6d, 0x6f, + 0x6f, 0x74, 0x68, 0x20, 0x28, 0x61, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x69, + 0x61, 0x73, 0x65, 0x64, 0x29, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x20, 0x74, + 0x68, 0x6f, 0x75, 0x67, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x65, + 0x78, 0x74, 0x75, 0x72, 0x65, 0x20, 0x69, 0x73, 0x20, 0x77, 0x69, 0x64, + 0x65, 0x72, 0x20, 0x74, 0x68, 0x61, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x62, 0x69, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x2f, 0x2f, 0x20, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, + 0x62, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x28, 0x78, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x70, 0x2e, 0x62, + 0x69, 0x6e, 0x73, 0x20, 0x2f, 0x20, 0x70, 0x2e, 0x77, 0x69, 0x64, 0x74, + 0x68, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x69, 0x6e, 0x74, 0x20, 0x62, 0x69, 0x6e, 0x30, 0x20, 0x3d, 0x20, 0x69, + 0x6e, 0x74, 0x28, 0x62, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x29, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, + 0x62, 0x69, 0x6e, 0x31, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x6e, 0x28, 0x62, + 0x69, 0x6e, 0x30, 0x20, 0x2b, 0x20, 0x31, 0x2c, 0x20, 0x69, 0x6e, 0x74, + 0x28, 0x70, 0x2e, 0x62, 0x69, 0x6e, 0x73, 0x29, 0x20, 0x2d, 0x20, 0x31, + 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x66, 0x72, 0x61, 0x63, 0x42, 0x69, 0x6e, + 0x20, 0x3d, 0x20, 0x62, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x20, 0x2d, 0x20, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x62, 0x69, 0x6e, 0x30, 0x29, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x20, 0x6d, 0x61, 0x67, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x78, + 0x28, 0x66, 0x65, 0x74, 0x63, 0x68, 0x4d, 0x61, 0x67, 0x28, 0x72, 0x6f, + 0x77, 0x20, 0x2a, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x70, 0x2e, 0x62, 0x69, + 0x6e, 0x73, 0x29, 0x20, 0x2b, 0x20, 0x62, 0x69, 0x6e, 0x30, 0x29, 0x2c, + 0x20, 0x66, 0x65, 0x74, 0x63, 0x68, 0x4d, 0x61, 0x67, 0x28, 0x72, 0x6f, + 0x77, 0x20, 0x2a, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x70, 0x2e, 0x62, 0x69, + 0x6e, 0x73, 0x29, 0x20, 0x2b, 0x20, 0x62, 0x69, 0x6e, 0x31, 0x29, 0x2c, + 0x20, 0x66, 0x72, 0x61, 0x63, 0x42, 0x69, 0x6e, 0x29, 0x3b, 0x0a, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x20, 0x74, 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, + 0x6d, 0x61, 0x67, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, + 0x30, 0x29, 0x20, 0x2a, 0x20, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, + 0x69, 0x30, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x74, 0x29, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, + 0x20, 0x69, 0x31, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x6e, 0x28, 0x69, 0x30, + 0x20, 0x2b, 0x20, 0x31, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x29, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x20, 0x66, 0x72, 0x61, 0x63, 0x20, 0x3d, 0x20, 0x74, 0x20, 0x2d, + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x69, 0x30, 0x29, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x75, 0x69, 0x6e, 0x74, + 0x20, 0x63, 0x30, 0x20, 0x3d, 0x20, 0x6c, 0x75, 0x74, 0x2e, 0x6c, 0x75, + 0x74, 0x5b, 0x69, 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x5d, 0x5b, 0x69, + 0x30, 0x20, 0x26, 0x20, 0x33, 0x5d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x20, 0x63, 0x31, 0x20, + 0x3d, 0x20, 0x6c, 0x75, 0x74, 0x2e, 0x6c, 0x75, 0x74, 0x5b, 0x69, 0x31, + 0x20, 0x3e, 0x3e, 0x20, 0x32, 0x5d, 0x5b, 0x69, 0x31, 0x20, 0x26, 0x20, + 0x33, 0x5d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x76, 0x65, 0x63, 0x34, 0x20, 0x63, 0x6f, 0x6c, 0x30, 0x20, 0x3d, 0x20, + 0x76, 0x65, 0x63, 0x34, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, + 0x63, 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x31, 0x36, 0x29, 0x20, 0x26, 0x20, + 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x28, 0x28, 0x63, 0x30, 0x20, 0x3e, 0x3e, 0x20, 0x38, 0x29, 0x20, 0x26, + 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x28, 0x63, 0x30, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, + 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x30, 0x20, + 0x3e, 0x3e, 0x20, 0x32, 0x34, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, + 0x75, 0x29, 0x29, 0x20, 0x2f, 0x20, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, + 0x34, 0x20, 0x63, 0x6f, 0x6c, 0x31, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, + 0x34, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x31, 0x20, + 0x3e, 0x3e, 0x20, 0x31, 0x36, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, + 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, + 0x31, 0x20, 0x3e, 0x3e, 0x20, 0x38, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, + 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x63, + 0x31, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x2c, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x28, 0x28, 0x63, 0x31, 0x20, 0x3e, 0x3e, 0x20, + 0x32, 0x34, 0x29, 0x20, 0x26, 0x20, 0x32, 0x35, 0x35, 0x75, 0x29, 0x29, + 0x20, 0x2f, 0x20, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x3b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x72, 0x61, 0x67, 0x43, 0x6f, + 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x78, 0x28, 0x63, 0x6f, + 0x6c, 0x30, 0x2c, 0x20, 0x63, 0x6f, 0x6c, 0x31, 0x2c, 0x20, 0x66, 0x72, + 0x61, 0x63, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x7d, + 0x0a, 0x52, 0x45, 0x46, 0x4c, 0x67, 0x05, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, + 0x6e, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x07, 0x00, 0x00, 0x00, 0x52, 0x6f, 0x77, 0x44, 0x61, 0x74, 0x61, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x67, 0x73, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x13, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x0f, + 0x00, 0x00, 0x00, 0x57, 0x61, 0x74, 0x65, 0x72, 0x66, 0x61, 0x6c, 0x6c, + 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, + 0x00, 0x00, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x05, 0x00, 0x00, 0x00, 0x77, 0x69, 0x64, 0x74, 0x68, 0x04, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x62, 0x69, 0x6e, 0x73, + 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x70, 0x61, 0x64, 0x30, + 0x10, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x70, 0x61, 0x64, 0x31, + 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x70, 0x61, 0x64, 0x32, + 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x70, 0x61, 0x64, 0x33, + 0x1c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x4c, + 0x75, 0x74, 0x44, 0x61, 0x74, 0x61, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x6c, 0x75, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0xb1, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x66, 0x72, 0x61, 0x67, + 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x75, 0x5f, 0x70, 0x72, 0x65, 0x76, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, + 0x00, 0x75, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x0b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, + 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, + 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x24, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x43, 0x61, 0x70, 0x61, 0x62, + 0x69, 0x6c, 0x69, 0x74, 0x79, 0x28, 0x31, 0x29, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00 diff --git a/modules/yup_audio_gui/displays/yup_SpectrogramComponentShader.vert b/modules/yup_audio_gui/displays/yup_SpectrogramComponentShader.vert new file mode 100644 index 000000000..ad60fe039 --- /dev/null +++ b/modules/yup_audio_gui/displays/yup_SpectrogramComponentShader.vert @@ -0,0 +1,29 @@ +/* + ============================================================================== + + This file is part of the YUP library. + Copyright (c) 2026 - kunitoki@gmail.com + + YUP is an open source library subject to open-source licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#version 450 + +void main() +{ + float x = float((gl_VertexIndex & 1u) << 2u) - 1.0; + float y = float((gl_VertexIndex & 2u) << 1u) - 1.0; + gl_Position = vec4(x, y, 0.0, 1.0); +} diff --git a/modules/yup_audio_gui/displays/yup_SpectrumAnalyzerComponent.cpp b/modules/yup_audio_gui/displays/yup_SpectrumAnalyzerComponent.cpp index abcfbdbaa..d06a7156b 100644 --- a/modules/yup_audio_gui/displays/yup_SpectrumAnalyzerComponent.cpp +++ b/modules/yup_audio_gui/displays/yup_SpectrumAnalyzerComponent.cpp @@ -95,8 +95,7 @@ void SpectrumAnalyzerComponent::processFFT() } // Apply window function - for (int i = 0; i < fftSize; ++i) - fftInputBuffer[static_cast (i)] *= windowBuffer[static_cast (i)]; + FloatVectorOperations::multiply (fftInputBuffer.data(), windowBuffer.data(), fftInputBuffer.data(), fftSize); // Perform FFT fftProcessor->performRealFFTForward (fftInputBuffer.data(), fftOutputBuffer.data()); diff --git a/modules/yup_dsp/frequency/yup_SpectrumAnalyzerState.cpp b/modules/yup_dsp/frequency/yup_SpectrumAnalyzerState.cpp index 9c13c9e9d..3119e3760 100644 --- a/modules/yup_dsp/frequency/yup_SpectrumAnalyzerState.cpp +++ b/modules/yup_dsp/frequency/yup_SpectrumAnalyzerState.cpp @@ -53,10 +53,12 @@ SpectrumAnalyzerState::~SpectrumAnalyzerState() void SpectrumAnalyzerState::pushSample (float sample) noexcept { // Lock-free write to FIFO - safe for audio thread - const auto writeScope = audioFifo->write (1); + { + const auto writeScope = audioFifo->write (1); - if (writeScope.blockSize1 > 0) - sampleBuffer[static_cast (writeScope.startIndex1)] = sample; + if (writeScope.blockSize1 > 0) + sampleBuffer[static_cast (writeScope.startIndex1)] = sample; + } // Check if we have enough samples for FFT processing with overlap if (audioFifo->getNumReady() >= fftSize) @@ -72,18 +74,20 @@ void SpectrumAnalyzerState::pushSamples (const float* samples, int numSamples) n return; // Lock-free write to FIFO - safe for audio thread - const auto writeScope = audioFifo->write (numSamples); - - // Copy first block - if (writeScope.blockSize1 > 0) - { - std::copy_n (samples, writeScope.blockSize1, &sampleBuffer[static_cast (writeScope.startIndex1)]); - } - - // Copy second block (wrap-around case) - if (writeScope.blockSize2 > 0) { - std::copy_n (samples + writeScope.blockSize1, writeScope.blockSize2, &sampleBuffer[static_cast (writeScope.startIndex2)]); + const auto writeScope = audioFifo->write (numSamples); + + // Copy first block + if (writeScope.blockSize1 > 0) + { + std::copy_n (samples, writeScope.blockSize1, &sampleBuffer[static_cast (writeScope.startIndex1)]); + } + + // Copy second block (wrap-around case) + if (writeScope.blockSize2 > 0) + { + std::copy_n (samples + writeScope.blockSize1, writeScope.blockSize2, &sampleBuffer[static_cast (writeScope.startIndex2)]); + } } // Check if we have enough samples for FFT processing with overlap diff --git a/tests/yup_audio_gui/yup_SpectrogramComponent.cpp b/tests/yup_audio_gui/yup_SpectrogramComponent.cpp index d882cca2a..ab23a8639 100644 --- a/tests/yup_audio_gui/yup_SpectrogramComponent.cpp +++ b/tests/yup_audio_gui/yup_SpectrogramComponent.cpp @@ -24,6 +24,7 @@ #include #include +#include #include using namespace yup; @@ -51,6 +52,46 @@ std::vector createSineBuffer (int numSamples, float period) return buffer; } +// Returns true when every pixel of the given row equals the expected RGBA color +// (bytes are compared as R, G, B, A, matching the readback pixel layout). +bool spectrogramRowIsColor (const Image& image, int row, uint32 expectedColor) +{ + const auto raw = image.getRawData(); + const auto rowBytes = static_cast (image.getWidth()) * 4u; + const auto rowData = raw.data() + static_cast (row) * rowBytes; + + const uint8 expectedBytes[4] = { + static_cast ((expectedColor >> 16) & 0xff), // R + static_cast ((expectedColor >> 8) & 0xff), // G + static_cast (expectedColor & 0xff), // B + static_cast ((expectedColor >> 24) & 0xff), // A + }; + + for (int x = 0; x < image.getWidth(); ++x) + { + if (std::memcmp (rowData + static_cast (x) * 4u, expectedBytes, 4) != 0) + return false; + } + + return true; +} + +// Returns true when two rows of two images hold identical bytes. +bool spectrogramRowsEqual (const Image& a, int rowA, const Image& b, int rowB) +{ + if (a.getWidth() != b.getWidth()) + return false; + + const auto rawA = a.getRawData(); + const auto rawB = b.getRawData(); + const auto rowBytes = static_cast (a.getWidth()) * 4u; + + return std::memcmp (rawA.data() + static_cast (rowA) * rowBytes, + rawB.data() + static_cast (rowB) * rowBytes, + rowBytes) + == 0; +} + } // namespace class SpectrogramComponentTests : public ::testing::Test @@ -129,7 +170,6 @@ TEST_F (SpectrogramComponentTests, ConstructorInitializesDefaults) { EXPECT_EQ (2048, spectrogram->getFFTSize()); EXPECT_EQ (WindowType::hann, spectrogram->getWindowType()); - EXPECT_EQ (25, spectrogram->getUpdateRate()); EXPECT_FLOAT_EQ (20.0f, spectrogram->getMinFrequency()); EXPECT_FLOAT_EQ (20000.0f, spectrogram->getMaxFrequency()); EXPECT_FLOAT_EQ (-100.0f, spectrogram->getMinDecibels()); @@ -175,22 +215,6 @@ TEST_F (SpectrogramComponentTests, SetWindowTypeUpdatesCurrentWindow) EXPECT_EQ (WindowType::rectangular, spectrogram->getWindowType()); } -TEST_F (SpectrogramComponentTests, SetUpdateRateClampsToSupportedRange) -{ - spectrogram->setUpdateRate (30); - EXPECT_EQ (30, spectrogram->getUpdateRate()); - - spectrogram->setUpdateRate (0); - EXPECT_EQ (1, spectrogram->getUpdateRate()); - - spectrogram->setUpdateRate (-10); - EXPECT_EQ (1, spectrogram->getUpdateRate()); - - spectrogram->setUpdateRate (1000); - EXPECT_GE (spectrogram->getUpdateRate(), 60); - EXPECT_LE (spectrogram->getUpdateRate(), 63); -} - TEST_F (SpectrogramComponentTests, SetFrequencyRangeUpdatesAndClampsValues) { spectrogram->setFrequencyRange (100.0f, 5000.0f); @@ -244,28 +268,10 @@ TEST_F (SpectrogramComponentTests, SetNumHistoryFramesUpdatesAndClampsToMinimum) spectrogram->setNumHistoryFrames (64); EXPECT_EQ (64, spectrogram->getNumHistoryFrames()); - ASSERT_TRUE (spectrogram->getSpectrogramImage().isValid()); - EXPECT_EQ (SpectrogramComponent::defaultSpectrogramWidth, spectrogram->getSpectrogramImage().getWidth()); - EXPECT_EQ (64, spectrogram->getSpectrogramImage().getHeight()); spectrogram->setNumHistoryFrames (1); EXPECT_EQ (4, spectrogram->getNumHistoryFrames()); - ASSERT_TRUE (spectrogram->getSpectrogramImage().isValid()); - EXPECT_EQ (SpectrogramComponent::defaultSpectrogramWidth, spectrogram->getSpectrogramImage().getWidth()); - EXPECT_EQ (4, spectrogram->getSpectrogramImage().getHeight()); -} - -TEST_F (SpectrogramComponentTests, GetSpectrogramImageReturnsCurrentHistoryImage) -{ - spectrogram->setNumHistoryFrames (8); - - const auto& image = spectrogram->getSpectrogramImage(); - - ASSERT_TRUE (image.isValid()); - EXPECT_EQ (SpectrogramComponent::defaultSpectrogramWidth, image.getWidth()); - EXPECT_EQ (8, image.getHeight()); - EXPECT_EQ (PixelFormat::RGBA, image.getPixelFormat()); } TEST_F (SpectrogramComponentTests, SetOverlapFactorDelegatesToAnalyzerState) @@ -282,27 +288,63 @@ TEST_F (SpectrogramComponentTests, SetOverlapFactorDelegatesToAnalyzerState) EXPECT_EQ (spectrogram->getFFTSize(), state->getHopSize()); } +TEST_F (SpectrogramComponentTests, SetScrollSpeedClampsToNonNegative) +{ + EXPECT_FLOAT_EQ (1.0f, spectrogram->getScrollSpeed()); + + spectrogram->setScrollSpeed (2.0f); + EXPECT_FLOAT_EQ (2.0f, spectrogram->getScrollSpeed()); + + spectrogram->setScrollSpeed (0.0f); + EXPECT_FLOAT_EQ (0.0f, spectrogram->getScrollSpeed()); + + spectrogram->setScrollSpeed (-1.0f); + EXPECT_FLOAT_EQ (0.0f, spectrogram->getScrollSpeed()); +} + //============================================================================== // Runtime Tests //============================================================================== -TEST_F (SpectrogramComponentTests, TimerCallbackWithoutAudioDataDoesNotCrash) +TEST_F (SpectrogramComponentTests, RefreshDisplayWithoutAudioDataDoesNotCrash) { - spectrogram->timerCallback(); + spectrogram->refreshDisplay (1.0 / 60.0); EXPECT_TRUE (true); } -TEST_F (SpectrogramComponentTests, TimerCallbackWithAudioDataDoesNotCrash) +TEST_F (SpectrogramComponentTests, RefreshDisplayWithAudioDataDoesNotCrash) { const auto testData = createSineBuffer (2048, 100.0f); state->pushSamples (testData.data(), static_cast (testData.size())); - spectrogram->timerCallback(); + spectrogram->refreshDisplay (1.0 / 60.0); EXPECT_TRUE (true); } +TEST_F (SpectrogramComponentTests, RefreshDisplaySkipsStaleBacklog) +{ + // refreshDisplay() only processes FFTs while the component is showing. + auto parent = std::make_unique ("parent"); + parent->setVisible (true); + parent->addAndMakeVisible (*spectrogram); + + spectrogram->setFFTSize (512); + spectrogram->setOverlapFactor (0.75f); // hop = fftSize / 4 + + // Fill the FIFO with far more than one analysis window (a stale backlog). + const auto testData = createSineBuffer (spectrogram->getFFTSize() * 8, 100.0f); + state->pushSamples (testData.data(), static_cast (testData.size())); + + spectrogram->refreshDisplay (1.0 / 60.0); + + // The stale rows are skipped: after ingestion the FIFO holds less than a + // full window instead of accumulating the backlog (which would make the + // display lag progressively behind the audio). + EXPECT_LT (state->getNumAvailableSamples(), spectrogram->getFFTSize()); +} + TEST_F (SpectrogramComponentTests, ClearHistoryDoesNotChangeConfiguration) { spectrogram->setFrequencyRange (100.0f, 10000.0f); @@ -318,9 +360,6 @@ TEST_F (SpectrogramComponentTests, ClearHistoryDoesNotChangeConfiguration) EXPECT_FLOAT_EQ (-6.0f, spectrogram->getMaxDecibels()); EXPECT_DOUBLE_EQ (48000.0, spectrogram->getSampleRate()); EXPECT_EQ (32, spectrogram->getNumHistoryFrames()); - ASSERT_TRUE (spectrogram->getSpectrogramImage().isValid()); - EXPECT_EQ (SpectrogramComponent::defaultSpectrogramWidth, spectrogram->getSpectrogramImage().getWidth()); - EXPECT_EQ (32, spectrogram->getSpectrogramImage().getHeight()); } TEST_F (SpectrogramComponentTests, PaintWithoutAudioDataDoesNotCrash) @@ -334,11 +373,11 @@ TEST_F (SpectrogramComponentTests, PaintWithoutAudioDataDoesNotCrash) EXPECT_TRUE (true); } -TEST_F (SpectrogramComponentTests, PaintAfterTimerCallbackDoesNotCrash) +TEST_F (SpectrogramComponentTests, PaintAfterRefreshDisplayDoesNotCrash) { const auto testData = createSineBuffer (2048, 100.0f); state->pushSamples (testData.data(), static_cast (testData.size())); - spectrogram->timerCallback(); + spectrogram->refreshDisplay (1.0 / 60.0); auto context = yup_constructHeadlessGraphicsContext ({}, {}); auto renderer = context->makeRenderer (800, 400); @@ -368,7 +407,6 @@ TEST_F (SpectrogramComponentTests, CompleteWorkflow) { spectrogram->setFFTSize (4096); spectrogram->setWindowType (WindowType::hamming); - spectrogram->setUpdateRate (30); spectrogram->setFrequencyRange (20.0f, 20000.0f); spectrogram->setDecibelRange (-100.0f, 0.0f); spectrogram->setSampleRate (48000.0); @@ -378,7 +416,7 @@ TEST_F (SpectrogramComponentTests, CompleteWorkflow) const auto testData = createSineBuffer (4096, 100.0f); state->pushSamples (testData.data(), static_cast (testData.size())); - spectrogram->timerCallback(); + spectrogram->refreshDisplay (1.0 / 60.0); auto context = yup_constructHeadlessGraphicsContext ({}, {}); auto renderer = context->makeRenderer (800, 400); @@ -388,7 +426,153 @@ TEST_F (SpectrogramComponentTests, CompleteWorkflow) EXPECT_EQ (4096, spectrogram->getFFTSize()); EXPECT_EQ (WindowType::hamming, spectrogram->getWindowType()); - EXPECT_EQ (30, spectrogram->getUpdateRate()); EXPECT_EQ (128, spectrogram->getNumHistoryFrames()); EXPECT_FLOAT_EQ (0.5f, spectrogram->getOverlapFactor()); } + +//============================================================================== +// GPU Path Tests (skipped when no GPU backend is available) +//============================================================================== + +class SpectrogramComponentGpuTests : public ::testing::Test +{ +protected: + static void SetUpTestSuite() + { + gpuContext = GraphicsContext::createContext (GpuPlatform::Metal, {}); + if (gpuContext == nullptr) + return; + + auto probe = GpuCanvas::create (*gpuContext, 64, 64); + if (probe == nullptr) + gpuContext.reset(); + } + + static void TearDownTestSuite() + { + gpuContext.reset(); + } + + void SetUp() override + { + if (gpuContext == nullptr) + GTEST_SKIP() << "No Metal GPU context available"; + + paintCanvas = GpuCanvas::create (*gpuContext, 800, 400); + if (paintCanvas == nullptr) + GTEST_SKIP() << "Unable to create paint GpuCanvas"; + + mm = MessageManager::getInstance(); + state = std::make_unique (2048); + spectrogram = std::make_unique (*state); + spectrogram->setBounds (0.0f, 0.0f, 800.0f, 400.0f); + + // refreshDisplay() only processes FFTs while the component is showing. + parent = std::make_unique ("parent"); + parent->setVisible (true); + parent->addAndMakeVisible (*spectrogram); + } + + void TearDown() override + { + spectrogram.reset(); + state.reset(); + paintCanvas.reset(); + parent.reset(); + } + + void paintComponent() + { + auto& g = paintCanvas->beginDraw(); + spectrogram->paint (g); + paintCanvas->commit(); + } + + void pushOneFftRow() + { + const auto testData = createSineBuffer (spectrogram->getFFTSize(), 100.0f); + state->pushSamples (testData.data(), static_cast (testData.size())); + spectrogram->refreshDisplay (1.0 / 60.0); + paintComponent(); + } + + static std::unique_ptr gpuContext; + + MessageManager* mm = nullptr; + GpuCanvas::Ptr paintCanvas; + std::unique_ptr parent; + std::unique_ptr state; + std::unique_ptr spectrogram; +}; + +std::unique_ptr SpectrogramComponentGpuTests::gpuContext; + +TEST_F (SpectrogramComponentGpuTests, AppliesRowsAndScrollsHistoryOnGpu) +{ + spectrogram->setNumHistoryFrames (16); + spectrogram->setOverlapFactor (0.0f); // one FFT per fftSize samples + + paintComponent(); // first paint creates the internal ping-pong canvases + + // First row: only the top row is non-background. + pushOneFftRow(); + + auto snapshot1 = spectrogram->getSpectrogramImage(); + + ASSERT_TRUE (snapshot1.isValid()); + EXPECT_EQ (SpectrogramComponent::defaultSpectrogramRenderWidth, snapshot1.getWidth()); + EXPECT_EQ (16, snapshot1.getHeight()); + EXPECT_FALSE (spectrogramRowIsColor (snapshot1, 0, 0xFF0a0a0a)); + + for (int row = 1; row < 16; ++row) + EXPECT_TRUE (spectrogramRowIsColor (snapshot1, row, 0xFF0a0a0a)); + + // Second row: the previous top row must have scrolled down by exactly one + // row, and the new top row must be non-background. + pushOneFftRow(); + + auto snapshot2 = spectrogram->getSpectrogramImage(); + + ASSERT_TRUE (snapshot2.isValid()); + EXPECT_TRUE (spectrogramRowsEqual (snapshot2, 1, snapshot1, 0)); + EXPECT_FALSE (spectrogramRowIsColor (snapshot2, 0, 0xFF0a0a0a)); + + for (int row = 2; row < 16; ++row) + EXPECT_TRUE (spectrogramRowIsColor (snapshot2, row, 0xFF0a0a0a)); +} + +TEST_F (SpectrogramComponentGpuTests, ClearHistoryResetsGpuHistoryToBackground) +{ + spectrogram->setNumHistoryFrames (8); + spectrogram->setOverlapFactor (0.0f); + + paintComponent(); + pushOneFftRow(); + + auto before = spectrogram->getSpectrogramImage(); + ASSERT_TRUE (before.isValid()); + EXPECT_FALSE (spectrogramRowIsColor (before, 0, 0xFF0a0a0a)); + + spectrogram->clearHistory(); + paintComponent(); // clearHistory() destroys the history canvases; repaint to recreate them + + auto after = spectrogram->getSpectrogramImage(); + ASSERT_TRUE (after.isValid()); + + for (int row = 0; row < 8; ++row) + EXPECT_TRUE (spectrogramRowIsColor (after, row, 0xFF0a0a0a)); +} + +TEST_F (SpectrogramComponentGpuTests, GetSpectrogramImageReturnsCurrentHistoryImage) +{ + spectrogram->setNumHistoryFrames (8); + + paintComponent(); // first paint creates the internal ping-pong canvases + + const auto image = spectrogram->getSpectrogramImage(); + + ASSERT_TRUE (image.isValid()); + EXPECT_EQ (SpectrogramComponent::defaultSpectrogramRenderWidth, image.getWidth()); + EXPECT_EQ (8, image.getHeight()); + EXPECT_EQ (PixelFormat::RGBA, image.getPixelFormat()); +} From 2d19cc9680d82eb6589af3478fb5771a01811bd2 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Fri, 11 Sep 2026 21:38:20 +0200 Subject: [PATCH 2/6] Improve FFTProcessor --- CHANGELOG.md | 2 + docs/dsp/frequency.md | 58 +- .../audiograph/source/nodes/AnalyzerNodes.h | 2 +- .../displays/yup_SpectrogramComponent.cpp | 8 +- .../displays/yup_SpectrogramComponent.h | 2 +- .../yup_SpectrumAnalyzerComponent.cpp | 2 +- .../displays/yup_SpectrumAnalyzerComponent.h | 2 +- .../spectral/yup_SpectralBridge.cpp | 2 +- .../spectral/yup_SpectralBridge.h | 2 +- .../convolution/yup_PartitionedConvolver.cpp | 4 +- .../yup_dsp/frequency/yup_FFTProcessor.cpp | 645 +-- modules/yup_dsp/frequency/yup_FFTProcessor.h | 37 +- modules/yup_dsp/frequency/yup_OouraFFT8g.h | 9 +- .../frequency/yup_OouraFFT8g_double.cpp | 3480 +++++++++++++++++ ...ouraFFT8g.cpp => yup_OouraFFT8g_float.cpp} | 6 - modules/yup_dsp/onsets/yup_Spectrogram.h | 2 +- modules/yup_dsp/yup_dsp.cpp | 4 +- tests/yup_dsp/yup_FFTProcessor.cpp | 408 +- tests/yup_dsp/yup_NoiseGenerators.cpp | 2 +- 19 files changed, 4263 insertions(+), 414 deletions(-) create mode 100644 modules/yup_dsp/frequency/yup_OouraFFT8g_double.cpp rename modules/yup_dsp/frequency/{yup_OouraFFT8g.cpp => yup_OouraFFT8g_float.cpp} (99%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 828b76b9e..3452cad4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - Added a `TuningMap` class (`midi/yup_TuningMap.h`): maps MIDI note numbers to frequencies under an arbitrary scale and key map, loading Scala `.scl` scale files and `.kbm` key map files via `loadScale()` / `loadKeyMap()` (which return a `yup::Result` and keep the previous tuning when a file fails to parse). `isNoteActive()` reports the notes a key map asks to retune, taken from the range in its header unless the file carries `< first last` lines, which declare it instead +- `FFTProcessor` is now templated on the sample type - `FFTProcessor` (the default) or `FFTProcessor` - and every backend (PFFFT, Apple vDSP, Intel IPP, FFTW3 and the Ooura fallback, which now ships both a `float` and a `double` implementation) gained a native double-precision path. References to the nested scaling enum need qualifying, e.g. `FFTProcessor::FFTScaling::asymmetric` + ### Graphics - `Image::getWidth()` and `Image::getHeight()` now return 0 on an invalid image instead of asserting and dereferencing null. Other accessors and pixel access still assert, as documented diff --git a/docs/dsp/frequency.md b/docs/dsp/frequency.md index f18e9b3a1..ebdbe3045 100644 --- a/docs/dsp/frequency.md +++ b/docs/dsp/frequency.md @@ -6,9 +6,9 @@ implementation the module can fall back on. ## FFTProcessor -`FFTProcessor` is a multi-backend, float-only FFT engine with a unified -interface. The best available backend is selected **at compile time**, in this -priority order: +`FFTProcessor` is a multi-backend FFT engine with a unified +interface, available in `float` and `double` precision. The best available +backend is selected **at compile time**, in this priority order: 1. **PFFFT** (`YUP_FFT_USING_PFFFT`) 2. **Apple vDSP** (`YUP_FFT_USING_VDSP`, via the `Accelerate` framework) @@ -20,12 +20,26 @@ The engine is non-copyable and move-only; `getBackendName()` reports which backend is active (`"PFFFT"`, `"Apple vDSP"`, `"Intel IPP"`, `"FFTW3"`, `"Ooura FFT"`, or `"Unknown"`). +### Precision + +The processing precision is the template argument — `FFTProcessor` +(the default) or `FFTProcessor`. Each backend uses its native +double-precision path where the underlying library exposes one: PFFFT +(`pffft_` / `pffftd_`), Apple vDSP (`vDSP_…` / `vDSP_…D`), Intel IPP +(`_32f` / `_64f`) and FFTW3 (`fftwf_` / `fftw_`). The Ooura fallback ships both +precisions of its transform routines. + +```cpp +FFTProcessor fftFloat (512); // fastest +FFTProcessor fftDouble (512); // higher precision +``` + ### Supported sizes and layout FFT sizes are powers of two in `[64, 65536]`. Buffers are **interleaved complex pairs** — `[re0, im0, re1, im1, ...]` — so an N-point complex spectrum -occupies `2 * N` floats. The engine handles backend-specific packed layouts -(e.g. PFFFT's `[DC, Nyquist, re1, im1, ...]`, Ooura's real-DFT packing) +occupies `2 * N` sample values. The engine handles backend-specific packed +layouts (e.g. PFFFT's `[DC, Nyquist, re1, im1, ...]`, Ooura's real-DFT packing) internally, presenting the same interleaved format to the caller for every backend. @@ -40,16 +54,16 @@ backend. | `asymmetric` | inverse scaled by `1/N`, forward unscaled | ```cpp -FFTProcessor fft (512); +FFTProcessor fft (512); std::vector realInput (512), complexOutput (1024); -fft.performRealFFTForward (realInput.data(), complexOutput.data()); // R → C, 512 reals → 1024 floats +fft.performRealFFTForward (realInput.data(), complexOutput.data()); // R → C, 512 reals → 1024 samples fft.performRealFFTInverse (complexOutput.data(), realInput.data()); // C → R fft.performComplexFFTForward (complexInput, complexOutput); // C → C fft.performComplexFFTInverse (complexInput, complexOutput); -fft.setScaling (FFTProcessor::FFTScaling::unitary); +fft.setScaling (FFTProcessor::FFTScaling::unitary); fft.setSize (1024); // re-initialize for a new power-of-two size ``` @@ -96,34 +110,6 @@ Key methods: data). - `reset()` — clears the FIFO and the ready flag. -## OouraFFT8g - -`yup_OouraFFT8g.h` exposes Takuya Ooura's classic **FFT8g** suite: single- -dimension, power-of-two, split-radix, decimation-in-frequency, in-place, -table-based transforms (public-domain ISC license, © 1996–2001 Ooura). These -are the primitives used by the Ooura backend of `FFTProcessor`, and are also -available directly: - -- `cdft (n, isgn, a, ip, w)` — complex DFT; `n = 2 × (#complex points)`; - `isgn = 1` forward, `-1` inverse; in-place. -- `rdft (n, isgn, a, ip, w)` — real DFT; packed output - `[DC, Nyquist, Re1, Im1, Re2, Im2, ...]`; in-place. -- `ddct` / `ddst` — discrete cosine / sine transforms. -- `dfct` / `dfst` — cosine / sine transforms of a real DFT, needing an extra - scratch buffer `t`. - -The work areas follow Ooura's original contract: `ip[0]` must be `0` on first -use (initialization flag), `ip` needs `2 + sqrt(n/2)` ints, and `w` needs -`n/2` floats: - -```cpp -std::vector a (1024); // 512 complex points -std::vector ip (2 + int (std::sqrt (512))); -std::vector w (512); -ip[0] = 0; // first call only -yup::cdft (1024, 1, a.data(), ip.data(), w.data()); // forward complex FFT, in-place -``` - ## Related - [Windowing](math.md) — pair `FFTProcessor` with `WindowFunctions` for diff --git a/examples/audiograph/source/nodes/AnalyzerNodes.h b/examples/audiograph/source/nodes/AnalyzerNodes.h index 80a258b14..d1aec89b4 100644 --- a/examples/audiograph/source/nodes/AnalyzerNodes.h +++ b/examples/audiograph/source/nodes/AnalyzerNodes.h @@ -601,7 +601,7 @@ class SpectrumAnalyzerDisplayComponent final SpectrumAnalyzerProcessor& processor; yup::Color accentColor; int fftSize = 0; - yup::FFTProcessor fftProcessor; + yup::FFTProcessor fftProcessor; std::vector fftInput; std::vector fftOutput; std::vector window; diff --git a/modules/yup_audio_gui/displays/yup_SpectrogramComponent.cpp b/modules/yup_audio_gui/displays/yup_SpectrogramComponent.cpp index 56d7872f1..1c1654638 100644 --- a/modules/yup_audio_gui/displays/yup_SpectrogramComponent.cpp +++ b/modules/yup_audio_gui/displays/yup_SpectrogramComponent.cpp @@ -218,7 +218,7 @@ SpectrogramComponent::~SpectrogramComponent() //============================================================================== void SpectrogramComponent::initializeFFTBuffers() { - fftProcessor = std::make_unique (fftSize); + fftProcessor = std::make_unique> (fftSize); fftInputBuffer.resize (static_cast (fftSize), 0.0f); fftOutputBuffer.resize (static_cast (fftSize * 2), 0.0f); windowBuffer.resize (static_cast (fftSize), 0.0f); @@ -553,9 +553,9 @@ void SpectrogramComponent::ensureWaterfallPipeline() } GpuPipelineOptions options; - options.colorTargetCount = 1; - options.colorTargets[0].format = GpuTextureFormat::rgba8unorm; - options.colorTargets[0].blendEnabled = false; + auto& colorTarget = options.colorTargets.emplace_back(); + colorTarget.format = GpuTextureFormat::rgba8unorm; + colorTarget.blendEnabled = false; auto result = GpuPipeline::compileFromBundle (gpuDevice, loaded.getReference(), options); diff --git a/modules/yup_audio_gui/displays/yup_SpectrogramComponent.h b/modules/yup_audio_gui/displays/yup_SpectrogramComponent.h index 8eb783ad4..070540485 100644 --- a/modules/yup_audio_gui/displays/yup_SpectrogramComponent.h +++ b/modules/yup_audio_gui/displays/yup_SpectrogramComponent.h @@ -293,7 +293,7 @@ class YUP_API SpectrogramComponent : public Component //============================================================================== SpectrumAnalyzerState& analyzerState; - std::unique_ptr fftProcessor; + std::unique_ptr> fftProcessor; std::vector fftInputBuffer; std::vector fftOutputBuffer; std::vector windowBuffer; diff --git a/modules/yup_audio_gui/displays/yup_SpectrumAnalyzerComponent.cpp b/modules/yup_audio_gui/displays/yup_SpectrumAnalyzerComponent.cpp index d06a7156b..fea74e988 100644 --- a/modules/yup_audio_gui/displays/yup_SpectrumAnalyzerComponent.cpp +++ b/modules/yup_audio_gui/displays/yup_SpectrumAnalyzerComponent.cpp @@ -44,7 +44,7 @@ SpectrumAnalyzerComponent::~SpectrumAnalyzerComponent() //============================================================================== void SpectrumAnalyzerComponent::initializeFFTBuffers() { - fftProcessor = std::make_unique (fftSize); + fftProcessor = std::make_unique> (fftSize); fftInputBuffer.resize (fftSize, 0.0f); fftOutputBuffer.resize (fftSize * 2, 0.0f); // Complex output needs 2x space windowBuffer.resize (fftSize, 0.0f); diff --git a/modules/yup_audio_gui/displays/yup_SpectrumAnalyzerComponent.h b/modules/yup_audio_gui/displays/yup_SpectrumAnalyzerComponent.h index da5e94925..a63ae65fd 100644 --- a/modules/yup_audio_gui/displays/yup_SpectrumAnalyzerComponent.h +++ b/modules/yup_audio_gui/displays/yup_SpectrumAnalyzerComponent.h @@ -260,7 +260,7 @@ class YUP_API SpectrumAnalyzerComponent SpectrumAnalyzerState& analyzerState; // FFT processing (performed on UI thread) - std::unique_ptr fftProcessor; + std::unique_ptr> fftProcessor; std::vector fftInputBuffer; // Real input samples std::vector fftOutputBuffer; // Complex FFT output std::vector windowBuffer; // Window function diff --git a/modules/yup_audio_processors/spectral/yup_SpectralBridge.cpp b/modules/yup_audio_processors/spectral/yup_SpectralBridge.cpp index 4ace53bde..8368348b6 100644 --- a/modules/yup_audio_processors/spectral/yup_SpectralBridge.cpp +++ b/modules/yup_audio_processors/spectral/yup_SpectralBridge.cpp @@ -302,7 +302,7 @@ void SpectralBridge::allocateResources() hopSize = fftSize / overlapFactor; fft.setSize (fftSize); - fft.setScaling (FFTProcessor::FFTScaling::asymmetric); + fft.setScaling (FFTProcessor::FFTScaling::asymmetric); buildWindows(); diff --git a/modules/yup_audio_processors/spectral/yup_SpectralBridge.h b/modules/yup_audio_processors/spectral/yup_SpectralBridge.h index 6333f2bb8..96fe803a6 100644 --- a/modules/yup_audio_processors/spectral/yup_SpectralBridge.h +++ b/modules/yup_audio_processors/spectral/yup_SpectralBridge.h @@ -194,7 +194,7 @@ class YUP_API SpectralBridge : public AudioProcessor void processAvailableFrames (AudioProcessContext& context); //============================================================================== - FFTProcessor fft; + FFTProcessor fft; std::shared_ptr spectralProcessor; SpectralBuffer spectralBuffer; diff --git a/modules/yup_dsp/convolution/yup_PartitionedConvolver.cpp b/modules/yup_dsp/convolution/yup_PartitionedConvolver.cpp index 7e1a0a47b..7c5db4e3c 100644 --- a/modules/yup_dsp/convolution/yup_PartitionedConvolver.cpp +++ b/modules/yup_dsp/convolution/yup_PartitionedConvolver.cpp @@ -37,7 +37,7 @@ class PartitionedConvolver::FFTLayer fftSize = hopSize * 2; fftProcessor.setSize (fftSize); - fftProcessor.setScaling (FFTProcessor::FFTScaling::asymmetric); + fftProcessor.setScaling (FFTProcessor::FFTScaling::asymmetric); overlapBuffer.assign (static_cast (hopSize), 0.0f); timeBuffer.assign (static_cast (fftSize), 0.0f); @@ -176,7 +176,7 @@ class PartitionedConvolver::FFTLayer int hopSize = 0; int fftSize = 0; - FFTProcessor fftProcessor; + FFTProcessor fftProcessor; // IR partitions in frequency domain std::vector> frequencyPartitions; diff --git a/modules/yup_dsp/frequency/yup_FFTProcessor.cpp b/modules/yup_dsp/frequency/yup_FFTProcessor.cpp index 94f0daceb..eb55182e8 100644 --- a/modules/yup_dsp/frequency/yup_FFTProcessor.cpp +++ b/modules/yup_dsp/frequency/yup_FFTProcessor.cpp @@ -21,21 +21,24 @@ namespace yup { +namespace detail +{ //============================================================================== // Base implementation class -class FFTProcessor::Engine +template +class FFTEngine { public: - virtual ~Engine() = default; + virtual ~FFTEngine() = default; virtual void initialize (int fftSize) = 0; virtual void cleanup() = 0; - virtual void performRealFFTForward (const float* realInput, float* complexOutput) = 0; - virtual void performRealFFTInverse (const float* complexInput, float* realOutput) = 0; - virtual void performComplexFFTForward (const float* complexInput, float* complexOutput) = 0; - virtual void performComplexFFTInverse (const float* complexInput, float* complexOutput) = 0; + virtual void performRealFFTForward (const SampleType* realInput, SampleType* complexOutput) = 0; + virtual void performRealFFTInverse (const SampleType* complexInput, SampleType* realOutput) = 0; + virtual void performComplexFFTForward (const SampleType* complexInput, SampleType* complexOutput) = 0; + virtual void performComplexFFTInverse (const SampleType* complexInput, SampleType* complexOutput) = 0; virtual String getBackendName() const = 0; @@ -43,29 +46,40 @@ class FFTProcessor::Engine int fftSize = 0; }; +} // namespace detail + //============================================================================== // PFFFT implementation #if YUP_FFT_USING_PFFFT -class PFFTEngine : public FFTProcessor::Engine +template +class PFFTEngine : public detail::FFTEngine { public: - ~PFFTEngine() override { cleanup(); } + ~PFFTEngine() override { this->cleanup(); } void initialize (int newFftSize) override { - cleanup(); + this->cleanup(); - fftSize = newFftSize; + this->fftSize = newFftSize; - realSetup = pffft_new_setup (fftSize, PFFFT_REAL); - complexSetup = pffft_new_setup (fftSize, PFFFT_COMPLEX); + if constexpr (std::is_same_v) + { + realSetupD = pffftd_new_setup (this->fftSize, PFFFT_REAL); + complexSetupD = pffftd_new_setup (this->fftSize, PFFFT_COMPLEX); + } + else + { + realSetup = pffft_new_setup (this->fftSize, PFFFT_REAL); + complexSetup = pffft_new_setup (this->fftSize, PFFFT_COMPLEX); + } - tempBuffer.resize (static_cast (fftSize * 2)); + tempBuffer.resize (static_cast (this->fftSize * 2)); // Allocate work buffers - PFFFT uses stack for small sizes, heap for larger - if (fftSize >= 16384) - workBuffer.resize (static_cast (fftSize)); + if (this->fftSize >= 16384) + workBuffer.resize (static_cast (this->fftSize)); } void cleanup() override @@ -82,68 +96,96 @@ class PFFTEngine : public FFTProcessor::Engine complexSetup = nullptr; } + if (realSetupD != nullptr) + { + pffftd_destroy_setup (realSetupD); + realSetupD = nullptr; + } + + if (complexSetupD != nullptr) + { + pffftd_destroy_setup (complexSetupD); + complexSetupD = nullptr; + } + workBuffer.clear(); tempBuffer.clear(); } - void performRealFFTForward (const float* realInput, float* complexOutput) override + void performRealFFTForward (const SampleType* realInput, SampleType* complexOutput) override { - float* workPtr = workBuffer.empty() ? nullptr : workBuffer.data(); + SampleType* workPtr = workBuffer.empty() ? nullptr : workBuffer.data(); - pffft_transform_ordered (realSetup, realInput, complexOutput, workPtr, PFFFT_FORWARD); + if constexpr (std::is_same_v) + pffftd_transform_ordered (realSetupD, realInput, complexOutput, workPtr, PFFFT_FORWARD); + else + pffft_transform_ordered (realSetup, realInput, complexOutput, workPtr, PFFFT_FORWARD); - convertFromPFFTPacked (complexOutput, fftSize); + convertFromPFFTPacked (complexOutput, this->fftSize); } - void performRealFFTInverse (const float* complexInput, float* realOutput) override + void performRealFFTInverse (const SampleType* complexInput, SampleType* realOutput) override { - float* workPtr = workBuffer.empty() ? nullptr : workBuffer.data(); + SampleType* workPtr = workBuffer.empty() ? nullptr : workBuffer.data(); - convertToPFFTPacked (complexInput, tempBuffer.data(), fftSize); + convertToPFFTPacked (complexInput, tempBuffer.data(), this->fftSize); - pffft_transform_ordered (realSetup, tempBuffer.data(), realOutput, workPtr, PFFFT_BACKWARD); + if constexpr (std::is_same_v) + pffftd_transform_ordered (realSetupD, tempBuffer.data(), realOutput, workPtr, PFFFT_BACKWARD); + else + pffft_transform_ordered (realSetup, tempBuffer.data(), realOutput, workPtr, PFFFT_BACKWARD); } - void performComplexFFTForward (const float* complexInput, float* complexOutput) override + void performComplexFFTForward (const SampleType* complexInput, SampleType* complexOutput) override { - float* workPtr = workBuffer.empty() ? nullptr : workBuffer.data(); - pffft_transform_ordered (complexSetup, complexInput, complexOutput, workPtr, PFFFT_FORWARD); + SampleType* workPtr = workBuffer.empty() ? nullptr : workBuffer.data(); + + if constexpr (std::is_same_v) + pffftd_transform_ordered (complexSetupD, complexInput, complexOutput, workPtr, PFFFT_FORWARD); + else + pffft_transform_ordered (complexSetup, complexInput, complexOutput, workPtr, PFFFT_FORWARD); } - void performComplexFFTInverse (const float* complexInput, float* complexOutput) override + void performComplexFFTInverse (const SampleType* complexInput, SampleType* complexOutput) override { - float* workPtr = workBuffer.empty() ? nullptr : workBuffer.data(); - pffft_transform_ordered (complexSetup, complexInput, complexOutput, workPtr, PFFFT_BACKWARD); + SampleType* workPtr = workBuffer.empty() ? nullptr : workBuffer.data(); + + if constexpr (std::is_same_v) + pffftd_transform_ordered (complexSetupD, complexInput, complexOutput, workPtr, PFFFT_BACKWARD); + else + pffft_transform_ordered (complexSetup, complexInput, complexOutput, workPtr, PFFFT_BACKWARD); } String getBackendName() const override { return "PFFFT"; } private: // Convert from PFFFT packed format to standard interleaved format - void convertFromPFFTPacked (float* interleaved, int size) + void convertFromPFFTPacked (SampleType* interleaved, int size) { // PFFFT packed: [DC_real, Nyquist_real, bin1_real, bin1_imag, bin2_real, bin2_imag, ...] // Standard: [DC_real, DC_imag, bin1_real, bin1_imag, ..., Nyquist_real, Nyquist_imag] - interleaved[size] = std::exchange (interleaved[1], 0.0f); // Nyquist real (from packed[1]) - interleaved[size + 1] = 0.0f; // Nyquist imaginary (always 0) + interleaved[size] = std::exchange (interleaved[1], SampleType (0)); // Nyquist real (from packed[1]) + interleaved[size + 1] = SampleType (0); // Nyquist imaginary (always 0) } // Convert from standard interleaved format to PFFFT packed format - void convertToPFFTPacked (const float* interleaved, float* packed, int size) + void convertToPFFTPacked (const SampleType* interleaved, SampleType* packed, int size) { // Standard: [DC_real, DC_imag, bin1_real, bin1_imag, ..., Nyquist_real, Nyquist_imag] // PFFFT packed: [DC_real, Nyquist_real, bin1_real, bin1_imag, bin2_real, bin2_imag, ...] packed[0] = interleaved[0]; // DC real packed[1] = interleaved[size]; // Nyquist real (to packed[1]) - std::memcpy (&packed[2], &interleaved[2], (size - 2) * sizeof (float)); + std::memcpy (&packed[2], &interleaved[2], static_cast (size - 2) * sizeof (SampleType)); } PFFFT_Setup* realSetup = nullptr; PFFFT_Setup* complexSetup = nullptr; - std::vector workBuffer; - std::vector tempBuffer; + PFFFTD_Setup* realSetupD = nullptr; + PFFFTD_Setup* complexSetupD = nullptr; + std::vector workBuffer; + std::vector tempBuffer; }; #endif @@ -152,20 +194,23 @@ class PFFTEngine : public FFTProcessor::Engine // Ooura FFT implementation #if YUP_FFT_USING_OOURA -class OouraEngine : public FFTProcessor::Engine +template +class OouraEngine : public detail::FFTEngine { public: - ~OouraEngine() override { cleanup(); } + ~OouraEngine() override { this->cleanup(); } void initialize (int newFftSize) override { - cleanup(); + this->cleanup(); - fftSize = newFftSize; + this->fftSize = newFftSize; - const int workSize = 2 + static_cast (std::sqrt (fftSize / 2)); - workBuffer.resize (static_cast (fftSize * 2)); // Need space for complex data - tempBuffer.resize (static_cast (fftSize)); + // The complex transforms operate on 2 * fftSize values, which needs a larger + // bit-reversal table than the real transform (Ooura requires 2 + sqrt (n / 2)). + const int workSize = 2 + static_cast (std::sqrt (static_cast (this->fftSize))); + workBuffer.resize (static_cast (this->fftSize * 2)); // Need space for complex data + tempBuffer.resize (static_cast (this->fftSize)); intBuffer.resize (static_cast (workSize)); intBuffer[0] = 0; // Initialization flag } @@ -177,84 +222,84 @@ class OouraEngine : public FFTProcessor::Engine intBuffer.clear(); } - void performRealFFTForward (const float* realInput, float* complexOutput) override + void performRealFFTForward (const SampleType* realInput, SampleType* complexOutput) override { // Copy real input to work buffer - std::copy (realInput, realInput + fftSize, workBuffer.begin()); + std::copy (realInput, realInput + this->fftSize, workBuffer.begin()); // Real-to-complex forward transform - rdft (fftSize, 1, workBuffer.data(), intBuffer.data(), tempBuffer.data()); + rdft (this->fftSize, 1, workBuffer.data(), intBuffer.data(), tempBuffer.data()); // Convert Ooura format to standard interleaved complex format // Ooura rdft output: a[0]=DC, a[1]=Nyquist, a[2k]=Re[k], a[2k+1]=Im[k] for k=1..n/2-1 - complexOutput[0] = workBuffer[0]; // DC real - complexOutput[1] = 0.0f; // DC imaginary + complexOutput[0] = workBuffer[0]; // DC real + complexOutput[1] = SampleType (0); // DC imaginary // Nyquist frequency - Ooura stores it at position 1 - complexOutput[fftSize] = workBuffer[1]; // Nyquist real - complexOutput[fftSize + 1] = 0.0f; // Nyquist imaginary + complexOutput[this->fftSize] = workBuffer[1]; // Nyquist real + complexOutput[this->fftSize + 1] = SampleType (0); // Nyquist imaginary // Handle frequencies 1 to n/2-1 // Ooura stores them as alternating real/imag starting at index 2 - for (int i = 1; i < fftSize / 2; ++i) + for (int i = 1; i < this->fftSize / 2; ++i) { complexOutput[i * 2] = workBuffer[i * 2]; // real part complexOutput[i * 2 + 1] = -workBuffer[i * 2 + 1]; // imaginary part (negate) } } - void performRealFFTInverse (const float* complexInput, float* realOutput) override + void performRealFFTInverse (const SampleType* complexInput, SampleType* realOutput) override { // Convert standard interleaved format to Ooura format - workBuffer[0] = complexInput[0]; // DC real - workBuffer[1] = complexInput[fftSize]; // Nyquist real + workBuffer[0] = complexInput[0]; // DC real + workBuffer[1] = complexInput[this->fftSize]; // Nyquist real - for (int i = 1; i < fftSize / 2; ++i) + for (int i = 1; i < this->fftSize / 2; ++i) { workBuffer[i * 2] = complexInput[i * 2]; // real part workBuffer[i * 2 + 1] = -complexInput[i * 2 + 1]; // imaginary part (negate back) } // Complex-to-real inverse transform - rdft (fftSize, -1, workBuffer.data(), intBuffer.data(), tempBuffer.data()); + rdft (this->fftSize, -1, workBuffer.data(), intBuffer.data(), tempBuffer.data()); // Apply Ooura-specific scaling for real inverse: needs 2x factor - for (int i = 0; i < fftSize; ++i) + for (int i = 0; i < this->fftSize; ++i) { - realOutput[i] = workBuffer[i] * 2.0f; + realOutput[i] = workBuffer[i] * SampleType (2); } } - void performComplexFFTForward (const float* complexInput, float* complexOutput) override + void performComplexFFTForward (const SampleType* complexInput, SampleType* complexOutput) override { // Copy interleaved complex input to work buffer - std::copy (complexInput, complexInput + fftSize * 2, workBuffer.begin()); + std::copy (complexInput, complexInput + this->fftSize * 2, workBuffer.begin()); // Complex forward transform - cdft (fftSize * 2, 1, workBuffer.data(), intBuffer.data(), tempBuffer.data()); + cdft (this->fftSize * 2, 1, workBuffer.data(), intBuffer.data(), tempBuffer.data()); // Copy result - std::copy (workBuffer.begin(), workBuffer.begin() + fftSize * 2, complexOutput); + std::copy (workBuffer.begin(), workBuffer.begin() + this->fftSize * 2, complexOutput); } - void performComplexFFTInverse (const float* complexInput, float* complexOutput) override + void performComplexFFTInverse (const SampleType* complexInput, SampleType* complexOutput) override { // Copy interleaved complex input to work buffer - std::copy (complexInput, complexInput + fftSize * 2, workBuffer.begin()); + std::copy (complexInput, complexInput + this->fftSize * 2, workBuffer.begin()); // Complex inverse transform - cdft (fftSize * 2, -1, workBuffer.data(), intBuffer.data(), tempBuffer.data()); + cdft (this->fftSize * 2, -1, workBuffer.data(), intBuffer.data(), tempBuffer.data()); // Copy result - let framework handle scaling - std::copy (workBuffer.begin(), workBuffer.begin() + fftSize * 2, complexOutput); + std::copy (workBuffer.begin(), workBuffer.begin() + this->fftSize * 2, complexOutput); } String getBackendName() const override { return "Ooura FFT"; } private: - std::vector workBuffer; + std::vector workBuffer; std::vector intBuffer; - std::vector tempBuffer; + std::vector tempBuffer; }; #endif @@ -263,111 +308,145 @@ class OouraEngine : public FFTProcessor::Engine // Apple vDSP implementation #if YUP_FFT_USING_VDSP -class VDSPEngine : public FFTProcessor::Engine +template +class VDSPEngine : public detail::FFTEngine { public: - ~VDSPEngine() override { cleanup(); } + ~VDSPEngine() override { this->cleanup(); } void initialize (int newFftSize) override { - cleanup(); + this->cleanup(); - fftSize = newFftSize; - order = static_cast (std::log2 (fftSize)); + this->fftSize = newFftSize; + order = static_cast (std::log2 (this->fftSize)); - fftSetup = vDSP_create_fftsetup (order, FFT_RADIX2); + if constexpr (std::is_same_v) + fftSetup = vDSP_create_fftsetupD (order, FFT_RADIX2); + else + fftSetup = vDSP_create_fftsetup (order, FFT_RADIX2); - forwardNormalisation = 0.5f; - inverseNormalisation = 1.0f / static_cast (fftSize); + forwardNormalisation = SampleType (0.5); + inverseNormalisation = SampleType (1) / static_cast (this->fftSize); - tempBuffer.resize (fftSize * 2); + tempBuffer.resize (static_cast (this->fftSize * 2)); } void cleanup() override { if (fftSetup != nullptr) { - vDSP_destroy_fftsetup (fftSetup); + if constexpr (std::is_same_v) + vDSP_destroy_fftsetupD (fftSetup); + else + vDSP_destroy_fftsetup (fftSetup); + fftSetup = nullptr; } tempBuffer.clear(); } - void performRealFFTForward (const float* realInput, float* complexOutput) override + void performRealFFTForward (const SampleType* realInput, SampleType* complexOutput) override { // Copy input to output buffer to work in-place - std::copy_n (realInput, fftSize, complexOutput); - complexOutput[fftSize] = 0.0f; + std::copy_n (realInput, this->fftSize, complexOutput); + complexOutput[this->fftSize] = SampleType (0); // Perform vDSP real FFT - DSPSplitComplex splitInOut = { complexOutput, complexOutput + 1 }; - vDSP_fft_zrip (fftSetup, &splitInOut, 2, order, kFFTDirection_Forward); + SplitComplex splitInOut = { complexOutput, complexOutput + 1 }; + + if constexpr (std::is_same_v) + vDSP_fft_zripD (fftSetup, &splitInOut, 2, order, kFFTDirection_Forward); + else + vDSP_fft_zrip (fftSetup, &splitInOut, 2, order, kFFTDirection_Forward); // Normalize vDSP output to match other engines (vDSP outputs 2x expected) - vDSP_vsmul (complexOutput, 1, &forwardNormalisation, complexOutput, 1, static_cast (fftSize << 1)); + if constexpr (std::is_same_v) + vDSP_vsmulD (complexOutput, 1, &forwardNormalisation, complexOutput, 1, static_cast (this->fftSize << 1)); + else + vDSP_vsmul (complexOutput, 1, &forwardNormalisation, complexOutput, 1, static_cast (this->fftSize << 1)); // Set Nyquist bin (real only, imaginary = 0), set DC bin (real only, imaginary = 0) - auto* complexData = reinterpret_cast (complexOutput); - complexData[fftSize >> 1] = ComplexFloat (complexData[0].imag(), 0.0f); - complexData[0] = ComplexFloat (complexData[0].real(), 0.0f); + auto* complexData = reinterpret_cast (complexOutput); + complexData[this->fftSize >> 1] = Complex (complexData[0].imag(), SampleType (0)); + complexData[0] = Complex (complexData[0].real(), SampleType (0)); } - void performRealFFTInverse (const float* complexInput, float* realOutput) override + void performRealFFTInverse (const SampleType* complexInput, SampleType* realOutput) override { // Copy input to temp buffer for processing - std::copy_n (complexInput, fftSize * 2, tempBuffer.data()); + std::copy_n (complexInput, this->fftSize * 2, tempBuffer.data()); // Pack Nyquist real into DC imaginary for vDSP - auto* complexData = reinterpret_cast (tempBuffer.data()); - complexData[0] = ComplexFloat (complexData[0].real(), complexData[fftSize >> 1].real()); + auto* complexData = reinterpret_cast (tempBuffer.data()); + complexData[0] = Complex (complexData[0].real(), complexData[this->fftSize >> 1].real()); // Perform vDSP real inverse FFT - DSPSplitComplex splitInOut = { tempBuffer.data(), tempBuffer.data() + 1 }; - vDSP_fft_zrip (fftSetup, &splitInOut, 2, order, kFFTDirection_Inverse); + SplitComplex splitInOut = { tempBuffer.data(), tempBuffer.data() + 1 }; + + if constexpr (std::is_same_v) + vDSP_fft_zripD (fftSetup, &splitInOut, 2, order, kFFTDirection_Inverse); + else + vDSP_fft_zrip (fftSetup, &splitInOut, 2, order, kFFTDirection_Inverse); // Clear upper half and extract real parts - vDSP_vclr (tempBuffer.data() + fftSize, 1, static_cast (fftSize)); + if constexpr (std::is_same_v) + vDSP_vclrD (tempBuffer.data() + this->fftSize, 1, static_cast (this->fftSize)); + else + vDSP_vclr (tempBuffer.data() + this->fftSize, 1, static_cast (this->fftSize)); - std::copy_n (tempBuffer.data(), fftSize, realOutput); + std::copy_n (tempBuffer.data(), this->fftSize, realOutput); } - void performComplexFFTForward (const float* complexInput, float* complexOutput) override + void performComplexFFTForward (const SampleType* complexInput, SampleType* complexOutput) override { - std::copy_n (complexInput, fftSize * 2, tempBuffer.data()); + std::copy_n (complexInput, this->fftSize * 2, tempBuffer.data()); - DSPSplitComplex splitInput = { tempBuffer.data(), tempBuffer.data() + 1 }; - DSPSplitComplex splitOutput = { complexOutput, complexOutput + 1 }; + SplitComplex splitInput = { tempBuffer.data(), tempBuffer.data() + 1 }; + SplitComplex splitOutput = { complexOutput, complexOutput + 1 }; // Perform complex FFT - vDSP_fft_zop (fftSetup, &splitInput, 2, &splitOutput, 2, order, kFFTDirection_Forward); + if constexpr (std::is_same_v) + vDSP_fft_zopD (fftSetup, &splitInput, 2, &splitOutput, 2, order, kFFTDirection_Forward); + else + vDSP_fft_zop (fftSetup, &splitInput, 2, &splitOutput, 2, order, kFFTDirection_Forward); // Normalization - float scale = forwardNormalisation * 2.0f; - vDSP_vsmul (complexOutput, 1, &scale, complexOutput, 1, static_cast (fftSize << 1)); + SampleType scale = forwardNormalisation * SampleType (2); + + if constexpr (std::is_same_v) + vDSP_vsmulD (complexOutput, 1, &scale, complexOutput, 1, static_cast (this->fftSize << 1)); + else + vDSP_vsmul (complexOutput, 1, &scale, complexOutput, 1, static_cast (this->fftSize << 1)); } - void performComplexFFTInverse (const float* complexInput, float* complexOutput) override + void performComplexFFTInverse (const SampleType* complexInput, SampleType* complexOutput) override { - std::memcpy (tempBuffer.data(), complexInput, fftSize * 2 * sizeof (float)); + std::memcpy (tempBuffer.data(), complexInput, static_cast (this->fftSize * 2) * sizeof (SampleType)); - DSPSplitComplex splitInput = { tempBuffer.data(), tempBuffer.data() + 1 }; - DSPSplitComplex splitOutput = { complexOutput, complexOutput + 1 }; + SplitComplex splitInput = { tempBuffer.data(), tempBuffer.data() + 1 }; + SplitComplex splitOutput = { complexOutput, complexOutput + 1 }; // Perform complex FFT - vDSP_fft_zop (fftSetup, &splitInput, 2, &splitOutput, 2, order, kFFTDirection_Inverse); + if constexpr (std::is_same_v) + vDSP_fft_zopD (fftSetup, &splitInput, 2, &splitOutput, 2, order, kFFTDirection_Inverse); + else + vDSP_fft_zop (fftSetup, &splitInput, 2, &splitOutput, 2, order, kFFTDirection_Inverse); } String getBackendName() const override { return "Apple vDSP"; } private: - using ComplexFloat = std::complex; + using Complex = std::complex; + using SplitComplex = std::conditional_t, DSPDoubleSplitComplex, DSPSplitComplex>; + using SetupType = std::conditional_t, FFTSetupD, FFTSetup>; - FFTSetup fftSetup = nullptr; + SetupType fftSetup = nullptr; vDSP_Length order = 0; - float forwardNormalisation = 0.5f; - float inverseNormalisation = 1.0f; - std::vector tempBuffer; + SampleType forwardNormalisation = SampleType (0.5); + SampleType inverseNormalisation = SampleType (1); + std::vector tempBuffer; }; #endif @@ -376,33 +455,51 @@ class VDSPEngine : public FFTProcessor::Engine // Intel IPP implementation #if YUP_FFT_USING_IPP -class IPPEngine : public FFTProcessor::Engine +template +class IPPEngine : public detail::FFTEngine { public: - ~IPPEngine() override { cleanup(); } + ~IPPEngine() override { this->cleanup(); } void initialize (int newFftSize) override { - cleanup(); - fftSize = newFftSize; + this->cleanup(); + this->fftSize = newFftSize; + const int order = static_cast (std::log2 (this->fftSize)); int specSizeComplex, specSizeReal, workSizeComplex, workSizeReal; - // Get buffer sizes - ippsFFTGetSize_C_32fc (static_cast (std::log2 (fftSize)), IPP_FFT_NODIV_BY_ANY, ippAlgHintFast, &specSizeComplex, nullptr, &workSizeComplex); - ippsFFTGetSize_R_32f (static_cast (std::log2 (fftSize)), IPP_FFT_NODIV_BY_ANY, ippAlgHintFast, &specSizeReal, nullptr, &workSizeReal); + if constexpr (std::is_same_v) + { + // Get buffer sizes + ippsFFTGetSize_C_64fc (order, IPP_FFT_NODIV_BY_ANY, ippAlgHintFast, &specSizeComplex, nullptr, &workSizeComplex); + ippsFFTGetSize_R_64f (order, IPP_FFT_NODIV_BY_ANY, ippAlgHintFast, &specSizeReal, nullptr, &workSizeReal); - // Allocate specification structures - specComplex = reinterpret_cast (ippsMalloc_8u (specSizeComplex)); - specReal = reinterpret_cast (ippsMalloc_8u (specSizeReal)); + // Allocate specification structures + specComplex = reinterpret_cast (ippsMalloc_8u (specSizeComplex)); + specReal = reinterpret_cast (ippsMalloc_8u (specSizeReal)); - // Initialize specifications - ippsFFTInit_C_32fc (&specComplex, static_cast (std::log2 (fftSize)), IPP_FFT_NODIV_BY_ANY, ippAlgHintFast); - ippsFFTInit_R_32f (&specReal, static_cast (std::log2 (fftSize)), IPP_FFT_NODIV_BY_ANY, ippAlgHintFast); + // Initialize specifications + ippsFFTInit_C_64fc (&specComplex, order, IPP_FFT_NODIV_BY_ANY, ippAlgHintFast); + ippsFFTInit_R_64f (&specReal, order, IPP_FFT_NODIV_BY_ANY, ippAlgHintFast); + } + else + { + // Get buffer sizes + ippsFFTGetSize_C_32fc (order, IPP_FFT_NODIV_BY_ANY, ippAlgHintFast, &specSizeComplex, nullptr, &workSizeComplex); + ippsFFTGetSize_R_32f (order, IPP_FFT_NODIV_BY_ANY, ippAlgHintFast, &specSizeReal, nullptr, &workSizeReal); + + // Allocate specification structures + specComplex = reinterpret_cast (ippsMalloc_8u (specSizeComplex)); + specReal = reinterpret_cast (ippsMalloc_8u (specSizeReal)); + + // Initialize specifications + ippsFFTInit_C_32fc (&specComplex, order, IPP_FFT_NODIV_BY_ANY, ippAlgHintFast); + ippsFFTInit_R_32f (&specReal, order, IPP_FFT_NODIV_BY_ANY, ippAlgHintFast); + } // Allocate work buffer - const int maxWorkSize = jmax (workSizeComplex, workSizeReal); - workBuffer = reinterpret_cast (ippsMalloc_8u (maxWorkSize)); + workBuffer = reinterpret_cast (ippsMalloc_8u (jmax (workSizeComplex, workSizeReal))); } void cleanup() override @@ -412,50 +509,85 @@ class IPPEngine : public FFTProcessor::Engine ippsFree (workBuffer); workBuffer = nullptr; } + if (specComplex != nullptr) { - ippsFFTFree_C_32fc (specComplex); + if constexpr (std::is_same_v) + ippsFFTFree_C_64fc (specComplex); + else + ippsFFTFree_C_32fc (specComplex); + specComplex = nullptr; } + if (specReal != nullptr) { - ippsFFTFree_R_32f (specReal); + if constexpr (std::is_same_v) + ippsFFTFree_R_64f (specReal); + else + ippsFFTFree_R_32f (specReal); + specReal = nullptr; } } - void performRealFFTForward (const float* realInput, float* complexOutput) override + void performRealFFTForward (const SampleType* realInput, SampleType* complexOutput) override { - ippsFFTFwd_RToPack_32f (realInput, complexOutput, specReal, reinterpret_cast (workBuffer)); + if constexpr (std::is_same_v) + ippsFFTFwd_RToPack_64f (realInput, complexOutput, specReal, workBuffer); + else + ippsFFTFwd_RToPack_32f (realInput, complexOutput, specReal, workBuffer); } - void performRealFFTInverse (const float* complexInput, float* realOutput) override + void performRealFFTInverse (const SampleType* complexInput, SampleType* realOutput) override { - ippsFFTInv_PackToR_32f (complexInput, realOutput, specReal, reinterpret_cast (workBuffer)); + if constexpr (std::is_same_v) + ippsFFTInv_PackToR_64f (complexInput, realOutput, specReal, workBuffer); + else + ippsFFTInv_PackToR_32f (complexInput, realOutput, specReal, workBuffer); } - void performComplexFFTForward (const float* complexInput, float* complexOutput) override + void performComplexFFTForward (const SampleType* complexInput, SampleType* complexOutput) override { - const auto* input = reinterpret_cast (complexInput); - auto* output = reinterpret_cast (complexOutput); - - ippsFFTFwd_CToC_32fc (input, output, specComplex, reinterpret_cast (workBuffer)); + if constexpr (std::is_same_v) + { + const auto* input = reinterpret_cast (complexInput); + auto* output = reinterpret_cast (complexOutput); + ippsFFTFwd_CToC_64fc (input, output, specComplex, workBuffer); + } + else + { + const auto* input = reinterpret_cast (complexInput); + auto* output = reinterpret_cast (complexOutput); + ippsFFTFwd_CToC_32fc (input, output, specComplex, workBuffer); + } } - void performComplexFFTInverse (const float* complexInput, float* complexOutput) override + void performComplexFFTInverse (const SampleType* complexInput, SampleType* complexOutput) override { - const auto* input = reinterpret_cast (complexInput); - auto* output = reinterpret_cast (complexOutput); - - ippsFFTInv_CToC_32fc (input, output, specComplex, reinterpret_cast (workBuffer)); + if constexpr (std::is_same_v) + { + const auto* input = reinterpret_cast (complexInput); + auto* output = reinterpret_cast (complexOutput); + ippsFFTInv_CToC_64fc (input, output, specComplex, workBuffer); + } + else + { + const auto* input = reinterpret_cast (complexInput); + auto* output = reinterpret_cast (complexOutput); + ippsFFTInv_CToC_32fc (input, output, specComplex, workBuffer); + } } String getBackendName() const override { return "Intel IPP"; } private: - Ipp32fc* workBuffer = nullptr; - IppsFFTSpec_C_32fc* specComplex = nullptr; - IppsFFTSpec_R_32f* specReal = nullptr; + using SpecComplex = std::conditional_t, IppsFFTSpec_C_64fc, IppsFFTSpec_C_32fc>; + using SpecReal = std::conditional_t, IppsFFTSpec_R_64f, IppsFFTSpec_R_32f>; + + Ipp8u* workBuffer = nullptr; + SpecComplex* specComplex = nullptr; + SpecReal* specReal = nullptr; }; #endif @@ -464,75 +596,73 @@ class IPPEngine : public FFTProcessor::Engine // FFTW3 implementation #if YUP_FFT_USING_FFTW3 -class FFTW3Engine : public FFTProcessor::Engine +template +class FFTW3Engine : public detail::FFTEngine { public: - ~FFTW3Engine() override { cleanup(); } + ~FFTW3Engine() override { this->cleanup(); } void initialize (int newFftSize) override { - cleanup(); - - fftSize = newFftSize; + this->cleanup(); - tempComplexBuffer = static_cast (fftwf_malloc (sizeof (fftwf_complex) * fftSize)); - tempRealBuffer = static_cast (fftwf_malloc (sizeof (float) * fftSize)); + this->fftSize = newFftSize; - auto* complexData = tempComplexBuffer; - auto* realData = tempRealBuffer; + if constexpr (std::is_same_v) + { + tempComplexBuffer = static_cast (fftw_malloc (sizeof (Complex) * static_cast (this->fftSize))); + tempRealBuffer = static_cast (fftw_malloc (sizeof (SampleType) * static_cast (this->fftSize))); - planComplexForward = fftwf_plan_dft_1d (fftSize, complexData, complexData, FFTW_FORWARD, FFTW_ESTIMATE); - planComplexInverse = fftwf_plan_dft_1d (fftSize, complexData, complexData, FFTW_BACKWARD, FFTW_ESTIMATE); - planRealForward = fftwf_plan_dft_r2c_1d (fftSize, realData, complexData, FFTW_ESTIMATE); - planRealInverse = fftwf_plan_dft_c2r_1d (fftSize, complexData, realData, FFTW_ESTIMATE); - } + auto* complexData = tempComplexBuffer; + auto* realData = tempRealBuffer; - void cleanup() override - { - if (planComplexForward != nullptr) - { - fftwf_destroy_plan (planComplexForward); - planComplexForward = nullptr; + planComplexForward = fftw_plan_dft_1d (this->fftSize, complexData, complexData, FFTW_FORWARD, FFTW_ESTIMATE); + planComplexInverse = fftw_plan_dft_1d (this->fftSize, complexData, complexData, FFTW_BACKWARD, FFTW_ESTIMATE); + planRealForward = fftw_plan_dft_r2c_1d (this->fftSize, realData, complexData, FFTW_ESTIMATE); + planRealInverse = fftw_plan_dft_c2r_1d (this->fftSize, complexData, realData, FFTW_ESTIMATE); } - - if (planComplexInverse != nullptr) + else { - fftwf_destroy_plan (planComplexInverse); - planComplexInverse = nullptr; - } + tempComplexBuffer = static_cast (fftwf_malloc (sizeof (Complex) * static_cast (this->fftSize))); + tempRealBuffer = static_cast (fftwf_malloc (sizeof (SampleType) * static_cast (this->fftSize))); - if (planRealForward != nullptr) - { - fftwf_destroy_plan (planRealForward); - planRealForward = nullptr; - } + auto* complexData = tempComplexBuffer; + auto* realData = tempRealBuffer; - if (planRealInverse != nullptr) - { - fftwf_destroy_plan (planRealInverse); - planRealInverse = nullptr; + planComplexForward = fftwf_plan_dft_1d (this->fftSize, complexData, complexData, FFTW_FORWARD, FFTW_ESTIMATE); + planComplexInverse = fftwf_plan_dft_1d (this->fftSize, complexData, complexData, FFTW_BACKWARD, FFTW_ESTIMATE); + planRealForward = fftwf_plan_dft_r2c_1d (this->fftSize, realData, complexData, FFTW_ESTIMATE); + planRealInverse = fftwf_plan_dft_c2r_1d (this->fftSize, complexData, realData, FFTW_ESTIMATE); } + } + + void cleanup() override + { + destroyPlan (planComplexForward); + destroyPlan (planComplexInverse); + destroyPlan (planRealForward); + destroyPlan (planRealInverse); if (tempComplexBuffer != nullptr) { - fftwf_free (tempComplexBuffer); + freeBuffer (tempComplexBuffer); tempComplexBuffer = nullptr; } if (tempRealBuffer != nullptr) { - fftwf_free (tempRealBuffer); + freeBuffer (tempRealBuffer); tempRealBuffer = nullptr; } } - void performRealFFTForward (const float* realInput, float* complexOutput) override + void performRealFFTForward (const SampleType* realInput, SampleType* complexOutput) override { - std::copy_n (realInput, fftSize, tempRealBuffer); + std::copy_n (realInput, this->fftSize, tempRealBuffer); - fftwf_execute (planRealForward); + execute (planRealForward); - const auto halfSize = fftSize / 2 + 1; + const auto halfSize = this->fftSize / 2 + 1; for (int i = 0; i < halfSize; ++i) { complexOutput[i * 2] = tempComplexBuffer[i][0]; // real @@ -540,49 +670,49 @@ class FFTW3Engine : public FFTProcessor::Engine } } - void performRealFFTInverse (const float* complexInput, float* realOutput) override + void performRealFFTInverse (const SampleType* complexInput, SampleType* realOutput) override { // Convert interleaved to FFTW format - const auto halfSize = fftSize / 2 + 1; + const auto halfSize = this->fftSize / 2 + 1; for (int i = 0; i < halfSize; ++i) { tempComplexBuffer[i][0] = complexInput[i * 2]; // real tempComplexBuffer[i][1] = complexInput[i * 2 + 1]; // imag } - fftwf_execute (planRealInverse); + execute (planRealInverse); - std::copy_n (tempRealBuffer, fftSize, realOutput); + std::copy_n (tempRealBuffer, this->fftSize, realOutput); } - void performComplexFFTForward (const float* complexInput, float* complexOutput) override + void performComplexFFTForward (const SampleType* complexInput, SampleType* complexOutput) override { - for (int i = 0; i < fftSize; ++i) + for (int i = 0; i < this->fftSize; ++i) { tempComplexBuffer[i][0] = complexInput[i * 2]; // real tempComplexBuffer[i][1] = complexInput[i * 2 + 1]; // imag } - fftwf_execute (planComplexForward); + execute (planComplexForward); - for (int i = 0; i < fftSize; ++i) + for (int i = 0; i < this->fftSize; ++i) { complexOutput[i * 2] = tempComplexBuffer[i][0]; // real complexOutput[i * 2 + 1] = tempComplexBuffer[i][1]; // imag } } - void performComplexFFTInverse (const float* complexInput, float* complexOutput) override + void performComplexFFTInverse (const SampleType* complexInput, SampleType* complexOutput) override { - for (int i = 0; i < fftSize; ++i) + for (int i = 0; i < this->fftSize; ++i) { tempComplexBuffer[i][0] = complexInput[i * 2]; // real tempComplexBuffer[i][1] = complexInput[i * 2 + 1]; // imag } - fftwf_execute (planComplexInverse); + execute (planComplexInverse); - for (int i = 0; i < fftSize; ++i) + for (int i = 0; i < this->fftSize; ++i) { complexOutput[i * 2] = tempComplexBuffer[i][0]; // real complexOutput[i * 2 + 1] = tempComplexBuffer[i][1]; // imag @@ -592,30 +722,64 @@ class FFTW3Engine : public FFTProcessor::Engine String getBackendName() const override { return "FFTW3"; } private: - fftwf_plan planComplexForward = nullptr; - fftwf_plan planComplexInverse = nullptr; - fftwf_plan planRealForward = nullptr; - fftwf_plan planRealInverse = nullptr; - fftwf_complex* tempComplexBuffer = nullptr; - float* tempRealBuffer = nullptr; + using Complex = std::conditional_t, fftw_complex, fftwf_complex>; + using Plan = std::conditional_t, fftw_plan, fftwf_plan>; + + void execute (Plan plan) + { + if constexpr (std::is_same_v) + fftw_execute (plan); + else + fftwf_execute (plan); + } + + void destroyPlan (Plan& plan) + { + if (plan == nullptr) + return; + + if constexpr (std::is_same_v) + fftw_destroy_plan (plan); + else + fftwf_destroy_plan (plan); + + plan = nullptr; + } + + template + void freeBuffer (BufferType*& buffer) + { + if constexpr (std::is_same_v) + fftw_free (buffer); + else + fftwf_free (buffer); + } + + Plan planComplexForward = nullptr; + Plan planComplexInverse = nullptr; + Plan planRealForward = nullptr; + Plan planRealInverse = nullptr; + Complex* tempComplexBuffer = nullptr; + SampleType* tempRealBuffer = nullptr; }; #endif //============================================================================== // Factory function to create appropriate implementation -std::unique_ptr createFFTEngine() +template +std::unique_ptr> createFFTEngine() { #if YUP_FFT_USING_PFFFT - return std::make_unique(); + return std::make_unique>(); #elif YUP_FFT_USING_VDSP - return std::make_unique(); + return std::make_unique>(); #elif YUP_FFT_USING_IPP - return std::make_unique(); + return std::make_unique>(); #elif YUP_FFT_USING_FFTW3 - return std::make_unique(); + return std::make_unique>(); #elif YUP_FFT_USING_OOURA - return std::make_unique(); + return std::make_unique>(); #else jassertfalse; // No FFT backend available return nullptr; @@ -624,32 +788,38 @@ std::unique_ptr createFFTEngine() //============================================================================== // Constructor implementations -FFTProcessor::FFTProcessor() - : engine (createFFTEngine()) +template +FFTProcessor::FFTProcessor() + : engine (createFFTEngine()) { setSize (512); } -FFTProcessor::FFTProcessor (int fftSize) - : engine (createFFTEngine()) +template +FFTProcessor::FFTProcessor (int fftSize) + : engine (createFFTEngine()) { setSize (fftSize); } -FFTProcessor::~FFTProcessor() +template +FFTProcessor::~FFTProcessor() { if (engine) engine->cleanup(); } -FFTProcessor::FFTProcessor (FFTProcessor&& other) noexcept +template +FFTProcessor::FFTProcessor (FFTProcessor&& other) noexcept : fftSize (std::exchange (other.fftSize, 0)) , scaling (other.scaling) + , scalingFactor (other.scalingFactor) , engine (std::move (other.engine)) { } -FFTProcessor& FFTProcessor::operator= (FFTProcessor&& other) noexcept +template +FFTProcessor& FFTProcessor::operator= (FFTProcessor&& other) noexcept { if (this != &other) { @@ -658,6 +828,7 @@ FFTProcessor& FFTProcessor::operator= (FFTProcessor&& other) noexcept fftSize = std::exchange (other.fftSize, 0); scaling = other.scaling; + scalingFactor = other.scalingFactor; engine = std::move (other.engine); } @@ -666,7 +837,8 @@ FFTProcessor& FFTProcessor::operator= (FFTProcessor&& other) noexcept //============================================================================== -void FFTProcessor::setScaling (FFTScaling newScaling) noexcept +template +void FFTProcessor::setScaling (FFTScaling newScaling) noexcept { if (scaling != newScaling) { @@ -676,7 +848,8 @@ void FFTProcessor::setScaling (FFTScaling newScaling) noexcept } } -void FFTProcessor::setSize (int newSize) +template +void FFTProcessor::setSize (int newSize) { jassert (isPowerOfTwo (newSize) && newSize >= 64 && newSize <= 65536); @@ -691,7 +864,8 @@ void FFTProcessor::setSize (int newSize) } } -void FFTProcessor::performRealFFTForward (const float* realInput, float* complexOutput) +template +void FFTProcessor::performRealFFTForward (const SampleType* realInput, SampleType* complexOutput) { jassert (realInput != nullptr && complexOutput != nullptr); jassert (engine != nullptr); @@ -701,7 +875,8 @@ void FFTProcessor::performRealFFTForward (const float* realInput, float* complex applyScaling (complexOutput, fftSize * 2, true); } -void FFTProcessor::performRealFFTInverse (const float* complexInput, float* realOutput) +template +void FFTProcessor::performRealFFTInverse (const SampleType* complexInput, SampleType* realOutput) { jassert (complexInput != nullptr && realOutput != nullptr); jassert (engine != nullptr); @@ -711,7 +886,8 @@ void FFTProcessor::performRealFFTInverse (const float* complexInput, float* real applyScaling (realOutput, fftSize, false); } -void FFTProcessor::performComplexFFTForward (const float* complexInput, float* complexOutput) +template +void FFTProcessor::performComplexFFTForward (const SampleType* complexInput, SampleType* complexOutput) { jassert (complexInput != nullptr && complexOutput != nullptr); jassert (engine != nullptr); @@ -721,7 +897,8 @@ void FFTProcessor::performComplexFFTForward (const float* complexInput, float* c applyScaling (complexOutput, fftSize * 2, true); } -void FFTProcessor::performComplexFFTInverse (const float* complexInput, float* complexOutput) +template +void FFTProcessor::performComplexFFTInverse (const SampleType* complexInput, SampleType* complexOutput) { jassert (complexInput != nullptr && complexOutput != nullptr); jassert (engine != nullptr); @@ -731,31 +908,39 @@ void FFTProcessor::performComplexFFTInverse (const float* complexInput, float* c applyScaling (complexOutput, fftSize * 2, false); } -String FFTProcessor::getBackendName() const +template +String FFTProcessor::getBackendName() const { return engine != nullptr ? engine->getBackendName() : "Unknown"; } //============================================================================== -void FFTProcessor::updateScalingFactor() +template +void FFTProcessor::updateScalingFactor() { if (scaling == FFTScaling::unitary) - scalingFactor = 1.0f / std::sqrt (static_cast (fftSize)); + scalingFactor = SampleType (1) / std::sqrt (static_cast (fftSize)); else if (scaling == FFTScaling::asymmetric) - scalingFactor = 1.0f / static_cast (fftSize); + scalingFactor = SampleType (1) / static_cast (fftSize); else - scalingFactor = 1.0f; + scalingFactor = SampleType (1); } -void FFTProcessor::applyScaling (float* data, int numElements, bool isForward) const +template +void FFTProcessor::applyScaling (SampleType* data, int numElements, bool isForward) const { if (scaling == FFTScaling::none || (scaling == FFTScaling::asymmetric && ! isForward)) return; - FloatVectorOperations::multiply (data, scalingFactor, numElements); + FloatVectorOperationsBase::multiply (data, scalingFactor, numElements); } +//============================================================================== + +template class FFTProcessor; +template class FFTProcessor; + } // namespace yup diff --git a/modules/yup_dsp/frequency/yup_FFTProcessor.h b/modules/yup_dsp/frequency/yup_FFTProcessor.h index 10ab39cd5..7b905e88c 100644 --- a/modules/yup_dsp/frequency/yup_FFTProcessor.h +++ b/modules/yup_dsp/frequency/yup_FFTProcessor.h @@ -24,6 +24,17 @@ namespace yup { +#ifndef DOXYGEN +namespace detail +{ + +/** @internal Abstract interface implemented by every FFT backend. */ +template +class FFTEngine; + +} // namespace detail +#endif + //============================================================================== /** Multi-backend FFT processor that provides a unified interface for different @@ -39,11 +50,16 @@ namespace yup The class automatically selects the best available backend at compile time based on preprocessor definitions and platform availability. - @note This class only works with float buffers for optimal performance. + The processing precision is selected with the @c SampleType template + argument: use @c float for the fastest transforms or @c double for higher + precision. Backends offering native support for both precisions switch their + internal implementation accordingly. + + @tparam SampleType The sample type used by all transform buffers (float or double). Example usage: @code - FFTProcessor fft (512); // 512-point FFT + FFTProcessor fft (512); // 512-point FFT std::vector realInput (512); std::vector complexOutput (1024); // 512 complex pairs = 1024 floats @@ -52,7 +68,10 @@ namespace yup fft.performRealFFTForward (realInput.data(), complexOutput.data()); @endcode + + @see SpectrumAnalyzerState, WindowFunctions */ +template class FFTProcessor { public: @@ -101,7 +120,7 @@ class FFTProcessor @param realInput Input buffer containing real samples (fftSize elements) @param complexOutput Output buffer for complex data (fftSize * 2 elements, interleaved real/imag) */ - void performRealFFTForward (const float* realInput, float* complexOutput); + void performRealFFTForward (const SampleType* realInput, SampleType* complexOutput); /** Performs an inverse complex-to-real FFT. @@ -109,7 +128,7 @@ class FFTProcessor @param complexInput Input buffer containing complex data (fftSize * 2 elements, interleaved real/imag) @param realOutput Output buffer for real data (fftSize elements) */ - void performRealFFTInverse (const float* complexInput, float* realOutput); + void performRealFFTInverse (const SampleType* complexInput, SampleType* realOutput); /** Performs a forward complex-to-complex FFT. @@ -117,7 +136,7 @@ class FFTProcessor @param complexInput Input buffer containing complex data (fftSize * 2 elements, interleaved real/imag) @param complexOutput Output buffer for complex data (fftSize * 2 elements, interleaved real/imag) */ - void performComplexFFTForward (const float* complexInput, float* complexOutput); + void performComplexFFTForward (const SampleType* complexInput, SampleType* complexOutput); /** Performs an inverse complex-to-complex FFT. @@ -125,7 +144,7 @@ class FFTProcessor @param complexInput Input buffer containing complex data (fftSize * 2 elements, interleaved real/imag) @param complexOutput Output buffer for complex data (fftSize * 2 elements, interleaved real/imag) */ - void performComplexFFTInverse (const float* complexInput, float* complexOutput); + void performComplexFFTInverse (const SampleType* complexInput, SampleType* complexOutput); //============================================================================== /** Returns a string describing the active FFT backend */ @@ -134,18 +153,18 @@ class FFTProcessor //============================================================================== #ifndef DOXYGEN /** @internal */ - class Engine; + using Engine = detail::FFTEngine; #endif private: //============================================================================== void updateScalingFactor(); - void applyScaling (float* data, int numElements, bool isForward) const; + void applyScaling (SampleType* data, int numElements, bool isForward) const; //============================================================================== int fftSize = -1; FFTScaling scaling = FFTScaling::none; - float scalingFactor = 1.0f; + SampleType scalingFactor = SampleType (1); std::unique_ptr engine; diff --git a/modules/yup_dsp/frequency/yup_OouraFFT8g.h b/modules/yup_dsp/frequency/yup_OouraFFT8g.h index 39333defb..37502e5a7 100644 --- a/modules/yup_dsp/frequency/yup_OouraFFT8g.h +++ b/modules/yup_dsp/frequency/yup_OouraFFT8g.h @@ -39,4 +39,11 @@ void ddst (int n, int isgn, float* a, int* ip, float* w); void dfct (int n, float* a, float* t, int* ip, float* w); void dfst (int n, float* a, float* t, int* ip, float* w); -} // namespace yup \ No newline at end of file +void cdft (int n, int isgn, double* a, int* ip, double* w); +void rdft (int n, int isgn, double* a, int* ip, double* w); +void ddct (int n, int isgn, double* a, int* ip, double* w); +void ddst (int n, int isgn, double* a, int* ip, double* w); +void dfct (int n, double* a, double* t, int* ip, double* w); +void dfst (int n, double* a, double* t, int* ip, double* w); + +} // namespace yup diff --git a/modules/yup_dsp/frequency/yup_OouraFFT8g_double.cpp b/modules/yup_dsp/frequency/yup_OouraFFT8g_double.cpp new file mode 100644 index 000000000..fa2fd4d7f --- /dev/null +++ b/modules/yup_dsp/frequency/yup_OouraFFT8g_double.cpp @@ -0,0 +1,3480 @@ +/* + ============================================================================== + + This file is part of the YUP library. + Copyright (c) 2025 - kunitoki@gmail.com + + YUP is an open source library subject to open-source licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== + + Copyright(C) 1996-2001 Takuya OOURA + email: ooura@mmm.t.u-tokyo.ac.jp + download: http://momonga.t.u-tokyo.ac.jp/~ooura/fft.html + You may use, copy, modify this code for any purpose and + without fee. You may distribute this ORIGINAL package. + + ============================================================================== +*/ + +/* +Fast Fourier/Cosine/Sine Transform + dimension :one + data length :power of 2 + decimation :frequency + radix :split-radix + data :inplace + table :use +functions + cdft: Complex Discrete Fourier Transform + rdft: Real Discrete Fourier Transform + ddct: Discrete Cosine Transform + ddst: Discrete Sine Transform + dfct: Cosine Transform of RDFT (Real Symmetric DFT) + dfst: Sine Transform of RDFT (Real Anti-symmetric DFT) +function prototypes + void cdft(int, int, double *, int *, double *); + void rdft(int, int, double *, int *, double *); + void ddct(int, int, double *, int *, double *); + void ddst(int, int, double *, int *, double *); + void dfct(int, double *, double *, int *, double *); + void dfst(int, double *, double *, int *, double *); +macro definitions + USE_CDFT_PTHREADS : default=not defined + CDFT_THREADS_BEGIN_N : must be >= 512, default=8192 + CDFT_4THREADS_BEGIN_N : must be >= 512, default=65536 + USE_CDFT_WINTHREADS : default=not defined + CDFT_THREADS_BEGIN_N : must be >= 512, default=32768 + CDFT_4THREADS_BEGIN_N : must be >= 512, default=524288 + + +-------- Complex DFT (Discrete Fourier Transform) -------- + [definition] + + X[k] = sum_j=0^n-1 x[j]*exp(2*pi*i*j*k/n), 0<=k + X[k] = sum_j=0^n-1 x[j]*exp(-2*pi*i*j*k/n), 0<=k + ip[0] = 0; // first time only + cdft(2*n, 1, a, ip, w); + + ip[0] = 0; // first time only + cdft(2*n, -1, a, ip, w); + [parameters] + 2*n :data length (int) + n >= 1, n = power of 2 + a[0...2*n-1] :input/output data (double *) + input data + a[2*j] = Re(x[j]), + a[2*j+1] = Im(x[j]), 0<=j= 2+sqrt(n) + strictly, + length of ip >= + 2+(1<<(int)(log(n+0.5)/log(2))/2). + ip[0],ip[1] are pointers of the cos/sin table. + w[0...n/2-1] :cos/sin table (double *) + w[],ip[] are initialized if ip[0] == 0. + [remark] + Inverse of + cdft(2*n, -1, a, ip, w); + is + cdft(2*n, 1, a, ip, w); + for (j = 0; j <= 2 * n - 1; j++) { + a[j] *= 1.0 / n; + } + . + + +-------- Real DFT / Inverse of Real DFT -------- + [definition] + RDFT + R[k] = sum_j=0^n-1 a[j]*cos(2*pi*j*k/n), 0<=k<=n/2 + I[k] = sum_j=0^n-1 a[j]*sin(2*pi*j*k/n), 0 IRDFT (excluding scale) + a[k] = (R[0] + R[n/2]*cos(pi*k))/2 + + sum_j=1^n/2-1 R[j]*cos(2*pi*j*k/n) + + sum_j=1^n/2-1 I[j]*sin(2*pi*j*k/n), 0<=k + ip[0] = 0; // first time only + rdft(n, 1, a, ip, w); + + ip[0] = 0; // first time only + rdft(n, -1, a, ip, w); + [parameters] + n :data length (int) + n >= 2, n = power of 2 + a[0...n-1] :input/output data (double *) + + output data + a[2*k] = R[k], 0<=k + input data + a[2*j] = R[j], 0<=j= 2+sqrt(n/2) + strictly, + length of ip >= + 2+(1<<(int)(log(n/2+0.5)/log(2))/2). + ip[0],ip[1] are pointers of the cos/sin table. + w[0...n/2-1] :cos/sin table (double *) + w[],ip[] are initialized if ip[0] == 0. + [remark] + Inverse of + rdft(n, 1, a, ip, w); + is + rdft(n, -1, a, ip, w); + for (j = 0; j <= n - 1; j++) { + a[j] *= 2.0 / n; + } + . + + +-------- DCT (Discrete Cosine Transform) / Inverse of DCT -------- + [definition] + IDCT (excluding scale) + C[k] = sum_j=0^n-1 a[j]*cos(pi*j*(k+1/2)/n), 0<=k DCT + C[k] = sum_j=0^n-1 a[j]*cos(pi*(j+1/2)*k/n), 0<=k + ip[0] = 0; // first time only + ddct(n, 1, a, ip, w); + + ip[0] = 0; // first time only + ddct(n, -1, a, ip, w); + [parameters] + n :data length (int) + n >= 2, n = power of 2 + a[0...n-1] :input/output data (double *) + output data + a[k] = C[k], 0<=k= 2+sqrt(n/2) + strictly, + length of ip >= + 2+(1<<(int)(log(n/2+0.5)/log(2))/2). + ip[0],ip[1] are pointers of the cos/sin table. + w[0...n*5/4-1] :cos/sin table (double *) + w[],ip[] are initialized if ip[0] == 0. + [remark] + Inverse of + ddct(n, -1, a, ip, w); + is + a[0] *= 0.5; + ddct(n, 1, a, ip, w); + for (j = 0; j <= n - 1; j++) { + a[j] *= 2.0 / n; + } + . + + +-------- DST (Discrete Sine Transform) / Inverse of DST -------- + [definition] + IDST (excluding scale) + S[k] = sum_j=1^n A[j]*sin(pi*j*(k+1/2)/n), 0<=k DST + S[k] = sum_j=0^n-1 a[j]*sin(pi*(j+1/2)*k/n), 0 + ip[0] = 0; // first time only + ddst(n, 1, a, ip, w); + + ip[0] = 0; // first time only + ddst(n, -1, a, ip, w); + [parameters] + n :data length (int) + n >= 2, n = power of 2 + a[0...n-1] :input/output data (double *) + + input data + a[j] = A[j], 0 + output data + a[k] = S[k], 0= 2+sqrt(n/2) + strictly, + length of ip >= + 2+(1<<(int)(log(n/2+0.5)/log(2))/2). + ip[0],ip[1] are pointers of the cos/sin table. + w[0...n*5/4-1] :cos/sin table (double *) + w[],ip[] are initialized if ip[0] == 0. + [remark] + Inverse of + ddst(n, -1, a, ip, w); + is + a[0] *= 0.5; + ddst(n, 1, a, ip, w); + for (j = 0; j <= n - 1; j++) { + a[j] *= 2.0 / n; + } + . + + +-------- Cosine Transform of RDFT (Real Symmetric DFT) -------- + [definition] + C[k] = sum_j=0^n a[j]*cos(pi*j*k/n), 0<=k<=n + [usage] + ip[0] = 0; // first time only + dfct(n, a, t, ip, w); + [parameters] + n :data length - 1 (int) + n >= 2, n = power of 2 + a[0...n] :input/output data (double *) + output data + a[k] = C[k], 0<=k<=n + t[0...n/2] :work area (double *) + ip[0...*] :work area for bit reversal (int *) + length of ip >= 2+sqrt(n/4) + strictly, + length of ip >= + 2+(1<<(int)(log(n/4+0.5)/log(2))/2). + ip[0],ip[1] are pointers of the cos/sin table. + w[0...n*5/8-1] :cos/sin table (double *) + w[],ip[] are initialized if ip[0] == 0. + [remark] + Inverse of + a[0] *= 0.5; + a[n] *= 0.5; + dfct(n, a, t, ip, w); + is + a[0] *= 0.5; + a[n] *= 0.5; + dfct(n, a, t, ip, w); + for (j = 0; j <= n; j++) { + a[j] *= 2.0 / n; + } + . + + +-------- Sine Transform of RDFT (Real Anti-symmetric DFT) -------- + [definition] + S[k] = sum_j=1^n-1 a[j]*sin(pi*j*k/n), 0= 2, n = power of 2 + a[0...n-1] :input/output data (double *) + output data + a[k] = S[k], 0= 2+sqrt(n/4) + strictly, + length of ip >= + 2+(1<<(int)(log(n/4+0.5)/log(2))/2). + ip[0],ip[1] are pointers of the cos/sin table. + w[0...n*5/8-1] :cos/sin table (double *) + w[],ip[] are initialized if ip[0] == 0. + [remark] + Inverse of + dfst(n, a, t, ip, w); + is + dfst(n, a, t, ip, w); + for (j = 1; j <= n - 1; j++) { + a[j] *= 2.0 / n; + } + . + + +Appendix : + The cos/sin table is recalculated when the larger table required. + w[] and ip[] are compatible with all routines. +*/ + +namespace yup +{ + +void cdft (int n, int isgn, double* a, int* ip, double* w) +{ + void makewt (int nw, int* ip, double* w); + void cftfsub (int n, double* a, int* ip, int nw, double* w); + void cftbsub (int n, double* a, int* ip, int nw, double* w); + int nw; + + nw = ip[0]; + if (n > (nw << 2)) + { + nw = n >> 2; + makewt (nw, ip, w); + } + if (isgn >= 0) + { + cftfsub (n, a, ip, nw, w); + } + else + { + cftbsub (n, a, ip, nw, w); + } +} + +void rdft (int n, int isgn, double* a, int* ip, double* w) +{ + void makewt (int nw, int* ip, double* w); + void makect (int nc, int* ip, double* c); + void cftfsub (int n, double* a, int* ip, int nw, double* w); + void cftbsub (int n, double* a, int* ip, int nw, double* w); + void rftfsub (int n, double* a, int nc, double* c); + void rftbsub (int n, double* a, int nc, double* c); + int nw, nc; + double xi; + + nw = ip[0]; + if (n > (nw << 2)) + { + nw = n >> 2; + makewt (nw, ip, w); + } + nc = ip[1]; + if (n > (nc << 2)) + { + nc = n >> 2; + makect (nc, ip, w + nw); + } + if (isgn >= 0) + { + if (n > 4) + { + cftfsub (n, a, ip, nw, w); + rftfsub (n, a, nc, w + nw); + } + else if (n == 4) + { + cftfsub (n, a, ip, nw, w); + } + xi = a[0] - a[1]; + a[0] += a[1]; + a[1] = xi; + } + else + { + a[1] = 0.5 * (a[0] - a[1]); + a[0] -= a[1]; + if (n > 4) + { + rftbsub (n, a, nc, w + nw); + cftbsub (n, a, ip, nw, w); + } + else if (n == 4) + { + cftbsub (n, a, ip, nw, w); + } + } +} + +void ddct (int n, int isgn, double* a, int* ip, double* w) +{ + void makewt (int nw, int* ip, double* w); + void makect (int nc, int* ip, double* c); + void cftfsub (int n, double* a, int* ip, int nw, double* w); + void cftbsub (int n, double* a, int* ip, int nw, double* w); + void rftfsub (int n, double* a, int nc, double* c); + void rftbsub (int n, double* a, int nc, double* c); + void dctsub (int n, double* a, int nc, double* c); + int j, nw, nc; + double xr; + + nw = ip[0]; + if (n > (nw << 2)) + { + nw = n >> 2; + makewt (nw, ip, w); + } + nc = ip[1]; + if (n > nc) + { + nc = n; + makect (nc, ip, w + nw); + } + if (isgn < 0) + { + xr = a[n - 1]; + for (j = n - 2; j >= 2; j -= 2) + { + a[j + 1] = a[j] - a[j - 1]; + a[j] += a[j - 1]; + } + a[1] = a[0] - xr; + a[0] += xr; + if (n > 4) + { + rftbsub (n, a, nc, w + nw); + cftbsub (n, a, ip, nw, w); + } + else if (n == 4) + { + cftbsub (n, a, ip, nw, w); + } + } + dctsub (n, a, nc, w + nw); + if (isgn >= 0) + { + if (n > 4) + { + cftfsub (n, a, ip, nw, w); + rftfsub (n, a, nc, w + nw); + } + else if (n == 4) + { + cftfsub (n, a, ip, nw, w); + } + xr = a[0] - a[1]; + a[0] += a[1]; + for (j = 2; j < n; j += 2) + { + a[j - 1] = a[j] - a[j + 1]; + a[j] += a[j + 1]; + } + a[n - 1] = xr; + } +} + +void ddst (int n, int isgn, double* a, int* ip, double* w) +{ + void makewt (int nw, int* ip, double* w); + void makect (int nc, int* ip, double* c); + void cftfsub (int n, double* a, int* ip, int nw, double* w); + void cftbsub (int n, double* a, int* ip, int nw, double* w); + void rftfsub (int n, double* a, int nc, double* c); + void rftbsub (int n, double* a, int nc, double* c); + void dstsub (int n, double* a, int nc, double* c); + int j, nw, nc; + double xr; + + nw = ip[0]; + if (n > (nw << 2)) + { + nw = n >> 2; + makewt (nw, ip, w); + } + nc = ip[1]; + if (n > nc) + { + nc = n; + makect (nc, ip, w + nw); + } + if (isgn < 0) + { + xr = a[n - 1]; + for (j = n - 2; j >= 2; j -= 2) + { + a[j + 1] = -a[j] - a[j - 1]; + a[j] -= a[j - 1]; + } + a[1] = a[0] + xr; + a[0] -= xr; + if (n > 4) + { + rftbsub (n, a, nc, w + nw); + cftbsub (n, a, ip, nw, w); + } + else if (n == 4) + { + cftbsub (n, a, ip, nw, w); + } + } + dstsub (n, a, nc, w + nw); + if (isgn >= 0) + { + if (n > 4) + { + cftfsub (n, a, ip, nw, w); + rftfsub (n, a, nc, w + nw); + } + else if (n == 4) + { + cftfsub (n, a, ip, nw, w); + } + xr = a[0] - a[1]; + a[0] += a[1]; + for (j = 2; j < n; j += 2) + { + a[j - 1] = -a[j] - a[j + 1]; + a[j] -= a[j + 1]; + } + a[n - 1] = -xr; + } +} + +void dfct (int n, double* a, double* t, int* ip, double* w) +{ + void makewt (int nw, int* ip, double* w); + void makect (int nc, int* ip, double* c); + void cftfsub (int n, double* a, int* ip, int nw, double* w); + void rftfsub (int n, double* a, int nc, double* c); + void dctsub (int n, double* a, int nc, double* c); + int j, k, l, m, mh, nw, nc; + double xr, xi, yr, yi; + + nw = ip[0]; + if (n > (nw << 3)) + { + nw = n >> 3; + makewt (nw, ip, w); + } + nc = ip[1]; + if (n > (nc << 1)) + { + nc = n >> 1; + makect (nc, ip, w + nw); + } + m = n >> 1; + yi = a[m]; + xi = a[0] + a[n]; + a[0] -= a[n]; + t[0] = xi - yi; + t[m] = xi + yi; + if (n > 2) + { + mh = m >> 1; + for (j = 1; j < mh; j++) + { + k = m - j; + xr = a[j] - a[n - j]; + xi = a[j] + a[n - j]; + yr = a[k] - a[n - k]; + yi = a[k] + a[n - k]; + a[j] = xr; + a[k] = yr; + t[j] = xi - yi; + t[k] = xi + yi; + } + t[mh] = a[mh] + a[n - mh]; + a[mh] -= a[n - mh]; + dctsub (m, a, nc, w + nw); + if (m > 4) + { + cftfsub (m, a, ip, nw, w); + rftfsub (m, a, nc, w + nw); + } + else if (m == 4) + { + cftfsub (m, a, ip, nw, w); + } + a[n - 1] = a[0] - a[1]; + a[1] = a[0] + a[1]; + for (j = m - 2; j >= 2; j -= 2) + { + a[2 * j + 1] = a[j] + a[j + 1]; + a[2 * j - 1] = a[j] - a[j + 1]; + } + l = 2; + m = mh; + while (m >= 2) + { + dctsub (m, t, nc, w + nw); + if (m > 4) + { + cftfsub (m, t, ip, nw, w); + rftfsub (m, t, nc, w + nw); + } + else if (m == 4) + { + cftfsub (m, t, ip, nw, w); + } + a[n - l] = t[0] - t[1]; + a[l] = t[0] + t[1]; + k = 0; + for (j = 2; j < m; j += 2) + { + k += l << 2; + a[k - l] = t[j] - t[j + 1]; + a[k + l] = t[j] + t[j + 1]; + } + l <<= 1; + mh = m >> 1; + for (j = 0; j < mh; j++) + { + k = m - j; + t[j] = t[m + k] - t[m + j]; + t[k] = t[m + k] + t[m + j]; + } + t[mh] = t[m + mh]; + m = mh; + } + a[l] = t[0]; + a[n] = t[2] - t[1]; + a[0] = t[2] + t[1]; + } + else + { + a[1] = a[0]; + a[2] = t[0]; + a[0] = t[1]; + } +} + +void dfst (int n, double* a, double* t, int* ip, double* w) +{ + void makewt (int nw, int* ip, double* w); + void makect (int nc, int* ip, double* c); + void cftfsub (int n, double* a, int* ip, int nw, double* w); + void rftfsub (int n, double* a, int nc, double* c); + void dstsub (int n, double* a, int nc, double* c); + int j, k, l, m, mh, nw, nc; + double xr, xi, yr, yi; + + nw = ip[0]; + if (n > (nw << 3)) + { + nw = n >> 3; + makewt (nw, ip, w); + } + nc = ip[1]; + if (n > (nc << 1)) + { + nc = n >> 1; + makect (nc, ip, w + nw); + } + if (n > 2) + { + m = n >> 1; + mh = m >> 1; + for (j = 1; j < mh; j++) + { + k = m - j; + xr = a[j] + a[n - j]; + xi = a[j] - a[n - j]; + yr = a[k] + a[n - k]; + yi = a[k] - a[n - k]; + a[j] = xr; + a[k] = yr; + t[j] = xi + yi; + t[k] = xi - yi; + } + t[0] = a[mh] - a[n - mh]; + a[mh] += a[n - mh]; + a[0] = a[m]; + dstsub (m, a, nc, w + nw); + if (m > 4) + { + cftfsub (m, a, ip, nw, w); + rftfsub (m, a, nc, w + nw); + } + else if (m == 4) + { + cftfsub (m, a, ip, nw, w); + } + a[n - 1] = a[1] - a[0]; + a[1] = a[0] + a[1]; + for (j = m - 2; j >= 2; j -= 2) + { + a[2 * j + 1] = a[j] - a[j + 1]; + a[2 * j - 1] = -a[j] - a[j + 1]; + } + l = 2; + m = mh; + while (m >= 2) + { + dstsub (m, t, nc, w + nw); + if (m > 4) + { + cftfsub (m, t, ip, nw, w); + rftfsub (m, t, nc, w + nw); + } + else if (m == 4) + { + cftfsub (m, t, ip, nw, w); + } + a[n - l] = t[1] - t[0]; + a[l] = t[0] + t[1]; + k = 0; + for (j = 2; j < m; j += 2) + { + k += l << 2; + a[k - l] = -t[j] - t[j + 1]; + a[k + l] = t[j] - t[j + 1]; + } + l <<= 1; + mh = m >> 1; + for (j = 1; j < mh; j++) + { + k = m - j; + t[j] = t[m + k] + t[m + j]; + t[k] = t[m + k] - t[m + j]; + } + t[0] = t[m + mh]; + m = mh; + } + a[l] = t[0]; + } + a[0] = 0; +} + +/* -------- initializing routines -------- */ + +void makewt (int nw, int* ip, double* w) +{ + void makeiptd (int nw, int* ip); + int j, nwh, nw0, nw1; + double delta, wn4r, wk1r, wk1i, wk3r, wk3i; + + ip[0] = nw; + ip[1] = 1; + if (nw > 2) + { + nwh = nw >> 1; + delta = atan (1.0) / nwh; + wn4r = cos (delta * nwh); + w[0] = 1; + w[1] = wn4r; + if (nwh == 4) + { + w[2] = cos (delta * 2); + w[3] = sin (delta * 2); + } + else if (nwh > 4) + { + makeiptd (nw, ip); + w[2] = 0.5 / cos (delta * 2); + w[3] = 0.5 / cos (delta * 6); + for (j = 4; j < nwh; j += 4) + { + w[j] = cos (delta * j); + w[j + 1] = sin (delta * j); + w[j + 2] = cos (3 * delta * j); + w[j + 3] = -sin (3 * delta * j); + } + } + nw0 = 0; + while (nwh > 2) + { + nw1 = nw0 + nwh; + nwh >>= 1; + w[nw1] = 1; + w[nw1 + 1] = wn4r; + if (nwh == 4) + { + wk1r = w[nw0 + 4]; + wk1i = w[nw0 + 5]; + w[nw1 + 2] = wk1r; + w[nw1 + 3] = wk1i; + } + else if (nwh > 4) + { + wk1r = w[nw0 + 4]; + wk3r = w[nw0 + 6]; + w[nw1 + 2] = 0.5 / wk1r; + w[nw1 + 3] = 0.5 / wk3r; + for (j = 4; j < nwh; j += 4) + { + wk1r = w[nw0 + 2 * j]; + wk1i = w[nw0 + 2 * j + 1]; + wk3r = w[nw0 + 2 * j + 2]; + wk3i = w[nw0 + 2 * j + 3]; + w[nw1 + j] = wk1r; + w[nw1 + j + 1] = wk1i; + w[nw1 + j + 2] = wk3r; + w[nw1 + j + 3] = wk3i; + } + } + nw0 = nw1; + } + } +} + +void makeiptd (int nw, int* ip) +{ + int j, l, m, m2, p, q; + + ip[2] = 0; + ip[3] = 16; + m = 2; + for (l = nw; l > 32; l >>= 2) + { + m2 = m << 1; + q = m2 << 3; + for (j = m; j < m2; j++) + { + p = ip[j] << 2; + ip[m + j] = p; + ip[m2 + j] = p + q; + } + m = m2; + } +} + +void makect (int nc, int* ip, double* c) +{ + int j, nch; + double delta; + + ip[1] = nc; + if (nc > 1) + { + nch = nc >> 1; + delta = atan (1.0) / nch; + c[0] = cos (delta * nch); + c[nch] = 0.5 * c[0]; + for (j = 1; j < nch; j++) + { + c[j] = 0.5 * cos (delta * j); + c[nc - j] = 0.5 * sin (delta * j); + } + } +} + +/* -------- child routines -------- */ + +#ifdef USE_CDFT_PTHREADS +#define USE_CDFT_THREADS +#ifndef CDFT_THREADS_BEGIN_N +#define CDFT_THREADS_BEGIN_N 8192 +#endif +#ifndef CDFT_4THREADS_BEGIN_N +#define CDFT_4THREADS_BEGIN_N 65536 +#endif +#define cdft_thread_t pthread_t +#define cdft_thread_create(thp, func, argp) \ + { \ + if (pthread_create (thp, NULL, func, (void*) argp) != 0) \ + { \ + fprintf (stderr, "cdft thread error\n"); \ + exit (1); \ + } \ + } +#define cdft_thread_wait(th) \ + { \ + if (pthread_join (th, NULL) != 0) \ + { \ + fprintf (stderr, "cdft thread error\n"); \ + exit (1); \ + } \ + } +#endif /* USE_CDFT_PTHREADS */ + +#ifdef USE_CDFT_WINTHREADS +#define USE_CDFT_THREADS +#ifndef CDFT_THREADS_BEGIN_N +#define CDFT_THREADS_BEGIN_N 32768 +#endif +#ifndef CDFT_4THREADS_BEGIN_N +#define CDFT_4THREADS_BEGIN_N 524288 +#endif +#define cdft_thread_t HANDLE +#define cdft_thread_create(thp, func, argp) \ + { \ + DWORD thid; \ + *(thp) = CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE) func, (LPVOID) argp, 0, &thid); \ + if (*(thp) == 0) \ + { \ + fprintf (stderr, "cdft thread error\n"); \ + exit (1); \ + } \ + } +#define cdft_thread_wait(th) \ + { \ + WaitForSingleObject (th, INFINITE); \ + CloseHandle (th); \ + } +#endif /* USE_CDFT_WINTHREADS */ + +void cftfsub (int n, double* a, int* ip, int nw, double* w) +{ + void bitrv2 (int n, int* ip, double* a); + void bitrv216 (double* a); + void bitrv208 (double* a); + void cftf1st (int n, double* a, double* w); + void cftrec4 (int n, double* a, int nw, double* w); + void cftleaf (int n, int isplt, double* a, int nw, double* w); + void cftfx41 (int n, double* a, int nw, double* w); + void cftf161 (double* a, double* w); + void cftf081 (double* a, double* w); + void cftf040 (double* a); + void cftx020 (double* a); +#ifdef USE_CDFT_THREADS + void cftrec4_th (int n, double* a, int nw, double* w); +#endif /* USE_CDFT_THREADS */ + + if (n > 8) + { + if (n > 32) + { + cftf1st (n, a, &w[nw - (n >> 2)]); +#ifdef USE_CDFT_THREADS + if (n > CDFT_THREADS_BEGIN_N) + { + cftrec4_th (n, a, nw, w); + } + else +#endif /* USE_CDFT_THREADS */ + if (n > 512) + { + cftrec4 (n, a, nw, w); + } + else if (n > 128) + { + cftleaf (n, 1, a, nw, w); + } + else + { + cftfx41 (n, a, nw, w); + } + bitrv2 (n, ip, a); + } + else if (n == 32) + { + cftf161 (a, &w[nw - 8]); + bitrv216 (a); + } + else + { + cftf081 (a, w); + bitrv208 (a); + } + } + else if (n == 8) + { + cftf040 (a); + } + else if (n == 4) + { + cftx020 (a); + } +} + +void cftbsub (int n, double* a, int* ip, int nw, double* w) +{ + void bitrv2conj (int n, int* ip, double* a); + void bitrv216neg (double* a); + void bitrv208neg (double* a); + void cftb1st (int n, double* a, double* w); + void cftrec4 (int n, double* a, int nw, double* w); + void cftleaf (int n, int isplt, double* a, int nw, double* w); + void cftfx41 (int n, double* a, int nw, double* w); + void cftf161 (double* a, double* w); + void cftf081 (double* a, double* w); + void cftb040 (double* a); + void cftx020 (double* a); +#ifdef USE_CDFT_THREADS + void cftrec4_th (int n, double* a, int nw, double* w); +#endif /* USE_CDFT_THREADS */ + + if (n > 8) + { + if (n > 32) + { + cftb1st (n, a, &w[nw - (n >> 2)]); +#ifdef USE_CDFT_THREADS + if (n > CDFT_THREADS_BEGIN_N) + { + cftrec4_th (n, a, nw, w); + } + else +#endif /* USE_CDFT_THREADS */ + if (n > 512) + { + cftrec4 (n, a, nw, w); + } + else if (n > 128) + { + cftleaf (n, 1, a, nw, w); + } + else + { + cftfx41 (n, a, nw, w); + } + bitrv2conj (n, ip, a); + } + else if (n == 32) + { + cftf161 (a, &w[nw - 8]); + bitrv216neg (a); + } + else + { + cftf081 (a, w); + bitrv208neg (a); + } + } + else if (n == 8) + { + cftb040 (a); + } + else if (n == 4) + { + cftx020 (a); + } +} + +void bitrv2 (int n, int* ip, double* a) +{ + int j, j1, k, k1, l, m, nh, nm; + double xr, xi, yr, yi; + + m = 1; + for (l = n >> 2; l > 8; l >>= 2) + { + m <<= 1; + } + nh = n >> 1; + nm = 4 * m; + if (l == 8) + { + for (k = 0; k < m; k++) + { + for (j = 0; j < k; j++) + { + j1 = 4 * j + 2 * ip[m + k]; + k1 = 4 * k + 2 * ip[m + j]; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 -= nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nh; + k1 += 2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 += nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += 2; + k1 += nh; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 -= nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nh; + k1 -= 2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 += nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + k1 = 4 * k + 2 * ip[m + k]; + j1 = k1 + 2; + k1 += nh; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 -= nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= 2; + k1 -= nh; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nh + 2; + k1 += nh + 2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nh - nm; + k1 += 2 * nm - 2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + } + else + { + for (k = 0; k < m; k++) + { + for (j = 0; j < k; j++) + { + j1 = 4 * j + ip[m + k]; + k1 = 4 * k + ip[m + j]; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nh; + k1 += 2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += 2; + k1 += nh; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nh; + k1 -= 2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + k1 = 4 * k + ip[m + k]; + j1 = k1 + 2; + k1 += nh; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + } +} + +void bitrv2conj (int n, int* ip, double* a) +{ + int j, j1, k, k1, l, m, nh, nm; + double xr, xi, yr, yi; + + m = 1; + for (l = n >> 2; l > 8; l >>= 2) + { + m <<= 1; + } + nh = n >> 1; + nm = 4 * m; + if (l == 8) + { + for (k = 0; k < m; k++) + { + for (j = 0; j < k; j++) + { + j1 = 4 * j + 2 * ip[m + k]; + k1 = 4 * k + 2 * ip[m + j]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 -= nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nh; + k1 += 2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 += nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += 2; + k1 += nh; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 -= nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nh; + k1 -= 2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 += nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + k1 = 4 * k + 2 * ip[m + k]; + j1 = k1 + 2; + k1 += nh; + a[j1 - 1] = -a[j1 - 1]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + a[k1 + 3] = -a[k1 + 3]; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 -= nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= 2; + k1 -= nh; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nh + 2; + k1 += nh + 2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nh - nm; + k1 += 2 * nm - 2; + a[j1 - 1] = -a[j1 - 1]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + a[k1 + 3] = -a[k1 + 3]; + } + } + else + { + for (k = 0; k < m; k++) + { + for (j = 0; j < k; j++) + { + j1 = 4 * j + ip[m + k]; + k1 = 4 * k + ip[m + j]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nh; + k1 += 2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += 2; + k1 += nh; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nh; + k1 -= 2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + k1 = 4 * k + ip[m + k]; + j1 = k1 + 2; + k1 += nh; + a[j1 - 1] = -a[j1 - 1]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + a[k1 + 3] = -a[k1 + 3]; + j1 += nm; + k1 += nm; + a[j1 - 1] = -a[j1 - 1]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + a[k1 + 3] = -a[k1 + 3]; + } + } +} + +void bitrv216 (double* a) +{ + double x1r, x1i, x2r, x2i, x3r, x3i, x4r, x4i, + x5r, x5i, x7r, x7i, x8r, x8i, x10r, x10i, + x11r, x11i, x12r, x12i, x13r, x13i, x14r, x14i; + + x1r = a[2]; + x1i = a[3]; + x2r = a[4]; + x2i = a[5]; + x3r = a[6]; + x3i = a[7]; + x4r = a[8]; + x4i = a[9]; + x5r = a[10]; + x5i = a[11]; + x7r = a[14]; + x7i = a[15]; + x8r = a[16]; + x8i = a[17]; + x10r = a[20]; + x10i = a[21]; + x11r = a[22]; + x11i = a[23]; + x12r = a[24]; + x12i = a[25]; + x13r = a[26]; + x13i = a[27]; + x14r = a[28]; + x14i = a[29]; + a[2] = x8r; + a[3] = x8i; + a[4] = x4r; + a[5] = x4i; + a[6] = x12r; + a[7] = x12i; + a[8] = x2r; + a[9] = x2i; + a[10] = x10r; + a[11] = x10i; + a[14] = x14r; + a[15] = x14i; + a[16] = x1r; + a[17] = x1i; + a[20] = x5r; + a[21] = x5i; + a[22] = x13r; + a[23] = x13i; + a[24] = x3r; + a[25] = x3i; + a[26] = x11r; + a[27] = x11i; + a[28] = x7r; + a[29] = x7i; +} + +void bitrv216neg (double* a) +{ + double x1r, x1i, x2r, x2i, x3r, x3i, x4r, x4i, + x5r, x5i, x6r, x6i, x7r, x7i, x8r, x8i, + x9r, x9i, x10r, x10i, x11r, x11i, x12r, x12i, + x13r, x13i, x14r, x14i, x15r, x15i; + + x1r = a[2]; + x1i = a[3]; + x2r = a[4]; + x2i = a[5]; + x3r = a[6]; + x3i = a[7]; + x4r = a[8]; + x4i = a[9]; + x5r = a[10]; + x5i = a[11]; + x6r = a[12]; + x6i = a[13]; + x7r = a[14]; + x7i = a[15]; + x8r = a[16]; + x8i = a[17]; + x9r = a[18]; + x9i = a[19]; + x10r = a[20]; + x10i = a[21]; + x11r = a[22]; + x11i = a[23]; + x12r = a[24]; + x12i = a[25]; + x13r = a[26]; + x13i = a[27]; + x14r = a[28]; + x14i = a[29]; + x15r = a[30]; + x15i = a[31]; + a[2] = x15r; + a[3] = x15i; + a[4] = x7r; + a[5] = x7i; + a[6] = x11r; + a[7] = x11i; + a[8] = x3r; + a[9] = x3i; + a[10] = x13r; + a[11] = x13i; + a[12] = x5r; + a[13] = x5i; + a[14] = x9r; + a[15] = x9i; + a[16] = x1r; + a[17] = x1i; + a[18] = x14r; + a[19] = x14i; + a[20] = x6r; + a[21] = x6i; + a[22] = x10r; + a[23] = x10i; + a[24] = x2r; + a[25] = x2i; + a[26] = x12r; + a[27] = x12i; + a[28] = x4r; + a[29] = x4i; + a[30] = x8r; + a[31] = x8i; +} + +void bitrv208 (double* a) +{ + double x1r, x1i, x3r, x3i, x4r, x4i, x6r, x6i; + + x1r = a[2]; + x1i = a[3]; + x3r = a[6]; + x3i = a[7]; + x4r = a[8]; + x4i = a[9]; + x6r = a[12]; + x6i = a[13]; + a[2] = x4r; + a[3] = x4i; + a[6] = x6r; + a[7] = x6i; + a[8] = x1r; + a[9] = x1i; + a[12] = x3r; + a[13] = x3i; +} + +void bitrv208neg (double* a) +{ + double x1r, x1i, x2r, x2i, x3r, x3i, x4r, x4i, + x5r, x5i, x6r, x6i, x7r, x7i; + + x1r = a[2]; + x1i = a[3]; + x2r = a[4]; + x2i = a[5]; + x3r = a[6]; + x3i = a[7]; + x4r = a[8]; + x4i = a[9]; + x5r = a[10]; + x5i = a[11]; + x6r = a[12]; + x6i = a[13]; + x7r = a[14]; + x7i = a[15]; + a[2] = x7r; + a[3] = x7i; + a[4] = x3r; + a[5] = x3i; + a[6] = x5r; + a[7] = x5i; + a[8] = x1r; + a[9] = x1i; + a[10] = x6r; + a[11] = x6i; + a[12] = x2r; + a[13] = x2i; + a[14] = x4r; + a[15] = x4i; +} + +void cftf1st (int n, double* a, double* w) +{ + int j, j0, j1, j2, j3, k, m, mh; + double wn4r, csc1, csc3, wk1r, wk1i, wk3r, wk3i, + wd1r, wd1i, wd3r, wd3i; + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i, + y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i; + + mh = n >> 3; + m = 2 * mh; + j1 = m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[0] + a[j2]; + x0i = a[1] + a[j2 + 1]; + x1r = a[0] - a[j2]; + x1i = a[1] - a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[0] = x0r + x2r; + a[1] = x0i + x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + a[j2] = x1r - x3i; + a[j2 + 1] = x1i + x3r; + a[j3] = x1r + x3i; + a[j3 + 1] = x1i - x3r; + wn4r = w[1]; + csc1 = w[2]; + csc3 = w[3]; + wd1r = 1; + wd1i = 0; + wd3r = 1; + wd3i = 0; + k = 0; + for (j = 2; j < mh - 2; j += 4) + { + k += 4; + wk1r = csc1 * (wd1r + w[k]); + wk1i = csc1 * (wd1i + w[k + 1]); + wk3r = csc3 * (wd3r + w[k + 2]); + wk3i = csc3 * (wd3i + w[k + 3]); + wd1r = w[k]; + wd1i = w[k + 1]; + wd3r = w[k + 2]; + wd3i = w[k + 3]; + j1 = j + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j] + a[j2]; + x0i = a[j + 1] + a[j2 + 1]; + x1r = a[j] - a[j2]; + x1i = a[j + 1] - a[j2 + 1]; + y0r = a[j + 2] + a[j2 + 2]; + y0i = a[j + 3] + a[j2 + 3]; + y1r = a[j + 2] - a[j2 + 2]; + y1i = a[j + 3] - a[j2 + 3]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + y2r = a[j1 + 2] + a[j3 + 2]; + y2i = a[j1 + 3] + a[j3 + 3]; + y3r = a[j1 + 2] - a[j3 + 2]; + y3i = a[j1 + 3] - a[j3 + 3]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + a[j + 2] = y0r + y2r; + a[j + 3] = y0i + y2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + a[j1 + 2] = y0r - y2r; + a[j1 + 3] = y0i - y2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2] = wk1r * x0r - wk1i * x0i; + a[j2 + 1] = wk1r * x0i + wk1i * x0r; + x0r = y1r - y3i; + x0i = y1i + y3r; + a[j2 + 2] = wd1r * x0r - wd1i * x0i; + a[j2 + 3] = wd1r * x0i + wd1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = wk3r * x0r + wk3i * x0i; + a[j3 + 1] = wk3r * x0i - wk3i * x0r; + x0r = y1r + y3i; + x0i = y1i - y3r; + a[j3 + 2] = wd3r * x0r + wd3i * x0i; + a[j3 + 3] = wd3r * x0i - wd3i * x0r; + j0 = m - j; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0] + a[j2]; + x0i = a[j0 + 1] + a[j2 + 1]; + x1r = a[j0] - a[j2]; + x1i = a[j0 + 1] - a[j2 + 1]; + y0r = a[j0 - 2] + a[j2 - 2]; + y0i = a[j0 - 1] + a[j2 - 1]; + y1r = a[j0 - 2] - a[j2 - 2]; + y1i = a[j0 - 1] - a[j2 - 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + y2r = a[j1 - 2] + a[j3 - 2]; + y2i = a[j1 - 1] + a[j3 - 1]; + y3r = a[j1 - 2] - a[j3 - 2]; + y3i = a[j1 - 1] - a[j3 - 1]; + a[j0] = x0r + x2r; + a[j0 + 1] = x0i + x2i; + a[j0 - 2] = y0r + y2r; + a[j0 - 1] = y0i + y2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + a[j1 - 2] = y0r - y2r; + a[j1 - 1] = y0i - y2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2] = wk1i * x0r - wk1r * x0i; + a[j2 + 1] = wk1i * x0i + wk1r * x0r; + x0r = y1r - y3i; + x0i = y1i + y3r; + a[j2 - 2] = wd1i * x0r - wd1r * x0i; + a[j2 - 1] = wd1i * x0i + wd1r * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = wk3i * x0r + wk3r * x0i; + a[j3 + 1] = wk3i * x0i - wk3r * x0r; + x0r = y1r + y3i; + x0i = y1i - y3r; + a[j3 - 2] = wd3i * x0r + wd3r * x0i; + a[j3 - 1] = wd3i * x0i - wd3r * x0r; + } + wk1r = csc1 * (wd1r + wn4r); + wk1i = csc1 * (wd1i + wn4r); + wk3r = csc3 * (wd3r - wn4r); + wk3i = csc3 * (wd3i - wn4r); + j0 = mh; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0 - 2] + a[j2 - 2]; + x0i = a[j0 - 1] + a[j2 - 1]; + x1r = a[j0 - 2] - a[j2 - 2]; + x1i = a[j0 - 1] - a[j2 - 1]; + x2r = a[j1 - 2] + a[j3 - 2]; + x2i = a[j1 - 1] + a[j3 - 1]; + x3r = a[j1 - 2] - a[j3 - 2]; + x3i = a[j1 - 1] - a[j3 - 1]; + a[j0 - 2] = x0r + x2r; + a[j0 - 1] = x0i + x2i; + a[j1 - 2] = x0r - x2r; + a[j1 - 1] = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2 - 2] = wk1r * x0r - wk1i * x0i; + a[j2 - 1] = wk1r * x0i + wk1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3 - 2] = wk3r * x0r + wk3i * x0i; + a[j3 - 1] = wk3r * x0i - wk3i * x0r; + x0r = a[j0] + a[j2]; + x0i = a[j0 + 1] + a[j2 + 1]; + x1r = a[j0] - a[j2]; + x1i = a[j0 + 1] - a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[j0] = x0r + x2r; + a[j0 + 1] = x0i + x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2] = wn4r * (x0r - x0i); + a[j2 + 1] = wn4r * (x0i + x0r); + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = -wn4r * (x0r + x0i); + a[j3 + 1] = -wn4r * (x0i - x0r); + x0r = a[j0 + 2] + a[j2 + 2]; + x0i = a[j0 + 3] + a[j2 + 3]; + x1r = a[j0 + 2] - a[j2 + 2]; + x1i = a[j0 + 3] - a[j2 + 3]; + x2r = a[j1 + 2] + a[j3 + 2]; + x2i = a[j1 + 3] + a[j3 + 3]; + x3r = a[j1 + 2] - a[j3 + 2]; + x3i = a[j1 + 3] - a[j3 + 3]; + a[j0 + 2] = x0r + x2r; + a[j0 + 3] = x0i + x2i; + a[j1 + 2] = x0r - x2r; + a[j1 + 3] = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2 + 2] = wk1i * x0r - wk1r * x0i; + a[j2 + 3] = wk1i * x0i + wk1r * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3 + 2] = wk3i * x0r + wk3r * x0i; + a[j3 + 3] = wk3i * x0i - wk3r * x0r; +} + +void cftb1st (int n, double* a, double* w) +{ + int j, j0, j1, j2, j3, k, m, mh; + double wn4r, csc1, csc3, wk1r, wk1i, wk3r, wk3i, + wd1r, wd1i, wd3r, wd3i; + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i, + y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i; + + mh = n >> 3; + m = 2 * mh; + j1 = m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[0] + a[j2]; + x0i = -a[1] - a[j2 + 1]; + x1r = a[0] - a[j2]; + x1i = -a[1] + a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[0] = x0r + x2r; + a[1] = x0i - x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i + x2i; + a[j2] = x1r + x3i; + a[j2 + 1] = x1i + x3r; + a[j3] = x1r - x3i; + a[j3 + 1] = x1i - x3r; + wn4r = w[1]; + csc1 = w[2]; + csc3 = w[3]; + wd1r = 1; + wd1i = 0; + wd3r = 1; + wd3i = 0; + k = 0; + for (j = 2; j < mh - 2; j += 4) + { + k += 4; + wk1r = csc1 * (wd1r + w[k]); + wk1i = csc1 * (wd1i + w[k + 1]); + wk3r = csc3 * (wd3r + w[k + 2]); + wk3i = csc3 * (wd3i + w[k + 3]); + wd1r = w[k]; + wd1i = w[k + 1]; + wd3r = w[k + 2]; + wd3i = w[k + 3]; + j1 = j + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j] + a[j2]; + x0i = -a[j + 1] - a[j2 + 1]; + x1r = a[j] - a[j2]; + x1i = -a[j + 1] + a[j2 + 1]; + y0r = a[j + 2] + a[j2 + 2]; + y0i = -a[j + 3] - a[j2 + 3]; + y1r = a[j + 2] - a[j2 + 2]; + y1i = -a[j + 3] + a[j2 + 3]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + y2r = a[j1 + 2] + a[j3 + 2]; + y2i = a[j1 + 3] + a[j3 + 3]; + y3r = a[j1 + 2] - a[j3 + 2]; + y3i = a[j1 + 3] - a[j3 + 3]; + a[j] = x0r + x2r; + a[j + 1] = x0i - x2i; + a[j + 2] = y0r + y2r; + a[j + 3] = y0i - y2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i + x2i; + a[j1 + 2] = y0r - y2r; + a[j1 + 3] = y0i + y2i; + x0r = x1r + x3i; + x0i = x1i + x3r; + a[j2] = wk1r * x0r - wk1i * x0i; + a[j2 + 1] = wk1r * x0i + wk1i * x0r; + x0r = y1r + y3i; + x0i = y1i + y3r; + a[j2 + 2] = wd1r * x0r - wd1i * x0i; + a[j2 + 3] = wd1r * x0i + wd1i * x0r; + x0r = x1r - x3i; + x0i = x1i - x3r; + a[j3] = wk3r * x0r + wk3i * x0i; + a[j3 + 1] = wk3r * x0i - wk3i * x0r; + x0r = y1r - y3i; + x0i = y1i - y3r; + a[j3 + 2] = wd3r * x0r + wd3i * x0i; + a[j3 + 3] = wd3r * x0i - wd3i * x0r; + j0 = m - j; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0] + a[j2]; + x0i = -a[j0 + 1] - a[j2 + 1]; + x1r = a[j0] - a[j2]; + x1i = -a[j0 + 1] + a[j2 + 1]; + y0r = a[j0 - 2] + a[j2 - 2]; + y0i = -a[j0 - 1] - a[j2 - 1]; + y1r = a[j0 - 2] - a[j2 - 2]; + y1i = -a[j0 - 1] + a[j2 - 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + y2r = a[j1 - 2] + a[j3 - 2]; + y2i = a[j1 - 1] + a[j3 - 1]; + y3r = a[j1 - 2] - a[j3 - 2]; + y3i = a[j1 - 1] - a[j3 - 1]; + a[j0] = x0r + x2r; + a[j0 + 1] = x0i - x2i; + a[j0 - 2] = y0r + y2r; + a[j0 - 1] = y0i - y2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i + x2i; + a[j1 - 2] = y0r - y2r; + a[j1 - 1] = y0i + y2i; + x0r = x1r + x3i; + x0i = x1i + x3r; + a[j2] = wk1i * x0r - wk1r * x0i; + a[j2 + 1] = wk1i * x0i + wk1r * x0r; + x0r = y1r + y3i; + x0i = y1i + y3r; + a[j2 - 2] = wd1i * x0r - wd1r * x0i; + a[j2 - 1] = wd1i * x0i + wd1r * x0r; + x0r = x1r - x3i; + x0i = x1i - x3r; + a[j3] = wk3i * x0r + wk3r * x0i; + a[j3 + 1] = wk3i * x0i - wk3r * x0r; + x0r = y1r - y3i; + x0i = y1i - y3r; + a[j3 - 2] = wd3i * x0r + wd3r * x0i; + a[j3 - 1] = wd3i * x0i - wd3r * x0r; + } + wk1r = csc1 * (wd1r + wn4r); + wk1i = csc1 * (wd1i + wn4r); + wk3r = csc3 * (wd3r - wn4r); + wk3i = csc3 * (wd3i - wn4r); + j0 = mh; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0 - 2] + a[j2 - 2]; + x0i = -a[j0 - 1] - a[j2 - 1]; + x1r = a[j0 - 2] - a[j2 - 2]; + x1i = -a[j0 - 1] + a[j2 - 1]; + x2r = a[j1 - 2] + a[j3 - 2]; + x2i = a[j1 - 1] + a[j3 - 1]; + x3r = a[j1 - 2] - a[j3 - 2]; + x3i = a[j1 - 1] - a[j3 - 1]; + a[j0 - 2] = x0r + x2r; + a[j0 - 1] = x0i - x2i; + a[j1 - 2] = x0r - x2r; + a[j1 - 1] = x0i + x2i; + x0r = x1r + x3i; + x0i = x1i + x3r; + a[j2 - 2] = wk1r * x0r - wk1i * x0i; + a[j2 - 1] = wk1r * x0i + wk1i * x0r; + x0r = x1r - x3i; + x0i = x1i - x3r; + a[j3 - 2] = wk3r * x0r + wk3i * x0i; + a[j3 - 1] = wk3r * x0i - wk3i * x0r; + x0r = a[j0] + a[j2]; + x0i = -a[j0 + 1] - a[j2 + 1]; + x1r = a[j0] - a[j2]; + x1i = -a[j0 + 1] + a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[j0] = x0r + x2r; + a[j0 + 1] = x0i - x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i + x2i; + x0r = x1r + x3i; + x0i = x1i + x3r; + a[j2] = wn4r * (x0r - x0i); + a[j2 + 1] = wn4r * (x0i + x0r); + x0r = x1r - x3i; + x0i = x1i - x3r; + a[j3] = -wn4r * (x0r + x0i); + a[j3 + 1] = -wn4r * (x0i - x0r); + x0r = a[j0 + 2] + a[j2 + 2]; + x0i = -a[j0 + 3] - a[j2 + 3]; + x1r = a[j0 + 2] - a[j2 + 2]; + x1i = -a[j0 + 3] + a[j2 + 3]; + x2r = a[j1 + 2] + a[j3 + 2]; + x2i = a[j1 + 3] + a[j3 + 3]; + x3r = a[j1 + 2] - a[j3 + 2]; + x3i = a[j1 + 3] - a[j3 + 3]; + a[j0 + 2] = x0r + x2r; + a[j0 + 3] = x0i - x2i; + a[j1 + 2] = x0r - x2r; + a[j1 + 3] = x0i + x2i; + x0r = x1r + x3i; + x0i = x1i + x3r; + a[j2 + 2] = wk1i * x0r - wk1r * x0i; + a[j2 + 3] = wk1i * x0i + wk1r * x0r; + x0r = x1r - x3i; + x0i = x1i - x3r; + a[j3 + 2] = wk3i * x0r + wk3r * x0i; + a[j3 + 3] = wk3i * x0i - wk3r * x0r; +} + +#ifdef USE_CDFT_THREADS +struct cdft_arg_st +{ + int n0; + int n; + double* a; + int nw; + double* w; +}; +typedef struct cdft_arg_st cdft_arg_t; + +void cftrec4_th (int n, double* a, int nw, double* w) +{ + void* cftrec1_th (void* p); + void* cftrec2_th (void* p); + int i, idiv4, m, nthread; + cdft_thread_t th[4]; + cdft_arg_t ag[4]; + + nthread = 2; + idiv4 = 0; + m = n >> 1; + if (n > CDFT_4THREADS_BEGIN_N) + { + nthread = 4; + idiv4 = 1; + m >>= 1; + } + for (i = 0; i < nthread; i++) + { + ag[i].n0 = n; + ag[i].n = m; + ag[i].a = &a[i * m]; + ag[i].nw = nw; + ag[i].w = w; + if (i != idiv4) + { + cdft_thread_create (&th[i], cftrec1_th, &ag[i]); + } + else + { + cdft_thread_create (&th[i], cftrec2_th, &ag[i]); + } + } + for (i = 0; i < nthread; i++) + { + cdft_thread_wait (th[i]); + } +} + +void* cftrec1_th (void* p) +{ + int cfttree (int n, int j, int k, double* a, int nw, double* w); + void cftleaf (int n, int isplt, double* a, int nw, double* w); + void cftmdl1 (int n, double* a, double* w); + int isplt, j, k, m, n, n0, nw; + double *a, *w; + + n0 = ((cdft_arg_t*) p)->n0; + n = ((cdft_arg_t*) p)->n; + a = ((cdft_arg_t*) p)->a; + nw = ((cdft_arg_t*) p)->nw; + w = ((cdft_arg_t*) p)->w; + m = n0; + while (m > 512) + { + m >>= 2; + cftmdl1 (m, &a[n - m], &w[nw - (m >> 1)]); + } + cftleaf (m, 1, &a[n - m], nw, w); + k = 0; + for (j = n - m; j > 0; j -= m) + { + k++; + isplt = cfttree (m, j, k, a, nw, w); + cftleaf (m, isplt, &a[j - m], nw, w); + } + return (void*) 0; +} + +void* cftrec2_th (void* p) +{ + int cfttree (int n, int j, int k, double* a, int nw, double* w); + void cftleaf (int n, int isplt, double* a, int nw, double* w); + void cftmdl2 (int n, double* a, double* w); + int isplt, j, k, m, n, n0, nw; + double *a, *w; + + n0 = ((cdft_arg_t*) p)->n0; + n = ((cdft_arg_t*) p)->n; + a = ((cdft_arg_t*) p)->a; + nw = ((cdft_arg_t*) p)->nw; + w = ((cdft_arg_t*) p)->w; + k = 1; + m = n0; + while (m > 512) + { + m >>= 2; + k <<= 2; + cftmdl2 (m, &a[n - m], &w[nw - m]); + } + cftleaf (m, 0, &a[n - m], nw, w); + k >>= 1; + for (j = n - m; j > 0; j -= m) + { + k++; + isplt = cfttree (m, j, k, a, nw, w); + cftleaf (m, isplt, &a[j - m], nw, w); + } + return (void*) 0; +} +#endif /* USE_CDFT_THREADS */ + +void cftrec4 (int n, double* a, int nw, double* w) +{ + int cfttree (int n, int j, int k, double* a, int nw, double* w); + void cftleaf (int n, int isplt, double* a, int nw, double* w); + void cftmdl1 (int n, double* a, double* w); + int isplt, j, k, m; + + m = n; + while (m > 512) + { + m >>= 2; + cftmdl1 (m, &a[n - m], &w[nw - (m >> 1)]); + } + cftleaf (m, 1, &a[n - m], nw, w); + k = 0; + for (j = n - m; j > 0; j -= m) + { + k++; + isplt = cfttree (m, j, k, a, nw, w); + cftleaf (m, isplt, &a[j - m], nw, w); + } +} + +int cfttree (int n, int j, int k, double* a, int nw, double* w) +{ + void cftmdl1 (int n, double* a, double* w); + void cftmdl2 (int n, double* a, double* w); + int i, isplt, m; + + if ((k & 3) != 0) + { + isplt = k & 1; + if (isplt != 0) + { + cftmdl1 (n, &a[j - n], &w[nw - (n >> 1)]); + } + else + { + cftmdl2 (n, &a[j - n], &w[nw - n]); + } + } + else + { + m = n; + for (i = k; (i & 3) == 0; i >>= 2) + { + m <<= 2; + } + isplt = i & 1; + if (isplt != 0) + { + while (m > 128) + { + cftmdl1 (m, &a[j - m], &w[nw - (m >> 1)]); + m >>= 2; + } + } + else + { + while (m > 128) + { + cftmdl2 (m, &a[j - m], &w[nw - m]); + m >>= 2; + } + } + } + return isplt; +} + +void cftleaf (int n, int isplt, double* a, int nw, double* w) +{ + void cftmdl1 (int n, double* a, double* w); + void cftmdl2 (int n, double* a, double* w); + void cftf161 (double* a, double* w); + void cftf162 (double* a, double* w); + void cftf081 (double* a, double* w); + void cftf082 (double* a, double* w); + + if (n == 512) + { + cftmdl1 (128, a, &w[nw - 64]); + cftf161 (a, &w[nw - 8]); + cftf162 (&a[32], &w[nw - 32]); + cftf161 (&a[64], &w[nw - 8]); + cftf161 (&a[96], &w[nw - 8]); + cftmdl2 (128, &a[128], &w[nw - 128]); + cftf161 (&a[128], &w[nw - 8]); + cftf162 (&a[160], &w[nw - 32]); + cftf161 (&a[192], &w[nw - 8]); + cftf162 (&a[224], &w[nw - 32]); + cftmdl1 (128, &a[256], &w[nw - 64]); + cftf161 (&a[256], &w[nw - 8]); + cftf162 (&a[288], &w[nw - 32]); + cftf161 (&a[320], &w[nw - 8]); + cftf161 (&a[352], &w[nw - 8]); + if (isplt != 0) + { + cftmdl1 (128, &a[384], &w[nw - 64]); + cftf161 (&a[480], &w[nw - 8]); + } + else + { + cftmdl2 (128, &a[384], &w[nw - 128]); + cftf162 (&a[480], &w[nw - 32]); + } + cftf161 (&a[384], &w[nw - 8]); + cftf162 (&a[416], &w[nw - 32]); + cftf161 (&a[448], &w[nw - 8]); + } + else + { + cftmdl1 (64, a, &w[nw - 32]); + cftf081 (a, &w[nw - 8]); + cftf082 (&a[16], &w[nw - 8]); + cftf081 (&a[32], &w[nw - 8]); + cftf081 (&a[48], &w[nw - 8]); + cftmdl2 (64, &a[64], &w[nw - 64]); + cftf081 (&a[64], &w[nw - 8]); + cftf082 (&a[80], &w[nw - 8]); + cftf081 (&a[96], &w[nw - 8]); + cftf082 (&a[112], &w[nw - 8]); + cftmdl1 (64, &a[128], &w[nw - 32]); + cftf081 (&a[128], &w[nw - 8]); + cftf082 (&a[144], &w[nw - 8]); + cftf081 (&a[160], &w[nw - 8]); + cftf081 (&a[176], &w[nw - 8]); + if (isplt != 0) + { + cftmdl1 (64, &a[192], &w[nw - 32]); + cftf081 (&a[240], &w[nw - 8]); + } + else + { + cftmdl2 (64, &a[192], &w[nw - 64]); + cftf082 (&a[240], &w[nw - 8]); + } + cftf081 (&a[192], &w[nw - 8]); + cftf082 (&a[208], &w[nw - 8]); + cftf081 (&a[224], &w[nw - 8]); + } +} + +void cftmdl1 (int n, double* a, double* w) +{ + int j, j0, j1, j2, j3, k, m, mh; + double wn4r, wk1r, wk1i, wk3r, wk3i; + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + mh = n >> 3; + m = 2 * mh; + j1 = m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[0] + a[j2]; + x0i = a[1] + a[j2 + 1]; + x1r = a[0] - a[j2]; + x1i = a[1] - a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[0] = x0r + x2r; + a[1] = x0i + x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + a[j2] = x1r - x3i; + a[j2 + 1] = x1i + x3r; + a[j3] = x1r + x3i; + a[j3 + 1] = x1i - x3r; + wn4r = w[1]; + k = 0; + for (j = 2; j < mh; j += 2) + { + k += 4; + wk1r = w[k]; + wk1i = w[k + 1]; + wk3r = w[k + 2]; + wk3i = w[k + 3]; + j1 = j + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j] + a[j2]; + x0i = a[j + 1] + a[j2 + 1]; + x1r = a[j] - a[j2]; + x1i = a[j + 1] - a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2] = wk1r * x0r - wk1i * x0i; + a[j2 + 1] = wk1r * x0i + wk1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = wk3r * x0r + wk3i * x0i; + a[j3 + 1] = wk3r * x0i - wk3i * x0r; + j0 = m - j; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0] + a[j2]; + x0i = a[j0 + 1] + a[j2 + 1]; + x1r = a[j0] - a[j2]; + x1i = a[j0 + 1] - a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[j0] = x0r + x2r; + a[j0 + 1] = x0i + x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2] = wk1i * x0r - wk1r * x0i; + a[j2 + 1] = wk1i * x0i + wk1r * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = wk3i * x0r + wk3r * x0i; + a[j3 + 1] = wk3i * x0i - wk3r * x0r; + } + j0 = mh; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0] + a[j2]; + x0i = a[j0 + 1] + a[j2 + 1]; + x1r = a[j0] - a[j2]; + x1i = a[j0 + 1] - a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[j0] = x0r + x2r; + a[j0 + 1] = x0i + x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2] = wn4r * (x0r - x0i); + a[j2 + 1] = wn4r * (x0i + x0r); + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = -wn4r * (x0r + x0i); + a[j3 + 1] = -wn4r * (x0i - x0r); +} + +void cftmdl2 (int n, double* a, double* w) +{ + int j, j0, j1, j2, j3, k, kr, m, mh; + double wn4r, wk1r, wk1i, wk3r, wk3i, wd1r, wd1i, wd3r, wd3i; + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i, y0r, y0i, y2r, y2i; + + mh = n >> 3; + m = 2 * mh; + wn4r = w[1]; + j1 = m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[0] - a[j2 + 1]; + x0i = a[1] + a[j2]; + x1r = a[0] + a[j2 + 1]; + x1i = a[1] - a[j2]; + x2r = a[j1] - a[j3 + 1]; + x2i = a[j1 + 1] + a[j3]; + x3r = a[j1] + a[j3 + 1]; + x3i = a[j1 + 1] - a[j3]; + y0r = wn4r * (x2r - x2i); + y0i = wn4r * (x2i + x2r); + a[0] = x0r + y0r; + a[1] = x0i + y0i; + a[j1] = x0r - y0r; + a[j1 + 1] = x0i - y0i; + y0r = wn4r * (x3r - x3i); + y0i = wn4r * (x3i + x3r); + a[j2] = x1r - y0i; + a[j2 + 1] = x1i + y0r; + a[j3] = x1r + y0i; + a[j3 + 1] = x1i - y0r; + k = 0; + kr = 2 * m; + for (j = 2; j < mh; j += 2) + { + k += 4; + wk1r = w[k]; + wk1i = w[k + 1]; + wk3r = w[k + 2]; + wk3i = w[k + 3]; + kr -= 4; + wd1i = w[kr]; + wd1r = w[kr + 1]; + wd3i = w[kr + 2]; + wd3r = w[kr + 3]; + j1 = j + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j] - a[j2 + 1]; + x0i = a[j + 1] + a[j2]; + x1r = a[j] + a[j2 + 1]; + x1i = a[j + 1] - a[j2]; + x2r = a[j1] - a[j3 + 1]; + x2i = a[j1 + 1] + a[j3]; + x3r = a[j1] + a[j3 + 1]; + x3i = a[j1 + 1] - a[j3]; + y0r = wk1r * x0r - wk1i * x0i; + y0i = wk1r * x0i + wk1i * x0r; + y2r = wd1r * x2r - wd1i * x2i; + y2i = wd1r * x2i + wd1i * x2r; + a[j] = y0r + y2r; + a[j + 1] = y0i + y2i; + a[j1] = y0r - y2r; + a[j1 + 1] = y0i - y2i; + y0r = wk3r * x1r + wk3i * x1i; + y0i = wk3r * x1i - wk3i * x1r; + y2r = wd3r * x3r + wd3i * x3i; + y2i = wd3r * x3i - wd3i * x3r; + a[j2] = y0r + y2r; + a[j2 + 1] = y0i + y2i; + a[j3] = y0r - y2r; + a[j3 + 1] = y0i - y2i; + j0 = m - j; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0] - a[j2 + 1]; + x0i = a[j0 + 1] + a[j2]; + x1r = a[j0] + a[j2 + 1]; + x1i = a[j0 + 1] - a[j2]; + x2r = a[j1] - a[j3 + 1]; + x2i = a[j1 + 1] + a[j3]; + x3r = a[j1] + a[j3 + 1]; + x3i = a[j1 + 1] - a[j3]; + y0r = wd1i * x0r - wd1r * x0i; + y0i = wd1i * x0i + wd1r * x0r; + y2r = wk1i * x2r - wk1r * x2i; + y2i = wk1i * x2i + wk1r * x2r; + a[j0] = y0r + y2r; + a[j0 + 1] = y0i + y2i; + a[j1] = y0r - y2r; + a[j1 + 1] = y0i - y2i; + y0r = wd3i * x1r + wd3r * x1i; + y0i = wd3i * x1i - wd3r * x1r; + y2r = wk3i * x3r + wk3r * x3i; + y2i = wk3i * x3i - wk3r * x3r; + a[j2] = y0r + y2r; + a[j2 + 1] = y0i + y2i; + a[j3] = y0r - y2r; + a[j3 + 1] = y0i - y2i; + } + wk1r = w[m]; + wk1i = w[m + 1]; + j0 = mh; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0] - a[j2 + 1]; + x0i = a[j0 + 1] + a[j2]; + x1r = a[j0] + a[j2 + 1]; + x1i = a[j0 + 1] - a[j2]; + x2r = a[j1] - a[j3 + 1]; + x2i = a[j1 + 1] + a[j3]; + x3r = a[j1] + a[j3 + 1]; + x3i = a[j1 + 1] - a[j3]; + y0r = wk1r * x0r - wk1i * x0i; + y0i = wk1r * x0i + wk1i * x0r; + y2r = wk1i * x2r - wk1r * x2i; + y2i = wk1i * x2i + wk1r * x2r; + a[j0] = y0r + y2r; + a[j0 + 1] = y0i + y2i; + a[j1] = y0r - y2r; + a[j1 + 1] = y0i - y2i; + y0r = wk1i * x1r - wk1r * x1i; + y0i = wk1i * x1i + wk1r * x1r; + y2r = wk1r * x3r - wk1i * x3i; + y2i = wk1r * x3i + wk1i * x3r; + a[j2] = y0r - y2r; + a[j2 + 1] = y0i - y2i; + a[j3] = y0r + y2r; + a[j3 + 1] = y0i + y2i; +} + +void cftfx41 (int n, double* a, int nw, double* w) +{ + void cftf161 (double* a, double* w); + void cftf162 (double* a, double* w); + void cftf081 (double* a, double* w); + void cftf082 (double* a, double* w); + + if (n == 128) + { + cftf161 (a, &w[nw - 8]); + cftf162 (&a[32], &w[nw - 32]); + cftf161 (&a[64], &w[nw - 8]); + cftf161 (&a[96], &w[nw - 8]); + } + else + { + cftf081 (a, &w[nw - 8]); + cftf082 (&a[16], &w[nw - 8]); + cftf081 (&a[32], &w[nw - 8]); + cftf081 (&a[48], &w[nw - 8]); + } +} + +void cftf161 (double* a, double* w) +{ + double wn4r, wk1r, wk1i, + x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i, + y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i, + y4r, y4i, y5r, y5i, y6r, y6i, y7r, y7i, + y8r, y8i, y9r, y9i, y10r, y10i, y11r, y11i, + y12r, y12i, y13r, y13i, y14r, y14i, y15r, y15i; + + wn4r = w[1]; + wk1r = w[2]; + wk1i = w[3]; + x0r = a[0] + a[16]; + x0i = a[1] + a[17]; + x1r = a[0] - a[16]; + x1i = a[1] - a[17]; + x2r = a[8] + a[24]; + x2i = a[9] + a[25]; + x3r = a[8] - a[24]; + x3i = a[9] - a[25]; + y0r = x0r + x2r; + y0i = x0i + x2i; + y4r = x0r - x2r; + y4i = x0i - x2i; + y8r = x1r - x3i; + y8i = x1i + x3r; + y12r = x1r + x3i; + y12i = x1i - x3r; + x0r = a[2] + a[18]; + x0i = a[3] + a[19]; + x1r = a[2] - a[18]; + x1i = a[3] - a[19]; + x2r = a[10] + a[26]; + x2i = a[11] + a[27]; + x3r = a[10] - a[26]; + x3i = a[11] - a[27]; + y1r = x0r + x2r; + y1i = x0i + x2i; + y5r = x0r - x2r; + y5i = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + y9r = wk1r * x0r - wk1i * x0i; + y9i = wk1r * x0i + wk1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + y13r = wk1i * x0r - wk1r * x0i; + y13i = wk1i * x0i + wk1r * x0r; + x0r = a[4] + a[20]; + x0i = a[5] + a[21]; + x1r = a[4] - a[20]; + x1i = a[5] - a[21]; + x2r = a[12] + a[28]; + x2i = a[13] + a[29]; + x3r = a[12] - a[28]; + x3i = a[13] - a[29]; + y2r = x0r + x2r; + y2i = x0i + x2i; + y6r = x0r - x2r; + y6i = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + y10r = wn4r * (x0r - x0i); + y10i = wn4r * (x0i + x0r); + x0r = x1r + x3i; + x0i = x1i - x3r; + y14r = wn4r * (x0r + x0i); + y14i = wn4r * (x0i - x0r); + x0r = a[6] + a[22]; + x0i = a[7] + a[23]; + x1r = a[6] - a[22]; + x1i = a[7] - a[23]; + x2r = a[14] + a[30]; + x2i = a[15] + a[31]; + x3r = a[14] - a[30]; + x3i = a[15] - a[31]; + y3r = x0r + x2r; + y3i = x0i + x2i; + y7r = x0r - x2r; + y7i = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + y11r = wk1i * x0r - wk1r * x0i; + y11i = wk1i * x0i + wk1r * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + y15r = wk1r * x0r - wk1i * x0i; + y15i = wk1r * x0i + wk1i * x0r; + x0r = y12r - y14r; + x0i = y12i - y14i; + x1r = y12r + y14r; + x1i = y12i + y14i; + x2r = y13r - y15r; + x2i = y13i - y15i; + x3r = y13r + y15r; + x3i = y13i + y15i; + a[24] = x0r + x2r; + a[25] = x0i + x2i; + a[26] = x0r - x2r; + a[27] = x0i - x2i; + a[28] = x1r - x3i; + a[29] = x1i + x3r; + a[30] = x1r + x3i; + a[31] = x1i - x3r; + x0r = y8r + y10r; + x0i = y8i + y10i; + x1r = y8r - y10r; + x1i = y8i - y10i; + x2r = y9r + y11r; + x2i = y9i + y11i; + x3r = y9r - y11r; + x3i = y9i - y11i; + a[16] = x0r + x2r; + a[17] = x0i + x2i; + a[18] = x0r - x2r; + a[19] = x0i - x2i; + a[20] = x1r - x3i; + a[21] = x1i + x3r; + a[22] = x1r + x3i; + a[23] = x1i - x3r; + x0r = y5r - y7i; + x0i = y5i + y7r; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + x0r = y5r + y7i; + x0i = y5i - y7r; + x3r = wn4r * (x0r - x0i); + x3i = wn4r * (x0i + x0r); + x0r = y4r - y6i; + x0i = y4i + y6r; + x1r = y4r + y6i; + x1i = y4i - y6r; + a[8] = x0r + x2r; + a[9] = x0i + x2i; + a[10] = x0r - x2r; + a[11] = x0i - x2i; + a[12] = x1r - x3i; + a[13] = x1i + x3r; + a[14] = x1r + x3i; + a[15] = x1i - x3r; + x0r = y0r + y2r; + x0i = y0i + y2i; + x1r = y0r - y2r; + x1i = y0i - y2i; + x2r = y1r + y3r; + x2i = y1i + y3i; + x3r = y1r - y3r; + x3i = y1i - y3i; + a[0] = x0r + x2r; + a[1] = x0i + x2i; + a[2] = x0r - x2r; + a[3] = x0i - x2i; + a[4] = x1r - x3i; + a[5] = x1i + x3r; + a[6] = x1r + x3i; + a[7] = x1i - x3r; +} + +void cftf162 (double* a, double* w) +{ + double wn4r, wk1r, wk1i, wk2r, wk2i, wk3r, wk3i, + x0r, x0i, x1r, x1i, x2r, x2i, + y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i, + y4r, y4i, y5r, y5i, y6r, y6i, y7r, y7i, + y8r, y8i, y9r, y9i, y10r, y10i, y11r, y11i, + y12r, y12i, y13r, y13i, y14r, y14i, y15r, y15i; + + wn4r = w[1]; + wk1r = w[4]; + wk1i = w[5]; + wk3r = w[6]; + wk3i = -w[7]; + wk2r = w[8]; + wk2i = w[9]; + x1r = a[0] - a[17]; + x1i = a[1] + a[16]; + x0r = a[8] - a[25]; + x0i = a[9] + a[24]; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + y0r = x1r + x2r; + y0i = x1i + x2i; + y4r = x1r - x2r; + y4i = x1i - x2i; + x1r = a[0] + a[17]; + x1i = a[1] - a[16]; + x0r = a[8] + a[25]; + x0i = a[9] - a[24]; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + y8r = x1r - x2i; + y8i = x1i + x2r; + y12r = x1r + x2i; + y12i = x1i - x2r; + x0r = a[2] - a[19]; + x0i = a[3] + a[18]; + x1r = wk1r * x0r - wk1i * x0i; + x1i = wk1r * x0i + wk1i * x0r; + x0r = a[10] - a[27]; + x0i = a[11] + a[26]; + x2r = wk3i * x0r - wk3r * x0i; + x2i = wk3i * x0i + wk3r * x0r; + y1r = x1r + x2r; + y1i = x1i + x2i; + y5r = x1r - x2r; + y5i = x1i - x2i; + x0r = a[2] + a[19]; + x0i = a[3] - a[18]; + x1r = wk3r * x0r - wk3i * x0i; + x1i = wk3r * x0i + wk3i * x0r; + x0r = a[10] + a[27]; + x0i = a[11] - a[26]; + x2r = wk1r * x0r + wk1i * x0i; + x2i = wk1r * x0i - wk1i * x0r; + y9r = x1r - x2r; + y9i = x1i - x2i; + y13r = x1r + x2r; + y13i = x1i + x2i; + x0r = a[4] - a[21]; + x0i = a[5] + a[20]; + x1r = wk2r * x0r - wk2i * x0i; + x1i = wk2r * x0i + wk2i * x0r; + x0r = a[12] - a[29]; + x0i = a[13] + a[28]; + x2r = wk2i * x0r - wk2r * x0i; + x2i = wk2i * x0i + wk2r * x0r; + y2r = x1r + x2r; + y2i = x1i + x2i; + y6r = x1r - x2r; + y6i = x1i - x2i; + x0r = a[4] + a[21]; + x0i = a[5] - a[20]; + x1r = wk2i * x0r - wk2r * x0i; + x1i = wk2i * x0i + wk2r * x0r; + x0r = a[12] + a[29]; + x0i = a[13] - a[28]; + x2r = wk2r * x0r - wk2i * x0i; + x2i = wk2r * x0i + wk2i * x0r; + y10r = x1r - x2r; + y10i = x1i - x2i; + y14r = x1r + x2r; + y14i = x1i + x2i; + x0r = a[6] - a[23]; + x0i = a[7] + a[22]; + x1r = wk3r * x0r - wk3i * x0i; + x1i = wk3r * x0i + wk3i * x0r; + x0r = a[14] - a[31]; + x0i = a[15] + a[30]; + x2r = wk1i * x0r - wk1r * x0i; + x2i = wk1i * x0i + wk1r * x0r; + y3r = x1r + x2r; + y3i = x1i + x2i; + y7r = x1r - x2r; + y7i = x1i - x2i; + x0r = a[6] + a[23]; + x0i = a[7] - a[22]; + x1r = wk1i * x0r + wk1r * x0i; + x1i = wk1i * x0i - wk1r * x0r; + x0r = a[14] + a[31]; + x0i = a[15] - a[30]; + x2r = wk3i * x0r - wk3r * x0i; + x2i = wk3i * x0i + wk3r * x0r; + y11r = x1r + x2r; + y11i = x1i + x2i; + y15r = x1r - x2r; + y15i = x1i - x2i; + x1r = y0r + y2r; + x1i = y0i + y2i; + x2r = y1r + y3r; + x2i = y1i + y3i; + a[0] = x1r + x2r; + a[1] = x1i + x2i; + a[2] = x1r - x2r; + a[3] = x1i - x2i; + x1r = y0r - y2r; + x1i = y0i - y2i; + x2r = y1r - y3r; + x2i = y1i - y3i; + a[4] = x1r - x2i; + a[5] = x1i + x2r; + a[6] = x1r + x2i; + a[7] = x1i - x2r; + x1r = y4r - y6i; + x1i = y4i + y6r; + x0r = y5r - y7i; + x0i = y5i + y7r; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + a[8] = x1r + x2r; + a[9] = x1i + x2i; + a[10] = x1r - x2r; + a[11] = x1i - x2i; + x1r = y4r + y6i; + x1i = y4i - y6r; + x0r = y5r + y7i; + x0i = y5i - y7r; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + a[12] = x1r - x2i; + a[13] = x1i + x2r; + a[14] = x1r + x2i; + a[15] = x1i - x2r; + x1r = y8r + y10r; + x1i = y8i + y10i; + x2r = y9r - y11r; + x2i = y9i - y11i; + a[16] = x1r + x2r; + a[17] = x1i + x2i; + a[18] = x1r - x2r; + a[19] = x1i - x2i; + x1r = y8r - y10r; + x1i = y8i - y10i; + x2r = y9r + y11r; + x2i = y9i + y11i; + a[20] = x1r - x2i; + a[21] = x1i + x2r; + a[22] = x1r + x2i; + a[23] = x1i - x2r; + x1r = y12r - y14i; + x1i = y12i + y14r; + x0r = y13r + y15i; + x0i = y13i - y15r; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + a[24] = x1r + x2r; + a[25] = x1i + x2i; + a[26] = x1r - x2r; + a[27] = x1i - x2i; + x1r = y12r + y14i; + x1i = y12i - y14r; + x0r = y13r - y15i; + x0i = y13i + y15r; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + a[28] = x1r - x2i; + a[29] = x1i + x2r; + a[30] = x1r + x2i; + a[31] = x1i - x2r; +} + +void cftf081 (double* a, double* w) +{ + double wn4r, x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i, + y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i, + y4r, y4i, y5r, y5i, y6r, y6i, y7r, y7i; + + wn4r = w[1]; + x0r = a[0] + a[8]; + x0i = a[1] + a[9]; + x1r = a[0] - a[8]; + x1i = a[1] - a[9]; + x2r = a[4] + a[12]; + x2i = a[5] + a[13]; + x3r = a[4] - a[12]; + x3i = a[5] - a[13]; + y0r = x0r + x2r; + y0i = x0i + x2i; + y2r = x0r - x2r; + y2i = x0i - x2i; + y1r = x1r - x3i; + y1i = x1i + x3r; + y3r = x1r + x3i; + y3i = x1i - x3r; + x0r = a[2] + a[10]; + x0i = a[3] + a[11]; + x1r = a[2] - a[10]; + x1i = a[3] - a[11]; + x2r = a[6] + a[14]; + x2i = a[7] + a[15]; + x3r = a[6] - a[14]; + x3i = a[7] - a[15]; + y4r = x0r + x2r; + y4i = x0i + x2i; + y6r = x0r - x2r; + y6i = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + x2r = x1r + x3i; + x2i = x1i - x3r; + y5r = wn4r * (x0r - x0i); + y5i = wn4r * (x0r + x0i); + y7r = wn4r * (x2r - x2i); + y7i = wn4r * (x2r + x2i); + a[8] = y1r + y5r; + a[9] = y1i + y5i; + a[10] = y1r - y5r; + a[11] = y1i - y5i; + a[12] = y3r - y7i; + a[13] = y3i + y7r; + a[14] = y3r + y7i; + a[15] = y3i - y7r; + a[0] = y0r + y4r; + a[1] = y0i + y4i; + a[2] = y0r - y4r; + a[3] = y0i - y4i; + a[4] = y2r - y6i; + a[5] = y2i + y6r; + a[6] = y2r + y6i; + a[7] = y2i - y6r; +} + +void cftf082 (double* a, double* w) +{ + double wn4r, wk1r, wk1i, x0r, x0i, x1r, x1i, + y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i, + y4r, y4i, y5r, y5i, y6r, y6i, y7r, y7i; + + wn4r = w[1]; + wk1r = w[2]; + wk1i = w[3]; + y0r = a[0] - a[9]; + y0i = a[1] + a[8]; + y1r = a[0] + a[9]; + y1i = a[1] - a[8]; + x0r = a[4] - a[13]; + x0i = a[5] + a[12]; + y2r = wn4r * (x0r - x0i); + y2i = wn4r * (x0i + x0r); + x0r = a[4] + a[13]; + x0i = a[5] - a[12]; + y3r = wn4r * (x0r - x0i); + y3i = wn4r * (x0i + x0r); + x0r = a[2] - a[11]; + x0i = a[3] + a[10]; + y4r = wk1r * x0r - wk1i * x0i; + y4i = wk1r * x0i + wk1i * x0r; + x0r = a[2] + a[11]; + x0i = a[3] - a[10]; + y5r = wk1i * x0r - wk1r * x0i; + y5i = wk1i * x0i + wk1r * x0r; + x0r = a[6] - a[15]; + x0i = a[7] + a[14]; + y6r = wk1i * x0r - wk1r * x0i; + y6i = wk1i * x0i + wk1r * x0r; + x0r = a[6] + a[15]; + x0i = a[7] - a[14]; + y7r = wk1r * x0r - wk1i * x0i; + y7i = wk1r * x0i + wk1i * x0r; + x0r = y0r + y2r; + x0i = y0i + y2i; + x1r = y4r + y6r; + x1i = y4i + y6i; + a[0] = x0r + x1r; + a[1] = x0i + x1i; + a[2] = x0r - x1r; + a[3] = x0i - x1i; + x0r = y0r - y2r; + x0i = y0i - y2i; + x1r = y4r - y6r; + x1i = y4i - y6i; + a[4] = x0r - x1i; + a[5] = x0i + x1r; + a[6] = x0r + x1i; + a[7] = x0i - x1r; + x0r = y1r - y3i; + x0i = y1i + y3r; + x1r = y5r - y7r; + x1i = y5i - y7i; + a[8] = x0r + x1r; + a[9] = x0i + x1i; + a[10] = x0r - x1r; + a[11] = x0i - x1i; + x0r = y1r + y3i; + x0i = y1i - y3r; + x1r = y5r + y7r; + x1i = y5i + y7i; + a[12] = x0r - x1i; + a[13] = x0i + x1r; + a[14] = x0r + x1i; + a[15] = x0i - x1r; +} + +void cftf040 (double* a) +{ + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + x0r = a[0] + a[4]; + x0i = a[1] + a[5]; + x1r = a[0] - a[4]; + x1i = a[1] - a[5]; + x2r = a[2] + a[6]; + x2i = a[3] + a[7]; + x3r = a[2] - a[6]; + x3i = a[3] - a[7]; + a[0] = x0r + x2r; + a[1] = x0i + x2i; + a[2] = x1r - x3i; + a[3] = x1i + x3r; + a[4] = x0r - x2r; + a[5] = x0i - x2i; + a[6] = x1r + x3i; + a[7] = x1i - x3r; +} + +void cftb040 (double* a) +{ + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + x0r = a[0] + a[4]; + x0i = a[1] + a[5]; + x1r = a[0] - a[4]; + x1i = a[1] - a[5]; + x2r = a[2] + a[6]; + x2i = a[3] + a[7]; + x3r = a[2] - a[6]; + x3i = a[3] - a[7]; + a[0] = x0r + x2r; + a[1] = x0i + x2i; + a[2] = x1r + x3i; + a[3] = x1i - x3r; + a[4] = x0r - x2r; + a[5] = x0i - x2i; + a[6] = x1r - x3i; + a[7] = x1i + x3r; +} + +void cftx020 (double* a) +{ + double x0r, x0i; + + x0r = a[0] - a[2]; + x0i = a[1] - a[3]; + a[0] += a[2]; + a[1] += a[3]; + a[2] = x0r; + a[3] = x0i; +} + +void rftfsub (int n, double* a, int nc, double* c) +{ + int j, k, kk, ks, m; + double wkr, wki, xr, xi, yr, yi; + + m = n >> 1; + ks = 2 * nc / m; + kk = 0; + for (j = 2; j < m; j += 2) + { + k = n - j; + kk += ks; + wkr = 0.5 - c[nc - kk]; + wki = c[kk]; + xr = a[j] - a[k]; + xi = a[j + 1] + a[k + 1]; + yr = wkr * xr - wki * xi; + yi = wkr * xi + wki * xr; + a[j] -= yr; + a[j + 1] -= yi; + a[k] += yr; + a[k + 1] -= yi; + } +} + +void rftbsub (int n, double* a, int nc, double* c) +{ + int j, k, kk, ks, m; + double wkr, wki, xr, xi, yr, yi; + + m = n >> 1; + ks = 2 * nc / m; + kk = 0; + for (j = 2; j < m; j += 2) + { + k = n - j; + kk += ks; + wkr = 0.5 - c[nc - kk]; + wki = c[kk]; + xr = a[j] - a[k]; + xi = a[j + 1] + a[k + 1]; + yr = wkr * xr + wki * xi; + yi = wkr * xi - wki * xr; + a[j] -= yr; + a[j + 1] -= yi; + a[k] += yr; + a[k + 1] -= yi; + } +} + +void dctsub (int n, double* a, int nc, double* c) +{ + int j, k, kk, ks, m; + double wkr, wki, xr; + + m = n >> 1; + ks = nc / n; + kk = 0; + for (j = 1; j < m; j++) + { + k = n - j; + kk += ks; + wkr = c[kk] - c[nc - kk]; + wki = c[kk] + c[nc - kk]; + xr = wki * a[j] - wkr * a[k]; + a[j] = wkr * a[j] + wki * a[k]; + a[k] = xr; + } + a[m] *= c[0]; +} + +void dstsub (int n, double* a, int nc, double* c) +{ + int j, k, kk, ks, m; + double wkr, wki, xr; + + m = n >> 1; + ks = nc / n; + kk = 0; + for (j = 1; j < m; j++) + { + k = n - j; + kk += ks; + wkr = c[kk] - c[nc - kk]; + wki = c[kk] + c[nc - kk]; + xr = wki * a[k] - wkr * a[j]; + a[k] = wkr * a[k] + wki * a[j]; + a[j] = xr; + } + a[m] *= c[0]; +} + +} // namespace yup diff --git a/modules/yup_dsp/frequency/yup_OouraFFT8g.cpp b/modules/yup_dsp/frequency/yup_OouraFFT8g_float.cpp similarity index 99% rename from modules/yup_dsp/frequency/yup_OouraFFT8g.cpp rename to modules/yup_dsp/frequency/yup_OouraFFT8g_float.cpp index 8f6f138d1..af9a9d395 100644 --- a/modules/yup_dsp/frequency/yup_OouraFFT8g.cpp +++ b/modules/yup_dsp/frequency/yup_OouraFFT8g_float.cpp @@ -859,9 +859,6 @@ void makect (int nc, int* ip, float* c) #ifndef CDFT_4THREADS_BEGIN_N #define CDFT_4THREADS_BEGIN_N 65536 #endif -#include -#include -#include #define cdft_thread_t pthread_t #define cdft_thread_create(thp, func, argp) \ { \ @@ -890,9 +887,6 @@ void makect (int nc, int* ip, float* c) #define CDFT_4THREADS_BEGIN_N 524288 #endif #define NOMINMAX -#include -#include -#include #define cdft_thread_t HANDLE #define cdft_thread_create(thp, func, argp) \ { \ diff --git a/modules/yup_dsp/onsets/yup_Spectrogram.h b/modules/yup_dsp/onsets/yup_Spectrogram.h index 0abbfdeb9..465e347f3 100644 --- a/modules/yup_dsp/onsets/yup_Spectrogram.h +++ b/modules/yup_dsp/onsets/yup_Spectrogram.h @@ -152,7 +152,7 @@ class Spectrogram std::vector magnitude; // [numFrames x numBins] std::vector lgd; // [numFrames x (fftSize/2)] - FFTProcessor fft; + FFTProcessor fft; std::vector fftInput; std::vector fftOutput; }; diff --git a/modules/yup_dsp/yup_dsp.cpp b/modules/yup_dsp/yup_dsp.cpp index a798d0c78..f2d62390d 100644 --- a/modules/yup_dsp/yup_dsp.cpp +++ b/modules/yup_dsp/yup_dsp.cpp @@ -63,6 +63,7 @@ #if ! YUP_FFT_FOUND_BACKEND && YUP_ENABLE_PFFFT && YUP_MODULE_AVAILABLE_pffft_library #include +#include #define YUP_FFT_USING_PFFFT 1 #define YUP_FFT_FOUND_BACKEND 1 #endif @@ -102,5 +103,6 @@ //============================================================================== #if YUP_ENABLE_OOURA && YUP_FFT_USING_OOURA -#include "frequency/yup_OouraFFT8g.cpp" +#include "frequency/yup_OouraFFT8g_float.cpp" +#include "frequency/yup_OouraFFT8g_double.cpp" #endif diff --git a/tests/yup_dsp/yup_FFTProcessor.cpp b/tests/yup_dsp/yup_FFTProcessor.cpp index e5e71ad24..3ae30c732 100644 --- a/tests/yup_dsp/yup_FFTProcessor.cpp +++ b/tests/yup_dsp/yup_FFTProcessor.cpp @@ -38,51 +38,56 @@ namespace yup::test // output[size] = Nyquist real, output[size+1] = Nyquist imaginary (always 0.0) //============================================================================== -class FFTProcessorValidation : public ::testing::Test +/** Shared validation fixture, instantiated once per sample precision. */ +template +class FFTProcessorValidationT : public ::testing::Test { protected: + using SampleType = SampleTypeT; + using ProcessorType = FFTProcessor; + void SetUp() override { generator.seed (42); // Fixed seed for reproducible tests } - // Generate random float in range [-1, 1] - float randomFloat() + // Generate random sample in range [-1, 1] + SampleType randomSample() { - std::uniform_real_distribution dist (-1.0f, 1.0f); + std::uniform_real_distribution dist (SampleType (-1), SampleType (1)); return dist (generator); } // Fill buffer with random real values - void generateRandomReal (float* buffer, int size) + void generateRandomReal (SampleType* buffer, int size) { for (int i = 0; i < size; ++i) - buffer[i] = randomFloat(); + buffer[i] = randomSample(); } // Fill buffer with random complex values (interleaved real/imag) - void generateRandomComplex (float* buffer, int size) + void generateRandomComplex (SampleType* buffer, int size) { for (int i = 0; i < size * 2; ++i) - buffer[i] = randomFloat(); + buffer[i] = randomSample(); } // Reference discrete Fourier transform for real input (produces full spectrum) - void computeReferenceDFT (const float* realInput, float* complexOutput, int size, bool inverse = false) + void computeReferenceDFT (const SampleType* realInput, SampleType* complexOutput, int size, bool inverse = false) { - const float sign = inverse ? 1.0f : -1.0f; - const float twoPi = 2.0f * MathConstants::pi; + const SampleType sign = inverse ? SampleType (1) : SampleType (-1); + const SampleType twoPi = SampleType (2) * MathConstants::pi; for (int k = 0; k < size; ++k) { - float realSum = 0.0f; - float imagSum = 0.0f; + SampleType realSum = SampleType (0); + SampleType imagSum = SampleType (0); for (int n = 0; n < size; ++n) { - const float angle = sign * twoPi * static_cast (k * n) / static_cast (size); - const float cosVal = std::cos (angle); - const float sinVal = std::sin (angle); + const SampleType angle = sign * twoPi * static_cast (k * n) / static_cast (size); + const SampleType cosVal = std::cos (angle); + const SampleType sinVal = std::sin (angle); realSum += realInput[n] * cosVal; imagSum += realInput[n] * sinVal; @@ -94,22 +99,22 @@ class FFTProcessorValidation : public ::testing::Test } // Reference DFT for real input producing standard interleaved format - void computeReferenceRealDFT (const float* realInput, float* interleavedOutput, int size) + void computeReferenceRealDFT (const SampleType* realInput, SampleType* interleavedOutput, int size) { - const float twoPi = 2.0f * MathConstants::pi; + const SampleType twoPi = SampleType (2) * MathConstants::pi; const int numBins = size / 2 + 1; // Compute all frequency bins (k=0 to size/2) for (int k = 0; k < numBins; ++k) { - float realSum = 0.0f; - float imagSum = 0.0f; + SampleType realSum = SampleType (0); + SampleType imagSum = SampleType (0); for (int n = 0; n < size; ++n) { - const float angle = -twoPi * static_cast (k * n) / static_cast (size); - const float cosVal = std::cos (angle); - const float sinVal = std::sin (angle); + const SampleType angle = -twoPi * static_cast (k * n) / static_cast (size); + const SampleType cosVal = std::cos (angle); + const SampleType sinVal = std::sin (angle); realSum += realInput[n] * cosVal; imagSum += realInput[n] * sinVal; @@ -121,14 +126,14 @@ class FFTProcessorValidation : public ::testing::Test } // Reference inverse DFT for hermitian-symmetric input producing real output - void computeReferenceRealIDFT (const float* complexInput, float* realOutput, int size) + void computeReferenceRealIDFT (const SampleType* complexInput, SampleType* realOutput, int size) { - const float twoPi = 2.0f * MathConstants::pi; + const SampleType twoPi = SampleType (2) * MathConstants::pi; const int numBins = size / 2 + 1; for (int n = 0; n < size; ++n) { - float sum = 0.0f; + SampleType sum = SampleType (0); // DC component sum += complexInput[0]; @@ -136,47 +141,47 @@ class FFTProcessorValidation : public ::testing::Test // Other frequencies (except Nyquist) for (int k = 1; k < numBins - 1; ++k) { - const float angle = twoPi * static_cast (k * n) / static_cast (size); - const float cosVal = std::cos (angle); - const float sinVal = std::sin (angle); + const SampleType angle = twoPi * static_cast (k * n) / static_cast (size); + const SampleType cosVal = std::cos (angle); + const SampleType sinVal = std::sin (angle); - const float real = complexInput[k * 2]; - const float imag = complexInput[k * 2 + 1]; + const SampleType real = complexInput[k * 2]; + const SampleType imag = complexInput[k * 2 + 1]; - sum += 2.0f * (real * cosVal + imag * sinVal); + sum += SampleType (2) * (real * cosVal + imag * sinVal); } // Nyquist component (if size is even) if (size % 2 == 0) { const int nyquistBin = size / 2; - const float nyquistAngle = twoPi * static_cast (nyquistBin * n) / static_cast (size); + const SampleType nyquistAngle = twoPi * static_cast (nyquistBin * n) / static_cast (size); sum += complexInput[nyquistBin * 2] * std::cos (nyquistAngle); } - realOutput[n] = sum / static_cast (size); + realOutput[n] = sum / static_cast (size); } } // Reference DFT for complex input (interleaved format) - void computeReferenceComplexDFT (const float* complexInput, float* complexOutput, int size, bool inverse = false) + void computeReferenceComplexDFT (const SampleType* complexInput, SampleType* complexOutput, int size, bool inverse = false) { - const float sign = inverse ? 1.0f : -1.0f; - const float twoPi = 2.0f * MathConstants::pi; + const SampleType sign = inverse ? SampleType (1) : SampleType (-1); + const SampleType twoPi = SampleType (2) * MathConstants::pi; for (int k = 0; k < size; ++k) { - float realSum = 0.0f; - float imagSum = 0.0f; + SampleType realSum = SampleType (0); + SampleType imagSum = SampleType (0); for (int n = 0; n < size; ++n) { - const float angle = sign * twoPi * static_cast (k * n) / static_cast (size); - const float cosVal = std::cos (angle); - const float sinVal = std::sin (angle); + const SampleType angle = sign * twoPi * static_cast (k * n) / static_cast (size); + const SampleType cosVal = std::cos (angle); + const SampleType sinVal = std::sin (angle); - const float inputReal = complexInput[n * 2]; - const float inputImag = complexInput[n * 2 + 1]; + const SampleType inputReal = complexInput[n * 2]; + const SampleType inputImag = complexInput[n * 2 + 1]; realSum += inputReal * cosVal - inputImag * sinVal; imagSum += inputReal * sinVal + inputImag * cosVal; @@ -188,7 +193,7 @@ class FFTProcessorValidation : public ::testing::Test } // Check if two arrays are approximately equal - bool areArraysClose (const float* a, const float* b, int size, float tolerance = 1e-3f) + bool areArraysClose (const SampleType* a, const SampleType* b, int size, SampleType tolerance = defaultTolerance) { for (int i = 0; i < size; ++i) { @@ -202,21 +207,25 @@ class FFTProcessorValidation : public ::testing::Test } std::mt19937 generator; - static constexpr float defaultTolerance = 1e-3f; + static constexpr SampleType defaultTolerance = SampleType (1e-3); + static constexpr SampleType tightTolerance = std::is_same_v ? SampleType (1e-9) : SampleType (1e-4); }; +using FFTProcessorValidation = FFTProcessorValidationT; +using FFTProcessorDoubleValidation = FFTProcessorValidationT; + //============================================================================== TEST_F (FFTProcessorValidation, StandardFormatValidation) { const int size = 64; - FFTProcessor processor (size); + ProcessorType processor (size); // Test 1: Impulse should produce flat spectrum { - std::vector impulse (size, 0.0f); - impulse[0] = 1.0f; + std::vector impulse (size, SampleType (0)); + impulse[0] = SampleType (1); - std::vector output (size * 2); + std::vector output (size * 2); processor.performRealFFTForward (impulse.data(), output.data()); // In standard format: DC=[1,0], Nyquist=[1,0] at output[size], output[size+1] @@ -235,12 +244,12 @@ TEST_F (FFTProcessorValidation, StandardFormatValidation) // Test 2: DC signal should have energy only at DC { - std::vector dcSignal (size, 1.0f); + std::vector dcSignal (size, SampleType (1)); - std::vector output (size * 2); + std::vector output (size * 2); processor.performRealFFTForward (dcSignal.data(), output.data()); - EXPECT_NEAR (output[0], static_cast (size), defaultTolerance) << "DC real should equal sum"; + EXPECT_NEAR (output[0], static_cast (size), defaultTolerance) << "DC real should equal sum"; EXPECT_NEAR (output[1], 0.0f, defaultTolerance) << "DC imaginary should be 0.0"; EXPECT_NEAR (output[size], 0.0f, defaultTolerance) << "Nyquist real should be 0.0"; EXPECT_NEAR (output[size + 1], 0.0f, defaultTolerance) << "Nyquist imaginary should be 0.0"; @@ -255,16 +264,16 @@ TEST_F (FFTProcessorValidation, StandardFormatValidation) // Test 3: Alternating pattern should have energy at Nyquist { - std::vector alternating (size); + std::vector alternating (size); for (int i = 0; i < size; ++i) - alternating[i] = (i % 2 == 0) ? 1.0f : -1.0f; + alternating[i] = (i % 2 == 0) ? SampleType (1) : SampleType (-1); - std::vector output (size * 2); + std::vector output (size * 2); processor.performRealFFTForward (alternating.data(), output.data()); EXPECT_NEAR (output[0], 0.0f, defaultTolerance) << "DC real should be 0.0 for alternating"; EXPECT_NEAR (output[1], 0.0f, defaultTolerance) << "DC imaginary should be 0.0"; - EXPECT_NEAR (output[size], static_cast (size), defaultTolerance) << "Nyquist should equal size"; + EXPECT_NEAR (output[size], static_cast (size), defaultTolerance) << "Nyquist should equal size"; EXPECT_NEAR (output[size + 1], 0.0f, defaultTolerance) << "Nyquist imaginary should be 0.0"; // All other bins should be zero @@ -281,11 +290,11 @@ TEST_F (FFTProcessorValidation, RealForwardTransformAccuracy) for (int order = 6; order <= 8; ++order) // Reduced range for debugging { const int size = 1 << order; - FFTProcessor processor (size); + ProcessorType processor (size); - std::vector input (size); - std::vector fftOutput (size * 2); - std::vector referenceOutput (size * 2); + std::vector input (size); + std::vector fftOutput (size * 2); + std::vector referenceOutput (size * 2); generateRandomReal (input.data(), size); computeReferenceRealDFT (input.data(), referenceOutput.data(), size); @@ -304,12 +313,12 @@ TEST_F (FFTProcessorValidation, RealInverseTransformAccuracy) for (int order = 6; order <= 8; ++order) // Reduced range for debugging { const int size = 1 << order; - FFTProcessor processor (size); + ProcessorType processor (size); // Test roundtrip: original -> forward -> inverse -> should equal original - std::vector originalInput (size); - std::vector complexData (size * 2); - std::vector reconstructed (size); + std::vector originalInput (size); + std::vector complexData (size * 2); + std::vector reconstructed (size); generateRandomReal (originalInput.data(), size); @@ -320,7 +329,7 @@ TEST_F (FFTProcessorValidation, RealInverseTransformAccuracy) processor.performRealFFTInverse (complexData.data(), reconstructed.data()); // For roundtrip test, we need to handle scaling - processor.setScaling (FFTProcessor::FFTScaling::asymmetric); + processor.setScaling (ProcessorType::FFTScaling::asymmetric); processor.performRealFFTForward (originalInput.data(), complexData.data()); processor.performRealFFTInverse (complexData.data(), reconstructed.data()); @@ -328,7 +337,7 @@ TEST_F (FFTProcessorValidation, RealInverseTransformAccuracy) << "Real inverse FFT roundtrip failed for size " << size << " (order " << order << ")"; // Reset scaling - processor.setScaling (FFTProcessor::FFTScaling::none); + processor.setScaling (ProcessorType::FFTScaling::none); } } @@ -336,14 +345,14 @@ TEST_F (FFTProcessorValidation, ComplexForwardTransformAccuracy) { // Test with simple known cases first const int size = 64; - FFTProcessor processor (size); + ProcessorType processor (size); // Test with impulse - std::vector impulse (size * 2, 0.0f); - impulse[0] = 1.0f; // Real part of first sample - impulse[1] = 0.0f; // Imag part of first sample + std::vector impulse (size * 2, SampleType (0)); + impulse[0] = SampleType (1); // Real part of first sample + impulse[1] = SampleType (0); // Imag part of first sample - std::vector output (size * 2); + std::vector output (size * 2); processor.performComplexFFTForward (impulse.data(), output.data()); // For impulse, all bins should have real=1.0, imag=0.0 @@ -359,12 +368,12 @@ TEST_F (FFTProcessorValidation, ComplexForwardTransformAccuracy) TEST_F (FFTProcessorValidation, ComplexInverseTransformAccuracy) { const int size = 64; - FFTProcessor processor (size); - processor.setScaling (FFTProcessor::FFTScaling::asymmetric); + ProcessorType processor (size); + processor.setScaling (ProcessorType::FFTScaling::asymmetric); - std::vector originalInput (size * 2); - std::vector transformed (size * 2); - std::vector reconstructed (size * 2); + std::vector originalInput (size * 2); + std::vector transformed (size * 2); + std::vector reconstructed (size * 2); generateRandomComplex (originalInput.data(), size); @@ -383,12 +392,12 @@ TEST_F (FFTProcessorValidation, RealRoundtripConsistency) for (int order = 6; order <= 8; ++order) { const int size = 1 << order; - FFTProcessor processor (size); - processor.setScaling (FFTProcessor::FFTScaling::asymmetric); + ProcessorType processor (size); + processor.setScaling (ProcessorType::FFTScaling::asymmetric); - std::vector original (size); - std::vector frequency (size * 2); - std::vector restored (size); + std::vector original (size); + std::vector frequency (size * 2); + std::vector restored (size); generateRandomReal (original.data(), size); @@ -406,12 +415,12 @@ TEST_F (FFTProcessorValidation, ComplexRoundtripConsistency) for (int order = 6; order <= 8; ++order) { const int size = 1 << order; - FFTProcessor processor (size); - processor.setScaling (FFTProcessor::FFTScaling::asymmetric); + ProcessorType processor (size); + processor.setScaling (ProcessorType::FFTScaling::asymmetric); - std::vector original (size * 2); - std::vector frequency (size * 2); - std::vector restored (size * 2); + std::vector original (size * 2); + std::vector frequency (size * 2); + std::vector restored (size * 2); generateRandomComplex (original.data(), size); @@ -427,17 +436,17 @@ TEST_F (FFTProcessorValidation, ComplexRoundtripConsistency) TEST_F (FFTProcessorValidation, DCAndNyquistBehavior) { const int size = 64; - FFTProcessor processor (size); + ProcessorType processor (size); // Test DC component { - std::vector dcInput (size, 1.0f); // All ones - std::vector output (size * 2); + std::vector dcInput (size, SampleType (1)); // All ones + std::vector output (size * 2); processor.performRealFFTForward (dcInput.data(), output.data()); // DC should have magnitude of size, other bins should be near zero - EXPECT_NEAR (output[0], static_cast (size), defaultTolerance) << "DC component incorrect"; + EXPECT_NEAR (output[0], static_cast (size), defaultTolerance) << "DC component incorrect"; EXPECT_NEAR (output[1], 0.0f, defaultTolerance) << "DC imaginary should be zero"; for (int i = 1; i < size / 2; ++i) @@ -449,15 +458,15 @@ TEST_F (FFTProcessorValidation, DCAndNyquistBehavior) // Test Nyquist frequency (alternating pattern) { - std::vector nyquistInput (size); + std::vector nyquistInput (size); for (int i = 0; i < size; ++i) - nyquistInput[i] = (i % 2 == 0) ? 1.0f : -1.0f; + nyquistInput[i] = (i % 2 == 0) ? SampleType (1) : SampleType (-1); - std::vector output (size * 2); + std::vector output (size * 2); processor.performRealFFTForward (nyquistInput.data(), output.data()); // In standard format, Nyquist is stored at output[size] - float nyquistMagnitude = std::abs (output[size]); + SampleType nyquistMagnitude = std::abs (output[size]); EXPECT_GT (nyquistMagnitude, 1.0f) << "Nyquist component should be significant for alternating pattern"; // The DC component should be zero for alternating pattern @@ -468,11 +477,11 @@ TEST_F (FFTProcessorValidation, DCAndNyquistBehavior) TEST_F (FFTProcessorValidation, LinearityProperty) { const int size = 128; - FFTProcessor processor (size); + ProcessorType processor (size); - std::vector signal1 (size); - std::vector signal2 (size); - std::vector combined (size); + std::vector signal1 (size); + std::vector signal2 (size); + std::vector combined (size); generateRandomReal (signal1.data(), size); generateRandomReal (signal2.data(), size); @@ -480,10 +489,10 @@ TEST_F (FFTProcessorValidation, LinearityProperty) for (int i = 0; i < size; ++i) combined[i] = signal1[i] + signal2[i]; - std::vector fft1 (size * 2); - std::vector fft2 (size * 2); - std::vector fftCombined (size * 2); - std::vector fftSum (size * 2); + std::vector fft1 (size * 2); + std::vector fft2 (size * 2); + std::vector fftCombined (size * 2); + std::vector fftSum (size * 2); processor.performRealFFTForward (signal1.data(), fft1.data()); processor.performRealFFTForward (signal2.data(), fft2.data()); @@ -502,16 +511,16 @@ TEST_F (FFTProcessorValidation, ScalingBehavior) const int size = 64; // Test different scaling modes - for (auto scaling : { FFTProcessor::FFTScaling::none, - FFTProcessor::FFTScaling::unitary, - FFTProcessor::FFTScaling::asymmetric }) + for (auto scaling : { ProcessorType::FFTScaling::none, + ProcessorType::FFTScaling::unitary, + ProcessorType::FFTScaling::asymmetric }) { - FFTProcessor processor (size); + ProcessorType processor (size); processor.setScaling (scaling); - std::vector input (size); - std::vector frequency (size * 2); - std::vector restored (size); + std::vector input (size); + std::vector frequency (size * 2); + std::vector restored (size); generateRandomReal (input.data(), size); @@ -519,13 +528,13 @@ TEST_F (FFTProcessorValidation, ScalingBehavior) processor.performRealFFTInverse (frequency.data(), restored.data()); // With proper scaling, we should get back the original - float tolerance = (scaling == FFTProcessor::FFTScaling::none) ? 1.0f : defaultTolerance; + SampleType tolerance = (scaling == ProcessorType::FFTScaling::none) ? SampleType (1) : defaultTolerance; - if (scaling == FFTProcessor::FFTScaling::none) + if (scaling == ProcessorType::FFTScaling::none) { // Without scaling, result should be multiplied by size for (int i = 0; i < size; ++i) - restored[i] /= static_cast (size); + restored[i] /= static_cast (size); } EXPECT_TRUE (areArraysClose (input.data(), restored.data(), size, tolerance)) @@ -535,7 +544,7 @@ TEST_F (FFTProcessorValidation, ScalingBehavior) TEST_F (FFTProcessorValidation, BackendIdentification) { - FFTProcessor processor (64); + ProcessorType processor (64); String backendName = processor.getBackendName(); EXPECT_FALSE (backendName.isEmpty()) << "Backend name should not be empty"; @@ -565,10 +574,10 @@ TEST_F (FFTProcessorValidation, EdgeCaseSizes) for (int size : { 64, 128, 1024, 2048, 4096 }) { EXPECT_NO_THROW ({ - FFTProcessor processor (size); + ProcessorType processor (size); - std::vector input (size); - std::vector output (size * 2); + std::vector input (size); + std::vector output (size * 2); generateRandomReal (input.data(), size); processor.performRealFFTForward (input.data(), output.data()); @@ -577,4 +586,169 @@ TEST_F (FFTProcessorValidation, EdgeCaseSizes) } } +//============================================================================== +// Double precision validation - the same public interface at a higher precision +//============================================================================== + +TEST_F (FFTProcessorDoubleValidation, BackendIdentification) +{ + ProcessorType processor (64); + const String backendName = processor.getBackendName(); + + EXPECT_FALSE (backendName.isEmpty()) << "Backend name should not be empty"; + EXPECT_NE (backendName, "Unknown") << "Backend should be identified"; +} + +TEST_F (FFTProcessorDoubleValidation, RealForwardTransformAccuracy) +{ + for (int order = 6; order <= 9; ++order) + { + const int size = 1 << order; + ProcessorType processor (size); + + std::vector input (size); + std::vector fftOutput (size * 2); + std::vector referenceOutput (size * 2); + + generateRandomReal (input.data(), size); + computeReferenceRealDFT (input.data(), referenceOutput.data(), size); + + processor.performRealFFTForward (input.data(), fftOutput.data()); + + const int numBins = size / 2 + 1; + EXPECT_TRUE (areArraysClose (fftOutput.data(), referenceOutput.data(), numBins * 2, tightTolerance)) + << "Double real forward FFT failed for size " << size; + } +} + +TEST_F (FFTProcessorDoubleValidation, RealRoundtripConsistency) +{ + for (int order = 6; order <= 9; ++order) + { + const int size = 1 << order; + ProcessorType processor (size); + processor.setScaling (ProcessorType::FFTScaling::asymmetric); + + std::vector original (size); + std::vector frequency (size * 2); + std::vector restored (size); + + generateRandomReal (original.data(), size); + + processor.performRealFFTForward (original.data(), frequency.data()); + processor.performRealFFTInverse (frequency.data(), restored.data()); + + EXPECT_TRUE (areArraysClose (original.data(), restored.data(), size, tightTolerance)) + << "Double real roundtrip failed for size " << size; + } +} + +TEST_F (FFTProcessorDoubleValidation, ComplexRoundtripConsistency) +{ + for (int order = 6; order <= 9; ++order) + { + const int size = 1 << order; + ProcessorType processor (size); + processor.setScaling (ProcessorType::FFTScaling::asymmetric); + + std::vector original (size * 2); + std::vector frequency (size * 2); + std::vector restored (size * 2); + + generateRandomComplex (original.data(), size); + + processor.performComplexFFTForward (original.data(), frequency.data()); + processor.performComplexFFTInverse (frequency.data(), restored.data()); + + EXPECT_TRUE (areArraysClose (original.data(), restored.data(), size * 2, tightTolerance)) + << "Double complex roundtrip failed for size " << size; + } +} + +TEST_F (FFTProcessorDoubleValidation, DcAndNyquistBinsAreExact) +{ + const int size = 64; + ProcessorType processor (size); + + // An impulse carries unit energy in every bin + { + std::vector impulse (size, SampleType (0)); + impulse[0] = SampleType (1); + + std::vector output (size * 2); + processor.performRealFFTForward (impulse.data(), output.data()); + + EXPECT_NEAR (output[0], 1.0, tightTolerance); + EXPECT_NEAR (output[1], 0.0, tightTolerance); + EXPECT_NEAR (output[size], 1.0, tightTolerance); + EXPECT_NEAR (output[size + 1], 0.0, tightTolerance); + } + + // An alternating pattern carries all of its energy at Nyquist + { + std::vector alternating (size); + for (int i = 0; i < size; ++i) + alternating[i] = (i % 2 == 0) ? SampleType (1) : SampleType (-1); + + std::vector output (size * 2); + processor.performRealFFTForward (alternating.data(), output.data()); + + EXPECT_NEAR (output[0], 0.0, tightTolerance); + EXPECT_NEAR (output[size], static_cast (size), tightTolerance); + EXPECT_NEAR (output[size + 1], 0.0, tightTolerance); + } +} + +TEST_F (FFTProcessorDoubleValidation, ScalingBehavior) +{ + const int size = 64; + + for (auto scaling : { ProcessorType::FFTScaling::none, + ProcessorType::FFTScaling::unitary, + ProcessorType::FFTScaling::asymmetric }) + { + ProcessorType processor (size); + processor.setScaling (scaling); + EXPECT_EQ (processor.getScaling(), scaling); + + std::vector input (size); + std::vector frequency (size * 2); + std::vector restored (size); + + generateRandomReal (input.data(), size); + + processor.performRealFFTForward (input.data(), frequency.data()); + processor.performRealFFTInverse (frequency.data(), restored.data()); + + SampleType tolerance = (scaling == ProcessorType::FFTScaling::none) ? SampleType (1000) : tightTolerance; + + if (scaling == ProcessorType::FFTScaling::none) + { + // Without scaling, the roundtrip result is scaled by the FFT size + for (int i = 0; i < size; ++i) + restored[i] /= static_cast (size); + } + + EXPECT_TRUE (areArraysClose (input.data(), restored.data(), size, tolerance)) + << "Double scaling behavior incorrect for scaling mode " << static_cast (scaling); + } +} + +TEST_F (FFTProcessorDoubleValidation, EdgeCaseSizes) +{ + for (int size : { 64, 128, 1024, 2048, 4096 }) + { + EXPECT_NO_THROW ({ + ProcessorType processor (size); + + std::vector input (size); + std::vector output (size * 2); + + generateRandomReal (input.data(), size); + processor.performRealFFTForward (input.data(), output.data()); + }) << "Double FFT failed for edge case size " + << size; + } +} + } // namespace yup::test diff --git a/tests/yup_dsp/yup_NoiseGenerators.cpp b/tests/yup_dsp/yup_NoiseGenerators.cpp index 14806ac02..bd97e1b98 100644 --- a/tests/yup_dsp/yup_NoiseGenerators.cpp +++ b/tests/yup_dsp/yup_NoiseGenerators.cpp @@ -78,7 +78,7 @@ class NoiseGeneratorsTests : public ::testing::Test std::vector avgMagnitude (numBins, 0.0f); int numChunks = static_cast (samples.size()) / fftSize; - FFTProcessor fft (fftSize); + FFTProcessor fft (fftSize); std::vector fftInputData (fftSize); std::vector fftOutputData (fftSize * 2); std::vector window (fftSize); From ddea97ff3410484c7c40cf368af3f1bfe15bf074 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Sat, 12 Sep 2026 15:24:13 +0200 Subject: [PATCH 3/6] More spectrum improvement (cherry picked from commit c06f1c9662494c6a203220e9c9726ca52a260c43) --- CHANGELOG.md | 2 + .../displays/yup_SpectrogramComponent.cpp | 67 +-- .../displays/yup_SpectrogramComponent.h | 11 +- .../yup_SpectrumAnalyzerComponent.cpp | 236 +++------ .../displays/yup_SpectrumAnalyzerComponent.h | 16 +- .../displays/yup_SpectrumBinMapping.cpp | 224 ++++++++ .../displays/yup_SpectrumBinMapping.h | 187 +++++++ modules/yup_audio_gui/yup_audio_gui.cpp | 1 + modules/yup_audio_gui/yup_audio_gui.h | 1 + .../yup_SpectrumAnalyzerComponent.cpp | 101 ++++ .../yup_audio_gui/yup_SpectrumBinMapping.cpp | 498 ++++++++++++++++++ 11 files changed, 1118 insertions(+), 226 deletions(-) create mode 100644 modules/yup_audio_gui/displays/yup_SpectrumBinMapping.cpp create mode 100644 modules/yup_audio_gui/displays/yup_SpectrumBinMapping.h create mode 100644 tests/yup_audio_gui/yup_SpectrumBinMapping.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 3452cad4e..acd767b74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -171,6 +171,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - `SpectrogramComponent` now keeps its waterfall history on the GPU: a precompiled `.ysl` shader bundle (embedded in `yup_SpectrogramComponentShader.inc`, built with the `yup_shader_bundler` host tool) drives a single fullscreen-triangle `GpuRenderPass` (see `GpuPipeline`) that scrolls the previous frame down by the pending rows and writes the new rows with the color map applied entirely on the GPU, uploading only the raw magnitudes as a uniform buffer - no per-paint CPU pixel upload, no GPU texture creation, and no 2D canvas flush (if the bundle cannot be compiled no waterfall is rendered). Pending FFT rows are always consumed (applied or dropped) so the update queue can never accumulate. The log-frequency → FFT-bin mapping is precomputed once per configuration instead of recomputed with pow/log per row, and the frequency grid (lines + labels) is cached in an offscreen canvas and only re-rendered when the frequency range or size changes. The component now requires a GPU render context (the CPU `Image` fallback was removed). The component's per-frame `refreshDisplay` hook processes pending FFT rows, and the history is presented at a fractional vertical offset that advances at the FFT row rate, so the waterfall scrolls smoothly between rows instead of jumping a row per update; the offset is clamped to a single row so bursts of FFT rows can never push the waterfall off-screen. The scroll speed is adjustable via the new `setScrollSpeed()` multiplier (1.0 = realtime, 0.0 = paused). - `SpectrogramComponent` waterfall failures (shader bundle load, pipeline compile, and GPU pass encode/draw) are now reported via `Logger::outputDebugString` in all build configurations instead of silently dropping pending rows, and the waterfall texture's render resolution is exposed as the new `defaultSpectrogramRenderWidth` constant (2x the frequency-bin count - `getSpectrogramImage()` returns that full-resolution image). - Fixed `SpectrumAnalyzerState` never flagging FFT data as ready after a single bulk `pushSamples()`: the readiness check ran before the scoped FIFO write had committed (the `AbstractFifo::ScopedWrite` commits in its destructor), so `isFFTDataReady()` stayed false until a second push arrived. `pushSample()`/`pushSamples()` now commit the write before checking, so a pushed window is immediately available to `SpectrogramComponent::refreshDisplay()` instead of leaving the backlog untouched. +- `SpectrumAnalyzerComponent` and `SpectrogramComponent` no longer snap every display point to its nearest FFT bin, which rendered identical levels (a flat staircase) for all the points sharing one bin. The new `SpectrumBinMapping` helper (`displays/yup_SpectrumBinMapping.h`) holds the shared log-frequency → fractional FFT bin mapping and evaluates levels continuously: the three bins surrounding a fractional position are parabolically interpolated in the amplitude decibel domain, evaluated at that position rather than at the vertex of the parabola, with a monotone linear fallback where the neighbours are not concave so a steep bin pair cannot undershoot. Display bands are aggregated over their fractional edges - `peak` for the peak/RMS level modes, `sum` (the levels integrated across the band width, in bin units) for `powerDecibels` and `mean` for `powerSpectralDensity` - so bins entering or leaving a band no longer step the displayed level. In the power modes this makes the band power an integral over the band's bandwidth instead of a sum over the integer bins its edges happen to touch +- `SpectrumAnalyzerComponent` builds its spectrum outline once per pixel column, interpolating between the 512 smoothed display points, so the curve stays continuous at any component width and at HiDPI scales instead of following the 512-point polyline verbatim. The private `computeSpectrumPath (Path, …)` became `createSpectrumPath (const Rectangle&, bool)` returning a `Path`, removing its reliance on `Path` sharing its `rive::rcp` between copies #### Layout diff --git a/modules/yup_audio_gui/displays/yup_SpectrogramComponent.cpp b/modules/yup_audio_gui/displays/yup_SpectrogramComponent.cpp index 1c1654638..636912f4f 100644 --- a/modules/yup_audio_gui/displays/yup_SpectrogramComponent.cpp +++ b/modules/yup_audio_gui/displays/yup_SpectrogramComponent.cpp @@ -404,32 +404,11 @@ void SpectrogramComponent::processFFT() magnitudeBuffer[static_cast (binIndex)] = std::sqrt (real * real + imag * imag) * windowGain; } - // Map FFT bins to display bins using the precomputed logarithmic mapping. + // Map FFT bins to display bins using the precomputed logarithmic mapping. The bands are resolved + // across the fractional FFT bin domain, so neighbouring columns never snap to the same bin. for (int i = 0; i < spectrogramWidth; ++i) { - const auto& mapping = displayBinMapping[static_cast (i)]; - const float binSpan = mapping.endBin - mapping.startBin; - - float magnitude = 0.0f; - - if (binSpan <= 1.5f) - { - const int bin1 = jlimit (0, numBins - 1, static_cast (mapping.exactBin)); - const int bin2 = jlimit (0, numBins - 1, bin1 + 1); - const float fraction = mapping.exactBin - static_cast (bin1); - - const float mag1 = magnitudeBuffer[static_cast (bin1)]; - const float mag2 = magnitudeBuffer[static_cast (bin2)]; - magnitude = mag1 + fraction * (mag2 - mag1); - } - else - { - const int binStart = jlimit (0, numBins - 1, static_cast (mapping.startBin)); - const int binEnd = jlimit (0, numBins - 1, static_cast (mapping.endBin + 0.5f)); - - for (int binIndex = binStart; binIndex <= binEnd; ++binIndex) - magnitude = jmax (magnitude, magnitudeBuffer[static_cast (binIndex)]); - } + const float magnitude = binMapping.getBandLevel (magnitudeBuffer, i, SpectrumBinMapping::BandAggregation::peak); // Convert to decibels and normalize to [0, 1] float magnitudeDb = magnitude > 0.0f @@ -447,43 +426,9 @@ void SpectrogramComponent::processFFT() void SpectrogramComponent::updateFrequencyMapping() { - displayBinMapping.resize (static_cast (spectrogramWidth)); - - const int numDisplayBins = spectrogramWidth; - const float invLastBin = 1.0f / static_cast (numDisplayBins - 1); - - for (int i = 0; i < numDisplayBins; ++i) - { - const float proportion = static_cast (i) * invLastBin; - const float logFreq = logMinFrequency + proportion * (logMaxFrequency - logMinFrequency); - const float centerFreq = std::pow (10.0f, logFreq); - - const float prevProportion = static_cast (i - 1) * invLastBin; - const float nextProportion = static_cast (i + 1) * invLastBin; - - float freqRangeStart, freqRangeEnd; - - if (i == 0) - { - freqRangeStart = minFrequency; - freqRangeEnd = (centerFreq + std::pow (10.0f, logMinFrequency + nextProportion * (logMaxFrequency - logMinFrequency))) * 0.5f; - } - else if (i == numDisplayBins - 1) - { - freqRangeStart = (std::pow (10.0f, logMinFrequency + prevProportion * (logMaxFrequency - logMinFrequency)) + centerFreq) * 0.5f; - freqRangeEnd = maxFrequency; - } - else - { - freqRangeStart = (std::pow (10.0f, logMinFrequency + prevProportion * (logMaxFrequency - logMinFrequency)) + centerFreq) * 0.5f; - freqRangeEnd = (centerFreq + std::pow (10.0f, logMinFrequency + nextProportion * (logMaxFrequency - logMinFrequency))) * 0.5f; - } - - auto& mapping = displayBinMapping[static_cast (i)]; - mapping.startBin = (freqRangeStart * static_cast (fftSize)) / static_cast (sampleRate); - mapping.endBin = (freqRangeEnd * static_cast (fftSize)) / static_cast (sampleRate); - mapping.exactBin = (centerFreq * static_cast (fftSize)) / static_cast (sampleRate); - } + binMapping.setFftParameters (fftSize, sampleRate); + binMapping.setFrequencyRange (std::pow (10.0f, logMinFrequency), std::pow (10.0f, logMaxFrequency)); + binMapping.setNumDisplayPoints (spectrogramWidth); } bool SpectrogramComponent::ensureGpuTargets (GraphicsContext& context) diff --git a/modules/yup_audio_gui/displays/yup_SpectrogramComponent.h b/modules/yup_audio_gui/displays/yup_SpectrogramComponent.h index 070540485..7161106a7 100644 --- a/modules/yup_audio_gui/displays/yup_SpectrogramComponent.h +++ b/modules/yup_audio_gui/displays/yup_SpectrogramComponent.h @@ -303,15 +303,8 @@ class YUP_API SpectrogramComponent : public Component // Spectrogram color map SpectrogramColorMap colorMap; - // Precomputed log-frequency > FFT-bin mapping - struct DisplayBinMapping - { - float startBin = 0.0f; - float endBin = 0.0f; - float exactBin = 0.0f; - }; - - std::vector displayBinMapping; + // Precomputed log-frequency > fractional FFT bin mapping + SpectrumBinMapping binMapping; // GPU waterfall state GpuDevice::Ptr gpuDevice; diff --git a/modules/yup_audio_gui/displays/yup_SpectrumAnalyzerComponent.cpp b/modules/yup_audio_gui/displays/yup_SpectrumAnalyzerComponent.cpp index fea74e988..2ae70f39e 100644 --- a/modules/yup_audio_gui/displays/yup_SpectrumAnalyzerComponent.cpp +++ b/modules/yup_audio_gui/displays/yup_SpectrumAnalyzerComponent.cpp @@ -32,6 +32,7 @@ SpectrumAnalyzerComponent::SpectrumAnalyzerComponent (SpectrumAnalyzerState& sta initializeFFTBuffers(); generateWindow(); + updateBinMapping(); startTimerHz (30); // 30 FPS updates by default } @@ -49,9 +50,17 @@ void SpectrumAnalyzerComponent::initializeFFTBuffers() fftOutputBuffer.resize (fftSize * 2, 0.0f); // Complex output needs 2x space windowBuffer.resize (fftSize, 0.0f); - // Pre-allocate magnitude buffer to avoid allocations during processing + // Pre-allocate magnitude buffers to avoid allocations during processing const int numBins = fftSize / 2 + 1; magnitudeBuffer.resize (numBins, 0.0f); + binLevelBuffer.resize (numBins, 0.0f); +} + +void SpectrumAnalyzerComponent::updateBinMapping() +{ + binMapping.setFftParameters (fftSize, sampleRate); + binMapping.setFrequencyRange (minFrequency, maxFrequency); + binMapping.setNumDisplayPoints (scopeSize); } //============================================================================== @@ -111,59 +120,35 @@ void SpectrumAnalyzerComponent::processFFT() magnitudeBuffer[static_cast (binIndex)] = magnitude; } + + // Pre-compute the calibrated level of every bin for the active level mode, so that display bands + // can be evaluated by interpolating between neighbouring bins instead of snapping to one of them. + for (int binIndex = 0; binIndex < numBins; ++binIndex) + binLevelBuffer[static_cast (binIndex)] = getBinLinearLevel (binIndex); } void SpectrumAnalyzerComponent::updateDisplay (bool hasNewFFTData) { + const auto aggregation = getBandAggregation(); + // Always apply consistent smoothing to prevent pulsating // Process display bins for (int i = 0; i < scopeSize; ++i) { float targetLevel = 0.0f; - if (hasNewFFTData) + if (hasNewFFTData && isPositiveAndBelow (i, binMapping.getNumDisplayPoints())) { - // Calculate frequency range for this display bin - const float proportion = float (i) / float (scopeSize - 1); - const float logFreq = logMinFrequency + proportion * (logMaxFrequency - logMinFrequency); - const float centerFreq = std::pow (10.0f, logFreq); - - // Calculate the frequency range that this display bin represents - float freqRangeStart, freqRangeEnd; - if (i == 0) - { - freqRangeStart = minFrequency; - const float nextLogFreq = logMinFrequency + (float (i + 1) / float (scopeSize - 1)) * (logMaxFrequency - logMinFrequency); - const float nextFreq = std::pow (10.0f, nextLogFreq); - freqRangeEnd = (centerFreq + nextFreq) * 0.5f; - } - else if (i == scopeSize - 1) - { - const float prevLogFreq = logMinFrequency + (float (i - 1) / float (scopeSize - 1)) * (logMaxFrequency - logMinFrequency); - const float prevFreq = std::pow (10.0f, prevLogFreq); - freqRangeStart = (prevFreq + centerFreq) * 0.5f; - freqRangeEnd = maxFrequency; - } - else - { - const float prevLogFreq = logMinFrequency + (float (i - 1) / float (scopeSize - 1)) * (logMaxFrequency - logMinFrequency); - const float nextLogFreq = logMinFrequency + (float (i + 1) / float (scopeSize - 1)) * (logMaxFrequency - logMinFrequency); - const float prevFreq = std::pow (10.0f, prevLogFreq); - const float nextFreq = std::pow (10.0f, nextLogFreq); - freqRangeStart = (prevFreq + centerFreq) * 0.5f; - freqRangeEnd = (centerFreq + nextFreq) * 0.5f; - } - - // Convert frequency range to bin range - const float startBin = (freqRangeStart * float (fftSize)) / float (sampleRate); - const float endBin = (freqRangeEnd * float (fftSize)) / float (sampleRate); - const float binSpan = endBin - startBin; - - const float exactBin = (centerFreq * float (fftSize)) / float (sampleRate); - const float magnitudeDb = getDisplayDecibelsForBinRange (startBin, endBin, exactBin); + // The band covered by this display point is resolved across the fractional FFT bin + // domain, so neighbouring display points always produce gradually changing levels. + const float bandLevel = binMapping.getBandLevel (binLevelBuffer, i, aggregation); // Map to display range [0.0, 1.0] - targetLevel = jmap (jlimit (minDecibels, maxDecibels, magnitudeDb), minDecibels, maxDecibels, 0.0f, 1.0f); + targetLevel = jmap (jlimit (minDecibels, maxDecibels, linearLevelToDecibels (bandLevel)), + minDecibels, + maxDecibels, + 0.0f, + 1.0f); } // Apply peak-hold with time-based release: instant attack, controlled release @@ -293,88 +278,22 @@ float SpectrumAnalyzerComponent::linearLevelToDecibels (float level) const noexc return (isPowerMode() ? 10.0f : 20.0f) * std::log10 (level); } -float SpectrumAnalyzerComponent::getInterpolatedPeakDecibels (float exactBin) const noexcept -{ - const int numBins = fftSize / 2 + 1; - const int lastBin = numBins - 1; - const int nearestBin = jlimit (0, lastBin, roundToInt (exactBin)); - - int peakBin = nearestBin; - float peakLevel = getBinLinearLevel (nearestBin); - - const int searchStart = jmax (0, nearestBin - 1); - const int searchEnd = jmin (lastBin, nearestBin + 1); - - for (int binIndex = searchStart; binIndex <= searchEnd; ++binIndex) - { - const float binLevel = getBinLinearLevel (binIndex); - - if (binLevel > peakLevel) - { - peakLevel = binLevel; - peakBin = binIndex; - } - } - - const float peakDecibels = linearLevelToDecibels (peakLevel); - - if (peakBin <= 0 || peakBin >= lastBin) - return peakDecibels; - - const float y0 = linearLevelToDecibels (getBinLinearLevel (peakBin - 1)); - const float y1 = peakDecibels; - const float y2 = linearLevelToDecibels (getBinLinearLevel (peakBin + 1)); - const float denominator = y0 - 2.0f * y1 + y2; - - if (std::abs (denominator) < 1.0e-6f || denominator >= 0.0f) - return y1; - - const float offset = jlimit (-1.0f, 1.0f, 0.5f * (y0 - y2) / denominator); - return y1 - 0.25f * (y0 - y2) * offset; -} - -float SpectrumAnalyzerComponent::getDisplayDecibelsForBinRange (float startBin, float endBin, float centerBin) const noexcept +SpectrumBinMapping::BandAggregation SpectrumAnalyzerComponent::getBandAggregation() const noexcept { - const int numBins = fftSize / 2 + 1; - const int lastBin = numBins - 1; - const float binSpan = endBin - startBin; - - if (binSpan <= 1.5f && ! isPowerMode()) - return getInterpolatedPeakDecibels (centerBin); - - const int binStart = jlimit (0, lastBin, static_cast (std::floor (startBin))); - const int binEnd = jlimit (0, lastBin, static_cast (std::ceil (endBin))); - - if (levelMode == LevelMode::powerDecibels) - { - float bandPower = 0.0f; - - for (int binIndex = binStart; binIndex <= binEnd; ++binIndex) - bandPower += getBinPower (binIndex); - - return linearLevelToDecibels (bandPower); - } - - if (levelMode == LevelMode::powerSpectralDensity) + switch (levelMode) { - float densitySum = 0.0f; - int densityCount = 0; + case LevelMode::powerDecibels: + return SpectrumBinMapping::BandAggregation::sum; - for (int binIndex = binStart; binIndex <= binEnd; ++binIndex) - { - densitySum += getBinPowerSpectralDensity (binIndex); - ++densityCount; - } + case LevelMode::powerSpectralDensity: + return SpectrumBinMapping::BandAggregation::mean; - return linearLevelToDecibels (densityCount > 0 ? densitySum / float (densityCount) : 0.0f); + case LevelMode::peakDecibels: + case LevelMode::rmsDecibels: + break; } - float peakLevel = 0.0f; - - for (int binIndex = binStart; binIndex <= binEnd; ++binIndex) - peakLevel = jmax (peakLevel, getBinLinearLevel (binIndex)); - - return linearLevelToDecibels (peakLevel); + return SpectrumBinMapping::BandAggregation::peak; } bool SpectrumAnalyzerComponent::isPowerMode() const noexcept @@ -410,12 +329,7 @@ void SpectrumAnalyzerComponent::drawLinesSpectrum (Graphics& g, const Rectangle< if (scopeSize < 3) return; - const float firstY = binToY (0, bounds.getHeight()); - - Path spectrumPath; - spectrumPath.startNewSubPath (bounds.getX(), firstY); - computeSpectrumPath (spectrumPath, bounds, false); - + auto spectrumPath = createSpectrumPath (bounds, false); auto filledPath = spectrumPath.createStrokePolygon (4.0f); auto lineColor = Color (0xFF00a840); @@ -447,13 +361,8 @@ void SpectrumAnalyzerComponent::drawFilledSpectrum (Graphics& g, const Rectangle if (scopeSize < 3) return; - const float firstX = frequencyToX (std::pow (10.0f, logMinFrequency), bounds); - const float firstY = binToY (0, bounds.getHeight()); - // Create filled path that starts and ends properly at baseline - Path fillPath; - fillPath.startNewSubPath (firstX, bounds.getBottom()); - computeSpectrumPath (fillPath, bounds, true); + auto fillPath = createSpectrumPath (bounds, true); auto gradient = ColorGradient ( Color (0xc000ff40), bounds.getX(), bounds.getY(), Color (0x1000ff40), bounds.getX(), bounds.getBottom()); @@ -461,9 +370,7 @@ void SpectrumAnalyzerComponent::drawFilledSpectrum (Graphics& g, const Rectangle g.fillPath (fillPath); // Draw the spectrum outline - Path spectrumPath; - spectrumPath.startNewSubPath (bounds.getX(), firstY); - computeSpectrumPath (spectrumPath, bounds, false); + auto spectrumPath = createSpectrumPath (bounds, false); g.setStrokeColor (Color (0xFF00ff40)); g.setStrokeWidth (1.5f); @@ -585,29 +492,38 @@ void SpectrumAnalyzerComponent::resized() } //============================================================================== -void SpectrumAnalyzerComponent::computeSpectrumPath (Path spectrumPath, const Rectangle& bounds, bool closePath) +Path SpectrumAnalyzerComponent::createSpectrumPath (const Rectangle& bounds, bool closePath) const { - float lastX = 0.0f; + Path path; - // Draw the spectrum curve - for (int i = 0; i < scopeSize; ++i) - { - const float proportion = float (i) / float (scopeSize - 1); - const float frequency = std::pow (10.0f, logMinFrequency + proportion * (logMaxFrequency - logMinFrequency)); - const float x = frequencyToX (frequency, bounds); - const float y = binToY (i, bounds.getHeight()); + const float width = bounds.getWidth(); + + if (scopeSize < 2 || width <= 0.0f || bounds.getHeight() <= 0.0f) + return path; - spectrumPath.lineTo (x, y); + // A closed path starts and ends on the baseline, so that it can be filled directly. + path.startNewSubPath (bounds.getX(), + closePath ? bounds.getBottom() : levelToY (getDisplayLevelForPosition (0.0f), bounds)); - lastX = x; + // Sample one point per pixel column and interpolate between the smoothed display points, so the + // outline stays continuous at any component width. + const int numColumns = jmax (1, roundToInt (width)); + + for (int column = 0; column <= numColumns; ++column) + { + const float proportion = float (column) / float (numColumns); + const float level = getDisplayLevelForPosition (proportion * float (scopeSize - 1)); + + path.lineTo (bounds.getX() + width * proportion, levelToY (level, bounds)); } - // End at baseline at the last spectrum frequency if (closePath) { - spectrumPath.lineTo (lastX, bounds.getBottom()); - spectrumPath.closeSubPath(); + path.lineTo (bounds.getRight(), bounds.getBottom()); + path.closeSubPath(); } + + return path; } //============================================================================== @@ -645,6 +561,8 @@ void SpectrumAnalyzerComponent::setFrequencyRange (float minFreq, float maxFreq) logMinFrequency = std::log10 (minFreq); logMaxFrequency = std::log10 (maxFreq); + updateBinMapping(); + repaint(); } } @@ -671,6 +589,8 @@ void SpectrumAnalyzerComponent::setSampleRate (double sampleRateToUse) { sampleRate = sampleRateToUse; + updateBinMapping(); + repaint(); } } @@ -689,6 +609,12 @@ void SpectrumAnalyzerComponent::setLevelMode (LevelMode mode) if (levelMode != mode) { levelMode = mode; + + // The calibration of the per-bin levels depends on the level mode. + if (! binLevelBuffer.empty()) + for (int binIndex = 0; binIndex < fftSize / 2 + 1; ++binIndex) + binLevelBuffer[static_cast (binIndex)] = getBinLinearLevel (binIndex); + repaint(); } } @@ -714,12 +640,21 @@ float SpectrumAnalyzerComponent::frequencyToX (float frequency, const Rectangle< return jmap (std::log10 (frequency), logMinFrequency, logMaxFrequency, bounds.getX(), bounds.getRight()); } -float SpectrumAnalyzerComponent::binToY (int binIndex, float height) const noexcept +float SpectrumAnalyzerComponent::levelToY (float level, const Rectangle& bounds) const noexcept { - if (isPositiveAndBelow (binIndex, (int) scopeData.size())) - return jmap (scopeData[static_cast (binIndex)], 0.0f, 1.0f, height, 0.0f); + return jmap (jlimit (0.0f, 1.0f, level), 0.0f, 1.0f, bounds.getBottom(), bounds.getY()); +} + +float SpectrumAnalyzerComponent::getDisplayLevelForPosition (float displayPoint) const noexcept +{ + const float position = jlimit (0.0f, float (scopeSize - 1), displayPoint); + const int lowerPoint = jlimit (0, scopeSize - 2, (int) std::floor (position)); + const float fraction = position - float (lowerPoint); + + const float lowerLevel = scopeData[static_cast (lowerPoint)]; + const float upperLevel = scopeData[static_cast (lowerPoint + 1)]; - return 0.0f; + return lowerLevel + fraction * (upperLevel - lowerLevel); } float SpectrumAnalyzerComponent::decibelToY (float decibel, const Rectangle& bounds) const noexcept @@ -755,6 +690,7 @@ void SpectrumAnalyzerComponent::setFFTSize (int size) initializeFFTBuffers(); generateWindow(); + updateBinMapping(); repaint(); } diff --git a/modules/yup_audio_gui/displays/yup_SpectrumAnalyzerComponent.h b/modules/yup_audio_gui/displays/yup_SpectrumAnalyzerComponent.h index a63ae65fd..f06dec6ea 100644 --- a/modules/yup_audio_gui/displays/yup_SpectrumAnalyzerComponent.h +++ b/modules/yup_audio_gui/displays/yup_SpectrumAnalyzerComponent.h @@ -31,7 +31,9 @@ namespace yup following the pattern from the JUCE spectrum analyzer tutorial. The component can be configured with different window functions, display types, frequency ranges, and update - rates. It automatically handles logarithmic frequency scaling for natural spectrum visualization. + rates. It automatically handles logarithmic frequency scaling for natural spectrum visualization. Levels are + interpolated across the FFT bin domain and the outline is sampled once per pixel column, so the rendered curve + stays continuous at any component width. Example usage: @@ -236,7 +238,8 @@ class YUP_API SpectrumAnalyzerComponent void updateDisplay (bool hasNewFFTData); void generateWindow(); void initializeFFTBuffers(); - void computeSpectrumPath (Path spectrumPath, const Rectangle& bounds, bool closePath); + void updateBinMapping(); + Path createSpectrumPath (const Rectangle& bounds, bool closePath) const; void drawLinesSpectrum (Graphics& g, const Rectangle& bounds); void drawFilledSpectrum (Graphics& g, const Rectangle& bounds); void drawFrequencyGrid (Graphics& g, const Rectangle& bounds); @@ -248,13 +251,13 @@ class YUP_API SpectrumAnalyzerComponent float getBinPowerSpectralDensity (int binIndex) const noexcept; float getBinLinearLevel (int binIndex) const noexcept; float linearLevelToDecibels (float level) const noexcept; - float getInterpolatedPeakDecibels (float exactBin) const noexcept; - float getDisplayDecibelsForBinRange (float startBin, float endBin, float centerBin) const noexcept; + SpectrumBinMapping::BandAggregation getBandAggregation() const noexcept; bool isPowerMode() const noexcept; float frequencyToX (float frequency, const Rectangle& bounds) const noexcept; float decibelToY (float decibel, const Rectangle& bounds) const noexcept; - float binToY (int binIndex, float height) const noexcept; + float levelToY (float level, const Rectangle& bounds) const noexcept; + float getDisplayLevelForPosition (float displayPoint) const noexcept; //============================================================================== SpectrumAnalyzerState& analyzerState; @@ -268,7 +271,8 @@ class YUP_API SpectrumAnalyzerComponent // Display data std::vector scopeData; - Path spectrumPath; + std::vector binLevelBuffer; // Calibrated linear level of every FFT bin + SpectrumBinMapping binMapping; // Log-frequency > fractional FFT bin mapping // Configuration WindowType currentWindowType = WindowType::hann; diff --git a/modules/yup_audio_gui/displays/yup_SpectrumBinMapping.cpp b/modules/yup_audio_gui/displays/yup_SpectrumBinMapping.cpp new file mode 100644 index 000000000..db4b241c0 --- /dev/null +++ b/modules/yup_audio_gui/displays/yup_SpectrumBinMapping.cpp @@ -0,0 +1,224 @@ +/* + ============================================================================== + + This file is part of the YUP library. + Copyright (c) 2026 - kunitoki@gmail.com + + YUP is an open source library subject to open-source licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace yup +{ + +//============================================================================== +bool SpectrumBinMapping::isValid() const noexcept +{ + return fftSize >= 2 + && sampleRate > 0.0 + && numDisplayPoints >= 2 + && minFrequency > 0.0f + && maxFrequency > minFrequency; +} + +void SpectrumBinMapping::setFftParameters (int newFftSize, double newSampleRate) +{ + if (fftSize == newFftSize && approximatelyEqual (sampleRate, newSampleRate)) + return; + + fftSize = jmax (0, newFftSize); + sampleRate = jmax (0.0, newSampleRate); + + updateRanges(); +} + +void SpectrumBinMapping::setFrequencyRange (float newMinFrequency, float newMaxFrequency) +{ + if (approximatelyEqual (minFrequency, newMinFrequency) + && approximatelyEqual (maxFrequency, newMaxFrequency)) + return; + + minFrequency = newMinFrequency; + maxFrequency = newMaxFrequency; + + logMinFrequency = minFrequency > 0.0f ? std::log10 (minFrequency) : 0.0f; + logMaxFrequency = maxFrequency > 0.0f ? std::log10 (maxFrequency) : 0.0f; + + updateRanges(); +} + +void SpectrumBinMapping::setNumDisplayPoints (int newNumDisplayPoints) +{ + if (numDisplayPoints == newNumDisplayPoints) + return; + + numDisplayPoints = newNumDisplayPoints; + + updateRanges(); +} + +//============================================================================== +const SpectrumBinRange& SpectrumBinMapping::getRange (int displayPoint) const noexcept +{ + static const SpectrumBinRange emptyRange; + + if (displayRanges.empty()) + return emptyRange; + + return displayRanges[(size_t) jlimit (0, (int) displayRanges.size() - 1, displayPoint)]; +} + +float SpectrumBinMapping::getFrequencyForDisplayPoint (int displayPoint) const noexcept +{ + return getRange (displayPoint).centerFrequency; +} + +//============================================================================== +float SpectrumBinMapping::levelToDecibels (float level) noexcept +{ + return level > 0.0f ? 20.0f * std::log10 (level) : minLevelDecibels; +} + +float SpectrumBinMapping::decibelsToLevel (float decibels) noexcept +{ + return decibels <= minLevelDecibels ? 0.0f : std::pow (10.0f, decibels / 20.0f); +} + +//============================================================================== +float SpectrumBinMapping::getInterpolatedLevel (Span binLevels, float exactBin) noexcept +{ + const int numBins = (int) binLevels.size(); + + if (numBins <= 0) + return 0.0f; + + if (numBins == 1) + return binLevels[0]; + + const int lastBin = numBins - 1; + const float position = jlimit (0.0f, (float) lastBin, exactBin); + const int lowerBin = jlimit (0, lastBin - 1, (int) std::floor (position)); + const float fraction = position - (float) lowerBin; + + const float lowerLevel = levelToDecibels (binLevels[(size_t) lowerBin]); + const float upperLevel = levelToDecibels (binLevels[(size_t) (lowerBin + 1)]); + const float linearLevel = lowerLevel + fraction * (upperLevel - lowerLevel); + + // There is no bin to the left of the first one, so the edge falls back to linear interpolation. + if (lowerBin == 0) + return decibelsToLevel (linearLevel); + + // Parabolic interpolation through the three bins surrounding the position, evaluated at the + // fractional position instead of at the vertex of the parabola, so the curve stays continuous + // while still rendering peaks at their refined height. + const float previousLevel = levelToDecibels (binLevels[(size_t) (lowerBin - 1)]); + + if (previousLevel - 2.0f * lowerLevel + upperLevel >= 0.0f) + return decibelsToLevel (linearLevel); + + const float interpolated = previousLevel * (0.5f * fraction * (fraction - 1.0f)) + + lowerLevel * (1.0f - fraction * fraction) + + upperLevel * (0.5f * fraction * (fraction + 1.0f)); + + return decibelsToLevel (interpolated); +} + +//============================================================================== +float SpectrumBinMapping::getBandLevel (Span binLevels, int displayPoint, BandAggregation aggregation) const noexcept +{ + if (displayRanges.empty() || binLevels.empty()) + return 0.0f; + + const int lastBin = (int) binLevels.size() - 1; + const auto& range = getRange (displayPoint); + const float startBin = jlimit (0.0f, (float) lastBin, range.startBin); + const float endBin = jlimit (startBin, (float) lastBin, range.endBin); + const int firstBinInside = jmax (0, (int) std::ceil (startBin)); + const int lastBinInside = jmin (lastBin, (int) std::floor (endBin)); + + // The band edges are evaluated at their fractional bin positions, so bins entering or leaving + // the band never make the aggregated level jump. + const float startLevel = getInterpolatedLevel (binLevels, startBin); + + if (aggregation == BandAggregation::peak) + { + float peak = jmax (startLevel, getInterpolatedLevel (binLevels, endBin)); + + for (int bin = firstBinInside; bin <= lastBinInside; ++bin) + peak = jmax (peak, binLevels[(size_t) bin]); + + return peak; + } + + float integral = 0.0f; + float previousPosition = startBin; + float previousLevel = startLevel; + + for (int bin = firstBinInside; bin <= lastBinInside; ++bin) + { + const float binLevel = binLevels[(size_t) bin]; + + integral += 0.5f * (previousLevel + binLevel) * ((float) bin - previousPosition); + previousPosition = (float) bin; + previousLevel = binLevel; + } + + const float endLevel = getInterpolatedLevel (binLevels, endBin); + integral += 0.5f * (previousLevel + endLevel) * (endBin - previousPosition); + + if (aggregation == BandAggregation::sum) + return integral; + + return integral / jmax (1.0e-6f, endBin - startBin); +} + +//============================================================================== +void SpectrumBinMapping::updateRanges() +{ + displayRanges.clear(); + + if (! isValid()) + return; + + displayRanges.resize ((size_t) numDisplayPoints); + + constexpr float half = 0.5f; + const float invLastPoint = 1.0f / (float) (numDisplayPoints - 1); + const float logRange = logMaxFrequency - logMinFrequency; + const float halvedStep = half * invLastPoint * logRange; + const float binPerHz = (float) fftSize / (float) sampleRate; + + for (int displayPoint = 0; displayPoint < numDisplayPoints; ++displayPoint) + { + const float logFrequency = logMinFrequency + (float) displayPoint * invLastPoint * logRange; + + auto& range = displayRanges[(size_t) displayPoint]; + range.centerFrequency = std::pow (10.0f, logFrequency); + range.exactBin = range.centerFrequency * binPerHz; + + // The band edges sit halfway (in log-frequency) between neighbouring display points, and + // the outermost points reach the ends of the displayed range. + const float startFrequency = displayPoint == 0 + ? minFrequency + : std::pow (10.0f, logFrequency - halvedStep); + const float endFrequency = displayPoint == numDisplayPoints - 1 + ? maxFrequency + : std::pow (10.0f, logFrequency + halvedStep); + + range.startBin = startFrequency * binPerHz; + range.endBin = endFrequency * binPerHz; + } +} + +} // namespace yup diff --git a/modules/yup_audio_gui/displays/yup_SpectrumBinMapping.h b/modules/yup_audio_gui/displays/yup_SpectrumBinMapping.h new file mode 100644 index 000000000..174896139 --- /dev/null +++ b/modules/yup_audio_gui/displays/yup_SpectrumBinMapping.h @@ -0,0 +1,187 @@ +/* + ============================================================================== + + This file is part of the YUP library. + Copyright (c) 2026 - kunitoki@gmail.com + + YUP is an open source library subject to open-source licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace yup +{ + +//============================================================================== +/** + Describes how a single display point of a logarithmic frequency axis maps onto + the fractional FFT bin domain. + + A display point never lands exactly on an FFT bin: at low frequencies many + display points share a single bin, while at high frequencies a single display + point can span many bins. + + @see SpectrumBinMapping + + @tags{Audio} +*/ +struct SpectrumBinRange +{ + float centerFrequency = 0.0f; ///< The frequency in Hz displayed by the point. + float exactBin = 0.0f; ///< The fractional FFT bin position of centerFrequency. + float startBin = 0.0f; ///< The fractional FFT bin position of the lower band edge. + float endBin = 0.0f; ///< The fractional FFT bin position of the upper band edge. +}; + +//============================================================================== +/** + Maps a logarithmic frequency axis onto fractional FFT bin positions and evaluates + the levels of that axis continuously across the bin domain. + + Snapping every display point to its nearest FFT bin makes all points that share a + bin report an identical level, which renders as a flat staircase. This class avoids + that: levels are interpolated between neighbouring bins, and band aggregation slides + continuously with the band edges, so neighbouring display points always produce + gradually changing levels. + + The level interpolation is performed on the amplitude decibel scale (20 * log10) so + that the interpolated curve keeps the rounded shape of a parabolic peak refinement, + evaluated at the fractional bin position rather than snapped to a bin. Bins where the + interpolation would not be concave fall back to a monotone linear interpolation, so a + steep bin pair can never produce an undershoot. + + Example usage: + + @code + SpectrumBinMapping mapping; + mapping.setFftParameters (fftSize, sampleRate); + mapping.setFrequencyRange (20.0f, 20000.0f); + mapping.setNumDisplayPoints (512); + + for (int displayPoint = 0; displayPoint < mapping.getNumDisplayPoints(); ++displayPoint) + displayLevels[displayPoint] = mapping.getBandLevel (binLevels, displayPoint, SpectrumBinMapping::BandAggregation::peak); + @endcode + + @see SpectrumAnalyzerComponent, SpectrogramComponent + + @tags{Audio} +*/ +class YUP_API SpectrumBinMapping +{ +public: + //============================================================================== + /** How the levels of the FFT bins covered by a display band are combined. */ + enum class BandAggregation + { + peak, ///< The highest interpolated level found across the band. + sum, ///< The levels integrated across the band width, expressed in bin units. + mean ///< The levels integrated across the band width and divided by it. + }; + + //============================================================================== + /** Creates an empty mapping, which is configured with the set* methods. */ + SpectrumBinMapping() = default; + + /** Destructor. */ + ~SpectrumBinMapping() = default; + + //============================================================================== + /** Sets the FFT size and sample rate used to convert frequencies into bin positions. + + @param newFftSize the FFT size, the mapping covers fftSize / 2 + 1 bins + @param newSampleRate the sample rate in Hz + */ + void setFftParameters (int newFftSize, double newSampleRate); + + /** Sets the displayed frequency range. + + @param newMinFrequency the lowest displayed frequency in Hz + @param newMaxFrequency the highest displayed frequency in Hz + */ + void setFrequencyRange (float newMinFrequency, float newMaxFrequency); + + /** Sets the number of display points spread logarithmically across the frequency range. + + @param newNumDisplayPoints the number of display points, at least 2 to span the range + */ + void setNumDisplayPoints (int newNumDisplayPoints); + + //============================================================================== + /** Returns the number of display points. */ + int getNumDisplayPoints() const noexcept { return (int) displayRanges.size(); } + + /** Returns the FFT size used by the mapping. */ + int getFftSize() const noexcept { return fftSize; } + + /** Returns the sample rate used by the mapping. */ + double getSampleRate() const noexcept { return sampleRate; } + + /** Returns the lowest displayed frequency in Hz. */ + float getMinFrequency() const noexcept { return minFrequency; } + + /** Returns the highest displayed frequency in Hz. */ + float getMaxFrequency() const noexcept { return maxFrequency; } + + /** Returns true if the mapping spans a valid frequency range. */ + bool isValid() const noexcept; + + //============================================================================== + /** Returns the bin range covered by a display point. + + @param displayPoint the display point index, clamped to the valid range + */ + const SpectrumBinRange& getRange (int displayPoint) const noexcept; + + /** Returns the frequency displayed by a display point. + + @param displayPoint the display point index, clamped to the valid range + */ + float getFrequencyForDisplayPoint (int displayPoint) const noexcept; + + //============================================================================== + /** Returns the linear level of an arbitrary fractional bin position. + + @param binLevels the non-negative linear level of every FFT bin, from bin 0 to Nyquist + @param exactBin the fractional bin position + */ + static float getInterpolatedLevel (Span binLevels, float exactBin) noexcept; + + /** Returns the level of the band covered by a display point. + + @param binLevels the non-negative linear level of every FFT bin, from bin 0 to Nyquist + @param displayPoint the display point index + @param aggregation how the levels of the bins inside the band are combined + */ + float getBandLevel (Span binLevels, int displayPoint, BandAggregation aggregation) const noexcept; + +private: + //============================================================================== + static float levelToDecibels (float level) noexcept; + static float decibelsToLevel (float decibels) noexcept; + + void updateRanges(); + + //============================================================================== + static constexpr float minLevelDecibels = -300.0f; + + std::vector displayRanges; + int fftSize = 0; + double sampleRate = 0.0; + float minFrequency = 0.0f; + float maxFrequency = 0.0f; + float logMinFrequency = 0.0f; + float logMaxFrequency = 0.0f; + int numDisplayPoints = 0; +}; + +} // namespace yup diff --git a/modules/yup_audio_gui/yup_audio_gui.cpp b/modules/yup_audio_gui/yup_audio_gui.cpp index d2458dd8d..bd94aad02 100644 --- a/modules/yup_audio_gui/yup_audio_gui.cpp +++ b/modules/yup_audio_gui/yup_audio_gui.cpp @@ -37,6 +37,7 @@ #include "waveform/yup_AudioThumbnail.cpp" #include "keyboard/yup_MidiKeyboardComponent.cpp" #include "displays/yup_AudioViewComponent.cpp" +#include "displays/yup_SpectrumBinMapping.cpp" #include "displays/yup_SpectrumAnalyzerComponent.cpp" #include "displays/yup_SpectrogramComponent.cpp" #include "displays/yup_CartesianPlane.cpp" diff --git a/modules/yup_audio_gui/yup_audio_gui.h b/modules/yup_audio_gui/yup_audio_gui.h index 1257b87fd..e01a678bf 100644 --- a/modules/yup_audio_gui/yup_audio_gui.h +++ b/modules/yup_audio_gui/yup_audio_gui.h @@ -57,6 +57,7 @@ #include "waveform/yup_AudioThumbnail.h" #include "keyboard/yup_MidiKeyboardComponent.h" #include "displays/yup_AudioViewComponent.h" +#include "displays/yup_SpectrumBinMapping.h" #include "displays/yup_SpectrumAnalyzerComponent.h" #include "displays/yup_SpectrogramComponent.h" #include "displays/yup_CartesianPlane.h" diff --git a/tests/yup_audio_gui/yup_SpectrumAnalyzerComponent.cpp b/tests/yup_audio_gui/yup_SpectrumAnalyzerComponent.cpp index e3e7d5af2..c3df932ad 100644 --- a/tests/yup_audio_gui/yup_SpectrumAnalyzerComponent.cpp +++ b/tests/yup_audio_gui/yup_SpectrumAnalyzerComponent.cpp @@ -850,3 +850,104 @@ TEST_F (SpectrumAnalyzerComponentTests, RapidConfigurationChanges) // Should handle rapid changes without crashing EXPECT_TRUE (true); } + +TEST_F (SpectrumAnalyzerComponentTests, PaintWithEveryLevelModeAndDisplayType) +{ + const SpectrumAnalyzerComponent::LevelMode levelModes[] = { + SpectrumAnalyzerComponent::LevelMode::peakDecibels, + SpectrumAnalyzerComponent::LevelMode::rmsDecibels, + SpectrumAnalyzerComponent::LevelMode::powerDecibels, + SpectrumAnalyzerComponent::LevelMode::powerSpectralDensity + }; + + const SpectrumAnalyzerComponent::DisplayType displayTypes[] = { + SpectrumAnalyzerComponent::DisplayType::filled, + SpectrumAnalyzerComponent::DisplayType::lines + }; + + std::vector testData (2048); + + for (int i = 0; i < 2048; ++i) + testData[static_cast (i)] = std::sin (2.0f * MathConstants::pi * static_cast (i) / 100.0f); + + state->pushSamples (testData.data(), 2048); + analyzer->timerCallback(); + + auto context = yup_constructHeadlessGraphicsContext ({}, {}); + auto renderer = context->makeRenderer (800, 400); + Graphics g (*context, *renderer); + + for (auto levelMode : levelModes) + { + analyzer->setLevelMode (levelMode); + + for (auto displayType : displayTypes) + { + analyzer->setDisplayType (displayType); + analyzer->paint (g); + + EXPECT_EQ (levelMode, analyzer->getLevelMode()); + EXPECT_EQ (displayType, analyzer->getDisplayType()); + } + } +} + +TEST_F (SpectrumAnalyzerComponentTests, PaintWithBandsNarrowerThanOneBin) +{ + // 20 Hz > 25 Hz at 44.1 kHz makes every display band narrower than an FFT bin, so the rendered + // levels come from the interpolation between the surrounding bins. + analyzer->setFFTSize (2048); + analyzer->setSampleRate (44100.0); + analyzer->setFrequencyRange (20.0f, 25.0f); + + std::vector testData (2048); + + for (int i = 0; i < 2048; ++i) + testData[static_cast (i)] = std::sin (2.0f * MathConstants::pi * static_cast (i) / 400.0f); + + state->pushSamples (testData.data(), 2048); + analyzer->timerCallback(); + + auto context = yup_constructHeadlessGraphicsContext ({}, {}); + auto renderer = context->makeRenderer (400, 200); + Graphics g (*context, *renderer); + + analyzer->setDisplayType (SpectrumAnalyzerComponent::DisplayType::filled); + analyzer->paint (g); + + analyzer->setDisplayType (SpectrumAnalyzerComponent::DisplayType::lines); + analyzer->paint (g); + + EXPECT_FLOAT_EQ (20.0f, analyzer->getMinFrequency()); + EXPECT_FLOAT_EQ (25.0f, analyzer->getMaxFrequency()); +} + +TEST_F (SpectrumAnalyzerComponentTests, PaintAfterEveryMappingChangingConfiguration) +{ + // The bin mapping is rebuilt from the FFT size, the sample rate and the frequency range, so + // every combination has to keep rendering. + const double sampleRates[] = { 44100.0, 48000.0, 96000.0 }; + const int fftSizes[] = { 512, 2048, 8192 }; + + auto context = yup_constructHeadlessGraphicsContext ({}, {}); + auto renderer = context->makeRenderer (300, 150); + Graphics g (*context, *renderer); + + for (auto sampleRate : sampleRates) + { + for (auto fftSize : fftSizes) + { + analyzer->setSampleRate (sampleRate); + analyzer->setFFTSize (fftSize); + analyzer->setFrequencyRange (30.0f, static_cast (sampleRate) / 3.0f); + + std::vector testData (static_cast (fftSize), 0.5f); + + state->pushSamples (testData.data(), fftSize); + analyzer->timerCallback(); + analyzer->paint (g); + + EXPECT_EQ (fftSize, analyzer->getFFTSize()); + } + } +} diff --git a/tests/yup_audio_gui/yup_SpectrumBinMapping.cpp b/tests/yup_audio_gui/yup_SpectrumBinMapping.cpp new file mode 100644 index 000000000..b4d95dd86 --- /dev/null +++ b/tests/yup_audio_gui/yup_SpectrumBinMapping.cpp @@ -0,0 +1,498 @@ +/* + ============================================================================== + + This file is part of the YUP library. + Copyright (c) 2026 - kunitoki@gmail.com + + YUP is an open source library subject to open-source licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#include + +#include + +using namespace yup; + +namespace +{ + +constexpr int spectrumTestFftSize = 2048; +constexpr double spectrumTestSampleRate = 44100.0; +constexpr float spectrumTestMinFrequency = 20.0f; +constexpr float spectrumTestMaxFrequency = 20000.0f; +constexpr int spectrumTestNumDisplayPoints = 512; + +constexpr int spectrumTestNumBins() +{ + return spectrumTestFftSize / 2 + 1; +} + +constexpr float spectrumTestBinsPerHertz() +{ + return static_cast (spectrumTestFftSize) / static_cast (spectrumTestSampleRate); +} + +/** Levels expressed as a linear ramp on the amplitude decibel scale, from 0 dB at bin 0 to + rangeDecibels at the last bin. Such a spectrum is reproduced exactly by the fractional level + interpolation, which makes it a precise reference for it. +*/ +std::vector makeDecibelRampSpectrum (float rangeDecibels = -40.0f) +{ + std::vector levels (static_cast (spectrumTestNumBins()), 1.0f); + const float lastBin = static_cast (spectrumTestNumBins() - 1); + + for (int bin = 0; bin < spectrumTestNumBins(); ++bin) + { + const float decibels = rangeDecibels * static_cast (bin) / lastBin; + levels[static_cast (bin)] = std::pow (10.0f, decibels / 20.0f); + } + + return levels; +} + +/** A single loud bin surrounded by a quiet floor, used to probe the interpolation across a peak. */ +std::vector makeImpulseSpectrum (int peakBin, float peakLevel = 1.0f, float floorLevel = 0.001f) +{ + std::vector levels (static_cast (spectrumTestNumBins()), floorLevel); + levels[static_cast (peakBin)] = peakLevel; + + return levels; +} + +class SpectrumBinMappingTests : public ::testing::Test +{ +protected: + void SetUp() override + { + mapping.setFftParameters (spectrumTestFftSize, spectrumTestSampleRate); + mapping.setFrequencyRange (spectrumTestMinFrequency, spectrumTestMaxFrequency); + mapping.setNumDisplayPoints (spectrumTestNumDisplayPoints); + } + + SpectrumBinMapping mapping; +}; + +} // namespace + +//============================================================================== +// Configuration +//============================================================================== + +TEST_F (SpectrumBinMappingTests, DefaultConstructedMappingIsInvalid) +{ + SpectrumBinMapping emptyMapping; + + EXPECT_FALSE (emptyMapping.isValid()); + EXPECT_EQ (0, emptyMapping.getNumDisplayPoints()); + + // Reading from an unconfigured mapping must be safe. + EXPECT_FLOAT_EQ (0.0f, emptyMapping.getFrequencyForDisplayPoint (10)); + EXPECT_FLOAT_EQ (0.0f, emptyMapping.getRange (10).exactBin); + EXPECT_FLOAT_EQ (0.0f, emptyMapping.getBandLevel ({}, 10, SpectrumBinMapping::BandAggregation::peak)); +} + +TEST_F (SpectrumBinMappingTests, ConfiguredMappingReportsItsParameters) +{ + EXPECT_TRUE (mapping.isValid()); + EXPECT_EQ (spectrumTestFftSize, mapping.getFftSize()); + EXPECT_DOUBLE_EQ (spectrumTestSampleRate, mapping.getSampleRate()); + EXPECT_FLOAT_EQ (spectrumTestMinFrequency, mapping.getMinFrequency()); + EXPECT_FLOAT_EQ (spectrumTestMaxFrequency, mapping.getMaxFrequency()); + EXPECT_EQ (spectrumTestNumDisplayPoints, mapping.getNumDisplayPoints()); +} + +TEST_F (SpectrumBinMappingTests, InvalidSettingsKeepTheMappingInvalid) +{ + SpectrumBinMapping invalidMapping; + + invalidMapping.setFftParameters (0, spectrumTestSampleRate); + invalidMapping.setFrequencyRange (spectrumTestMinFrequency, spectrumTestMaxFrequency); + invalidMapping.setNumDisplayPoints (spectrumTestNumDisplayPoints); + EXPECT_FALSE (invalidMapping.isValid()); + + invalidMapping.setFftParameters (spectrumTestFftSize, 0.0); + EXPECT_FALSE (invalidMapping.isValid()); + + invalidMapping.setFftParameters (spectrumTestFftSize, spectrumTestSampleRate); + invalidMapping.setFrequencyRange (spectrumTestMaxFrequency, spectrumTestMinFrequency); + EXPECT_FALSE (invalidMapping.isValid()); + + invalidMapping.setFrequencyRange (spectrumTestMinFrequency, spectrumTestMaxFrequency); + invalidMapping.setNumDisplayPoints (1); + EXPECT_FALSE (invalidMapping.isValid()); +} + +TEST_F (SpectrumBinMappingTests, ChangingTheDisplayPointCountRebuildsEveryRange) +{ + mapping.setNumDisplayPoints (64); + + EXPECT_EQ (64, mapping.getNumDisplayPoints()); + + const float binsPerHertz = spectrumTestBinsPerHertz(); + EXPECT_FLOAT_EQ (spectrumTestMinFrequency * binsPerHertz, mapping.getRange (0).startBin); + EXPECT_NEAR (spectrumTestMaxFrequency * binsPerHertz, mapping.getRange (63).endBin, 0.01f); + EXPECT_NEAR (spectrumTestMaxFrequency, mapping.getFrequencyForDisplayPoint (63), 1.0f); +} + +TEST_F (SpectrumBinMappingTests, ChangingTheFftSizeScalesTheBinPositions) +{ + const float referenceBin = mapping.getRange (200).exactBin; + + mapping.setFftParameters (spectrumTestFftSize * 2, spectrumTestSampleRate); + + const float scaledBin = mapping.getRange (200).exactBin; + EXPECT_NEAR (referenceBin * 2.0f, scaledBin, scaledBin * 1.0e-3f + 1.0e-4f); +} + +TEST_F (SpectrumBinMappingTests, ChangingTheFrequencyRangeRescalesTheBands) +{ + mapping.setFrequencyRange (500.0f, 4000.0f); + + EXPECT_FLOAT_EQ (500.0f, mapping.getFrequencyForDisplayPoint (0)); + EXPECT_NEAR (4000.0f, mapping.getFrequencyForDisplayPoint (spectrumTestNumDisplayPoints - 1), 0.5f); + EXPECT_NEAR (500.0f * spectrumTestBinsPerHertz(), mapping.getRange (0).startBin, 0.01f); +} + +//============================================================================== +// Frequency axis > bin ranges +//============================================================================== + +TEST_F (SpectrumBinMappingTests, FrequencyAxisSpansTheConfiguredRange) +{ + EXPECT_FLOAT_EQ (spectrumTestMinFrequency, mapping.getRange (0).centerFrequency); + EXPECT_NEAR (spectrumTestMaxFrequency, mapping.getRange (spectrumTestNumDisplayPoints - 1).centerFrequency, 1.0f); + + EXPECT_FLOAT_EQ (spectrumTestMinFrequency * spectrumTestBinsPerHertz(), mapping.getRange (0).startBin); + EXPECT_NEAR (spectrumTestMaxFrequency * spectrumTestBinsPerHertz(), mapping.getRange (spectrumTestNumDisplayPoints - 1).endBin, 0.01f); +} + +TEST_F (SpectrumBinMappingTests, BandsTileTheFrequencyRangeWithoutGaps) +{ + for (int point = 1; point < mapping.getNumDisplayPoints(); ++point) + EXPECT_NEAR (mapping.getRange (point - 1).endBin, mapping.getRange (point).startBin, 1.0e-4f); +} + +TEST_F (SpectrumBinMappingTests, BandsGrowWithFrequencyOnALogarithmicAxis) +{ + float previousWidth = 0.0f; + + for (int point = 0; point < mapping.getNumDisplayPoints(); ++point) + { + const auto& range = mapping.getRange (point); + const float width = range.endBin - range.startBin; + + EXPECT_GE (width, previousWidth); + previousWidth = width; + } +} + +TEST_F (SpectrumBinMappingTests, ExactBinMatchesTheCentreFrequency) +{ + const float binsPerHertz = spectrumTestBinsPerHertz(); + + for (int point = 0; point < mapping.getNumDisplayPoints(); ++point) + { + const auto& range = mapping.getRange (point); + + EXPECT_NEAR (range.centerFrequency * binsPerHertz, range.exactBin, range.exactBin * 1.0e-4f + 1.0e-6f); + EXPECT_LE (range.startBin, range.exactBin); + EXPECT_LE (range.exactBin, range.endBin); + } +} + +TEST_F (SpectrumBinMappingTests, RangeAccessClampsOutOfBoundsIndices) +{ + EXPECT_FLOAT_EQ (mapping.getRange (0).exactBin, mapping.getRange (-5).exactBin); + EXPECT_FLOAT_EQ (mapping.getRange (spectrumTestNumDisplayPoints - 1).exactBin, + mapping.getRange (spectrumTestNumDisplayPoints + 5).exactBin); +} + +//============================================================================== +// Fractional level interpolation +//============================================================================== + +TEST_F (SpectrumBinMappingTests, InterpolatedLevelIsExactAtEveryBinCentre) +{ + const auto levels = makeDecibelRampSpectrum(); + + for (int bin = 0; bin < spectrumTestNumBins(); ++bin) + EXPECT_NEAR (levels[static_cast (bin)], + SpectrumBinMapping::getInterpolatedLevel (levels, static_cast (bin)), + 1.0e-6f); +} + +TEST_F (SpectrumBinMappingTests, InterpolatedLevelReproducesADecibelRamp) +{ + // A spectrum that is linear in decibels is reproduced exactly by the fractional interpolation. + const auto levels = makeDecibelRampSpectrum (-60.0f); + const float lastBin = static_cast (spectrumTestNumBins() - 1); + + for (float position = 0.0f; position <= lastBin; position += 0.01f) + { + const float expected = -60.0f * position / lastBin; + const float interpolated = 20.0f * std::log10 (SpectrumBinMapping::getInterpolatedLevel (levels, position)); + + EXPECT_NEAR (expected, interpolated, 0.02f); + } +} + +TEST_F (SpectrumBinMappingTests, InterpolatedLevelChangesGraduallyAroundASteepPeak) +{ + // The neighbouring bins of this peak differ by 60 dB, which used to render as a single step. + const auto levels = makeImpulseSpectrum (100); + const float step = 0.001f; + + float previous = SpectrumBinMapping::getInterpolatedLevel (levels, 99.0f); + float largestChange = 0.0f; + + for (float position = 99.0f + step; position <= 101.0f; position += step) + { + const float current = SpectrumBinMapping::getInterpolatedLevel (levels, position); + + largestChange = jmax (largestChange, std::abs (20.0f * std::log10 (current / previous))); + previous = current; + } + + EXPECT_LT (largestChange, 0.5f); +} + +TEST_F (SpectrumBinMappingTests, InterpolatedLevelNeverSkipsAValueBetweenNeighbouringBins) +{ + // Consecutive fractions of a bin must never report the very same level: that is the flat + // staircase the interpolation is meant to remove. + const auto levels = makeDecibelRampSpectrum (-40.0f); + const float lastBin = static_cast (spectrumTestNumBins() - 1); + const float step = 0.01f; + + int identicalNeighbours = 0; + float previous = SpectrumBinMapping::getInterpolatedLevel (levels, 1.0f); + + for (float position = 1.0f + step; position <= lastBin - 1.0f; position += step) + { + const float current = SpectrumBinMapping::getInterpolatedLevel (levels, position); + + if (current == previous) + ++identicalNeighbours; + + EXPECT_LE (current, previous); + previous = current; + } + + EXPECT_EQ (0, identicalNeighbours); +} + +TEST_F (SpectrumBinMappingTests, InterpolatedLevelIsMonotoneOnAMonotoneSpectrum) +{ + std::vector levels (static_cast (spectrumTestNumBins()), 0.0f); + + for (int bin = 0; bin < spectrumTestNumBins(); ++bin) + levels[static_cast (bin)] = 0.05f + 0.9f * static_cast (bin) / static_cast (spectrumTestNumBins() - 1); + + float previous = 0.0f; + + for (float position = 0.0f; position <= static_cast (spectrumTestNumBins() - 1); position += 0.01f) + { + const float current = SpectrumBinMapping::getInterpolatedLevel (levels, position); + + EXPECT_GE (current, previous - 1.0e-7f); + previous = current; + } +} + +TEST_F (SpectrumBinMappingTests, InterpolatedLevelStaysWithinTheSurroundingBins) +{ + constexpr int peakBin = 512; + const auto levels = makeImpulseSpectrum (peakBin); + + EXPECT_NEAR (1.0f, SpectrumBinMapping::getInterpolatedLevel (levels, static_cast (peakBin)), 1.0e-6f); + + for (float position = peakBin - 2.0f; position <= peakBin + 2.0f; position += 0.005f) + { + const float level = SpectrumBinMapping::getInterpolatedLevel (levels, position); + + EXPECT_GE (level, 0.0f); + EXPECT_LE (level, 1.0f + 1.0e-6f); + } +} + +TEST_F (SpectrumBinMappingTests, InterpolatedLevelClampsOutsideTheBinRange) +{ + const auto levels = makeDecibelRampSpectrum(); + + EXPECT_FLOAT_EQ (levels.front(), SpectrumBinMapping::getInterpolatedLevel (levels, -12.0f)); + EXPECT_FLOAT_EQ (levels.back(), SpectrumBinMapping::getInterpolatedLevel (levels, static_cast (spectrumTestNumBins()) + 30.0f)); +} + +TEST_F (SpectrumBinMappingTests, InterpolatedLevelHandlesEmptyAndSingleBinInputs) +{ + EXPECT_FLOAT_EQ (0.0f, SpectrumBinMapping::getInterpolatedLevel ({}, 4.0f)); + + const std::vector singleBin { 0.25f }; + EXPECT_FLOAT_EQ (0.25f, SpectrumBinMapping::getInterpolatedLevel (singleBin, 0.0f)); + EXPECT_FLOAT_EQ (0.25f, SpectrumBinMapping::getInterpolatedLevel (singleBin, 17.0f)); +} + +//============================================================================== +// Band aggregation +//============================================================================== + +TEST_F (SpectrumBinMappingTests, ConstantSpectrumIntegratesToTheBandWidth) +{ + const std::vector levels (static_cast (spectrumTestNumBins()), 1.0f); + const int points[] = { 0, 1, 100, 400, spectrumTestNumDisplayPoints - 1 }; + + for (auto point : points) + { + const auto& range = mapping.getRange (point); + const float bandWidth = range.endBin - range.startBin; + + EXPECT_NEAR (bandWidth, mapping.getBandLevel (levels, point, SpectrumBinMapping::BandAggregation::sum), bandWidth * 1.0e-3f + 1.0e-6f); + EXPECT_NEAR (1.0f, mapping.getBandLevel (levels, point, SpectrumBinMapping::BandAggregation::mean), 1.0e-4f); + EXPECT_NEAR (1.0f, mapping.getBandLevel (levels, point, SpectrumBinMapping::BandAggregation::peak), 1.0e-4f); + } +} + +TEST_F (SpectrumBinMappingTests, PeakAggregationFindsTheLoudestBinInsideTheBand) +{ + constexpr float peakBin = 100.0f; + const auto levels = makeImpulseSpectrum (static_cast (peakBin)); + + // Find the display point whose band covers the peak bin. + int coveringPoint = -1; + + for (int point = 0; point < mapping.getNumDisplayPoints(); ++point) + if (mapping.getRange (point).startBin <= peakBin && peakBin <= mapping.getRange (point).endBin) + coveringPoint = point; + + ASSERT_GE (coveringPoint, 0); + EXPECT_NEAR (1.0f, mapping.getBandLevel (levels, coveringPoint, SpectrumBinMapping::BandAggregation::peak), 1.0e-4f); +} + +TEST_F (SpectrumBinMappingTests, PeakAggregationIsBoundedByTheBandAndItsEdgeLevels) +{ + const auto levels = makeDecibelRampSpectrum (-30.0f); + + for (int point = 0; point < mapping.getNumDisplayPoints(); ++point) + { + const auto& range = mapping.getRange (point); + + const float edgePeak = jmax (SpectrumBinMapping::getInterpolatedLevel (levels, range.startBin), + SpectrumBinMapping::getInterpolatedLevel (levels, range.endBin)); + + float highestBinInside = 0.0f; + + for (int bin = (int) std::ceil (range.startBin); bin <= (int) std::floor (range.endBin); ++bin) + highestBinInside = jmax (highestBinInside, levels[static_cast (jlimit (0, spectrumTestNumBins() - 1, bin))]); + + const float bandPeak = mapping.getBandLevel (levels, point, SpectrumBinMapping::BandAggregation::peak); + + EXPECT_GE (bandPeak, edgePeak * (1.0f - 1.0e-5f)); + EXPECT_LE (bandPeak, jmax (edgePeak, highestBinInside) * (1.0f + 1.0e-5f)); + } +} + +TEST_F (SpectrumBinMappingTests, MeanAggregationIsTheSumDividedByTheBandWidth) +{ + const auto levels = makeDecibelRampSpectrum(); + + for (int point = 0; point < mapping.getNumDisplayPoints(); point += 32) + { + const auto& range = mapping.getRange (point); + const float bandWidth = range.endBin - range.startBin; + + const float sum = mapping.getBandLevel (levels, point, SpectrumBinMapping::BandAggregation::sum); + const float mean = mapping.getBandLevel (levels, point, SpectrumBinMapping::BandAggregation::mean); + + EXPECT_NEAR (sum / bandWidth, mean, sum * 1.0e-3f + 1.0e-6f); + } +} + +TEST_F (SpectrumBinMappingTests, BandLevelsAreSafeForOutOfRangeIndicesAndEmptyInputs) +{ + const std::vector noLevels; + + EXPECT_FLOAT_EQ (0.0f, mapping.getBandLevel (noLevels, 0, SpectrumBinMapping::BandAggregation::peak)); + EXPECT_FLOAT_EQ (0.0f, mapping.getBandLevel ({}, -10, SpectrumBinMapping::BandAggregation::sum)); + + const auto levels = makeDecibelRampSpectrum(); + EXPECT_FLOAT_EQ (0.0f, mapping.getBandLevel (levels, -10, SpectrumBinMapping::BandAggregation::peak)); + EXPECT_GT (mapping.getBandLevel (levels, 0, SpectrumBinMapping::BandAggregation::peak), 0.0f); +} + +//============================================================================== +// Analyzer display scenario +//============================================================================== + +TEST_F (SpectrumBinMappingTests, NeighbouringDisplayPointsNeverReportTheSameLevel) +{ + // A realistic configuration: 512 display points over 20 Hz > 20 kHz at 44.1 kHz, where the + // lowest display bands are a fraction of an FFT bin wide. This is the region that used to + // collapse into one flat value per bin. + const auto levels = makeDecibelRampSpectrum (-45.0f); + + int identicalNeighbours = 0; + float largestChange = 0.0f; + + for (int point = 1; point < mapping.getNumDisplayPoints(); ++point) + { + const float previous = mapping.getBandLevel (levels, point - 1, SpectrumBinMapping::BandAggregation::peak); + const float current = mapping.getBandLevel (levels, point, SpectrumBinMapping::BandAggregation::peak); + + if (current == previous) + ++identicalNeighbours; + + largestChange = jmax (largestChange, std::abs (20.0f * std::log10 (current / previous))); + } + + EXPECT_EQ (0, identicalNeighbours); + EXPECT_LT (largestChange, 1.0f); +} + +TEST_F (SpectrumBinMappingTests, LowAndHighFrequencyBandsBothReportThePeakLevel) +{ + const auto levels = makeDecibelRampSpectrum (-45.0f); + + for (int point = 0; point < mapping.getNumDisplayPoints(); point += 64) + { + const auto& range = mapping.getRange (point); + + // The peak of this monotone spectrum always sits at the lower edge of the band. + const float expected = SpectrumBinMapping::getInterpolatedLevel (levels, range.startBin); + const float actual = mapping.getBandLevel (levels, point, SpectrumBinMapping::BandAggregation::peak); + + EXPECT_NEAR (expected, actual, expected * 1.0e-3f + 1.0e-9f); + } +} + +TEST_F (SpectrumBinMappingTests, SubBinBandsIntegrateLessPowerThanWideBands) +{ + // Two display points on a constant spectrum: the power integrated over a sub-bin band must stay + // below the power integrated over band that spans several bins. + const std::vector levels (static_cast (spectrumTestNumBins()), 1.0f); + + const auto& narrowRange = mapping.getRange (0); + const auto& wideRange = mapping.getRange (spectrumTestNumDisplayPoints - 1); + + ASSERT_LT (narrowRange.endBin - narrowRange.startBin, 1.0f); + ASSERT_GT (wideRange.endBin - wideRange.startBin, 1.0f); + + EXPECT_LT (mapping.getBandLevel (levels, 0, SpectrumBinMapping::BandAggregation::sum), + mapping.getBandLevel (levels, spectrumTestNumDisplayPoints - 1, SpectrumBinMapping::BandAggregation::sum)); + + // The average level of a constant spectrum is the same everywhere, whatever the band width. + EXPECT_NEAR (mapping.getBandLevel (levels, 0, SpectrumBinMapping::BandAggregation::mean), + mapping.getBandLevel (levels, spectrumTestNumDisplayPoints - 1, SpectrumBinMapping::BandAggregation::mean), + 1.0e-4f); +} From 3c0fa642808a36398fe2febf3cc11d12fad9e333 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Sat, 12 Sep 2026 15:43:02 +0200 Subject: [PATCH 4/6] More spectrogram fixes --- CHANGELOG.md | 3 +- .../displays/yup_SpectrogramComponent.cpp | 189 +++++++++++++----- .../displays/yup_SpectrogramComponent.h | 2 + .../yup_SpectrogramComponent.cpp | 41 ++++ 4 files changed, 180 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acd767b74..b4fdc232c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -168,7 +168,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Audio GUI (`yup_audio_gui`) -- `SpectrogramComponent` now keeps its waterfall history on the GPU: a precompiled `.ysl` shader bundle (embedded in `yup_SpectrogramComponentShader.inc`, built with the `yup_shader_bundler` host tool) drives a single fullscreen-triangle `GpuRenderPass` (see `GpuPipeline`) that scrolls the previous frame down by the pending rows and writes the new rows with the color map applied entirely on the GPU, uploading only the raw magnitudes as a uniform buffer - no per-paint CPU pixel upload, no GPU texture creation, and no 2D canvas flush (if the bundle cannot be compiled no waterfall is rendered). Pending FFT rows are always consumed (applied or dropped) so the update queue can never accumulate. The log-frequency → FFT-bin mapping is precomputed once per configuration instead of recomputed with pow/log per row, and the frequency grid (lines + labels) is cached in an offscreen canvas and only re-rendered when the frequency range or size changes. The component now requires a GPU render context (the CPU `Image` fallback was removed). The component's per-frame `refreshDisplay` hook processes pending FFT rows, and the history is presented at a fractional vertical offset that advances at the FFT row rate, so the waterfall scrolls smoothly between rows instead of jumping a row per update; the offset is clamped to a single row so bursts of FFT rows can never push the waterfall off-screen. The scroll speed is adjustable via the new `setScrollSpeed()` multiplier (1.0 = realtime, 0.0 = paused). +- `SpectrogramComponent` now keeps its waterfall history on the GPU: a precompiled `.ysl` shader bundle (embedded in `yup_SpectrogramComponentShader.inc`, built with the `yup_shader_bundler` host tool) drives a single fullscreen-triangle `GpuRenderPass` (see `GpuPipeline`) that scrolls the previous frame down by the pending rows and writes the new rows with the color map applied entirely on the GPU, uploading only the raw magnitudes as a uniform buffer - no per-paint CPU pixel upload, no GPU texture creation, and no 2D canvas flush (if the bundle cannot be compiled no waterfall is rendered). Pending FFT rows are always consumed (applied or dropped) so the update queue can never accumulate. The log-frequency → FFT-bin mapping is precomputed once per configuration instead of recomputed with pow/log per row, and the frequency grid (lines + labels) is cached in an offscreen canvas and only re-rendered when the frequency range or size changes. The component now requires a GPU render context (the CPU `Image` fallback was removed). The component's per-frame `refreshDisplay` hook processes pending FFT rows, and the history is presented at a fractional vertical offset that slides the newest row into place over one row period; that offset is pulled back with a fixed time constant instead of being clamped, so the motion never stalls at the edge of its window. The scroll speed is adjustable via the new `setScrollSpeed()` multiplier (1.0 = realtime, 0.0 = paused). +- `SpectrogramComponent`'s waterfall scroll is now independent of the frame rate. The component requests a repaint on every frame while the waterfall is live (through the existing `refreshDisplay` hook) instead of only when an FFT row arrives, so the sub-row offset above is actually rendered - previously the repaint cadence matched the row arrival cadence exactly, which made the display advance one whole row per repaint (one pixel, for a component whose height matches the history). A frame that produced more FFT rows than one GPU pass can write (a pass writes `defaultSpectrogramMagnitudes / defaultSpectrogramWidth` rows) now drains them over several passes instead of dropping the surplus, which is what happened whenever the display ran below the FFT row rate: at 30 fps half the history of a 2048/1024 configuration was silently lost. `setScrollSpeed (0.0)` now truly freezes the waterfall (pending rows are discarded rather than written, so the content no longer keeps sliding down while paused), and the animation stops requesting repaints once the analysis stalls instead of spinning on a frozen frame - `SpectrogramComponent` waterfall failures (shader bundle load, pipeline compile, and GPU pass encode/draw) are now reported via `Logger::outputDebugString` in all build configurations instead of silently dropping pending rows, and the waterfall texture's render resolution is exposed as the new `defaultSpectrogramRenderWidth` constant (2x the frequency-bin count - `getSpectrogramImage()` returns that full-resolution image). - Fixed `SpectrumAnalyzerState` never flagging FFT data as ready after a single bulk `pushSamples()`: the readiness check ran before the scoped FIFO write had committed (the `AbstractFifo::ScopedWrite` commits in its destructor), so `isFFTDataReady()` stayed false until a second push arrived. `pushSample()`/`pushSamples()` now commit the write before checking, so a pushed window is immediately available to `SpectrogramComponent::refreshDisplay()` instead of leaving the backlog untouched. - `SpectrumAnalyzerComponent` and `SpectrogramComponent` no longer snap every display point to its nearest FFT bin, which rendered identical levels (a flat staircase) for all the points sharing one bin. The new `SpectrumBinMapping` helper (`displays/yup_SpectrumBinMapping.h`) holds the shared log-frequency → fractional FFT bin mapping and evaluates levels continuously: the three bins surrounding a fractional position are parabolically interpolated in the amplitude decibel domain, evaluated at that position rather than at the vertex of the parabola, with a monotone linear fallback where the neighbours are not concave so a steep bin pair cannot undershoot. Display bands are aggregated over their fractional edges - `peak` for the peak/RMS level modes, `sum` (the levels integrated across the band width, in bin units) for `powerDecibels` and `mean` for `powerSpectralDensity` - so bins entering or leaving a band no longer step the displayed level. In the power modes this makes the band power an integral over the band's bandwidth instead of a sum over the integer bins its edges happen to touch diff --git a/modules/yup_audio_gui/displays/yup_SpectrogramComponent.cpp b/modules/yup_audio_gui/displays/yup_SpectrogramComponent.cpp index 636912f4f..116906a18 100644 --- a/modules/yup_audio_gui/displays/yup_SpectrogramComponent.cpp +++ b/modules/yup_audio_gui/displays/yup_SpectrogramComponent.cpp @@ -233,6 +233,14 @@ void SpectrogramComponent::applyPendingRows() if (pendingRows.empty()) return; + // A paused waterfall holds its last frame: new rows are discarded rather than written, otherwise + // the content would keep sliding down even though the scroll is stopped. + if (scrollSpeedMultiplier <= 0.0f) + { + pendingRows.clear(); + return; + } + if (waterfallPipeline == nullptr || gpuTargets[0] == nullptr || gpuTargets[1] == nullptr) { Logger::outputDebugString ("SpectrogramComponent: dropping " + String (static_cast (pendingRows.size())) @@ -241,74 +249,96 @@ void SpectrogramComponent::applyPendingRows() return; } - const int numRows = static_cast (pendingRows.size()); const int maxShaderRows = jmax (1, static_cast (rowDataCache.size()) / spectrogramWidth); - const int appliedRows = jmin (numRows, maxShaderRows); - - auto& previous = gpuTargets[pingPongIndex]; - auto& current = gpuTargets[pingPongIndex ^ 1]; - const WaterfallParams params { static_cast (appliedRows), - static_cast (defaultSpectrogramRenderWidth), // render width - static_cast (numHistoryFrames), - static_cast (spectrogramWidth), // bins - 0.0f, - 0.0f, - 0.0f, - 0.0f }; + // A single pass can only shift and write maxShaderRows rows, so a frame that produced more FFT rows + // than that - which is what happens whenever the display runs below the FFT row rate - needs several + // passes. Without them the surplus rows would be dropped and the history would silently lose data, so + // how much of the waterfall survives would depend on the frame rate. The pass count stays bounded so + // that a large backlog cannot stall the frame. + constexpr int maxPassesPerFrame = 4; - for (int row = 0; row < appliedRows; ++row) + for (int pass = 0; pass < maxPassesPerFrame && ! pendingRows.empty(); ++pass) { - const auto& magnitudes = pendingRows[static_cast (row)]; - std::copy (magnitudes.begin(), - magnitudes.begin() + spectrogramWidth, - rowDataCache.begin() + static_cast (row) * spectrogramWidth); - } - - if (lutNeedsRefresh) - { - const auto colorTable = colorMap.getColorTable(); - std::copy (colorTable.begin(), - colorTable.begin() + static_cast (jmin (colorTable.size(), lutDataCache.size())), - lutDataCache.begin()); - lutNeedsRefresh = false; - } + const int numRows = static_cast (pendingRows.size()); + const int appliedRows = jmin (numRows, maxShaderRows); + + auto& previous = gpuTargets[pingPongIndex]; + auto& current = gpuTargets[pingPongIndex ^ 1]; + + const WaterfallParams params { static_cast (appliedRows), + static_cast (defaultSpectrogramRenderWidth), // render width + static_cast (numHistoryFrames), + static_cast (spectrogramWidth), // bins + 0.0f, + 0.0f, + 0.0f, + 0.0f }; + + for (int row = 0; row < appliedRows; ++row) + { + const auto& magnitudes = pendingRows[static_cast (row)]; + std::copy (magnitudes.begin(), + magnitudes.begin() + spectrogramWidth, + rowDataCache.begin() + static_cast (row) * spectrogramWidth); + } - auto frame = GpuFrame::begin (gpuDevice); - if (frame.isValid()) - { - auto pass = current->beginRenderPass (frame, { false, GpuColor::transparentBlack() }); - if (pass.isValid()) + if (lutNeedsRefresh) { - pass.setPipeline (waterfallPipeline); - pass.setTexture (0, 0, previous->asTexture()); - pass.setUniformBuffer (0, 2, ¶ms, sizeof (params)); - pass.setUniformBuffer (0, 3, rowDataCache.data(), rowDataCache.size() * sizeof (float)); - pass.setUniformBuffer (0, 4, lutDataCache.data(), lutDataCache.size() * sizeof (uint32)); + const auto colorTable = colorMap.getColorTable(); + std::copy (colorTable.begin(), + colorTable.begin() + static_cast (jmin (colorTable.size(), lutDataCache.size())), + lutDataCache.begin()); + lutNeedsRefresh = false; + } - if (pass.draw (3) && pass.finish() && frame.submit()) - { - displayTexture = current->asTexture(); + bool rendered = false; - pingPongIndex ^= 1; - scrollOffset -= static_cast (appliedRows); + auto frame = GpuFrame::begin (gpuDevice); + if (frame.isValid()) + { + auto pass = current->beginRenderPass (frame, { false, GpuColor::transparentBlack() }); + if (pass.isValid()) + { + pass.setPipeline (waterfallPipeline); + pass.setTexture (0, 0, previous->asTexture()); + pass.setUniformBuffer (0, 2, ¶ms, sizeof (params)); + pass.setUniformBuffer (0, 3, rowDataCache.data(), rowDataCache.size() * sizeof (float)); + pass.setUniformBuffer (0, 4, lutDataCache.data(), lutDataCache.size() * sizeof (uint32)); + + if (pass.draw (3) && pass.finish() && frame.submit()) + { + displayTexture = current->asTexture(); + + pingPongIndex ^= 1; + rendered = true; + + lastRowTimeMs = Time::getMillisecondCounter(); + } + else + { + Logger::outputDebugString ("SpectrogramComponent: waterfall draw/finish/submit failed - rows not applied"); + } } else { - Logger::outputDebugString ("SpectrogramComponent: waterfall draw/finish/submit failed - rows not applied"); + Logger::outputDebugString ("SpectrogramComponent: beginRenderPass failed - rows not applied"); } } else { - Logger::outputDebugString ("SpectrogramComponent: beginRenderPass failed - rows not applied"); + Logger::outputDebugString ("SpectrogramComponent: GpuFrame::begin failed - rows not applied"); } - } - else - { - Logger::outputDebugString ("SpectrogramComponent: GpuFrame::begin failed - rows not applied"); - } - pendingRows.erase (pendingRows.begin(), pendingRows.begin() + appliedRows); + // The rows are always consumed, rendered or not, so the update queue can never accumulate. The + // scroll offset moves down with them, which keeps the history below them aligned with the audio + // timeline even when rows had to be skipped. + pendingRows.erase (pendingRows.begin(), pendingRows.begin() + appliedRows); + scrollOffset -= static_cast (appliedRows); + + if (! rendered) + break; + } } void SpectrogramComponent::advanceScroll() @@ -318,13 +348,58 @@ void SpectrogramComponent::advanceScroll() lastPaintTimeMs = now; const float rowRate = getRowRate(); - const float advance = rowRate > 0.0f ? rowRate * scrollSpeedMultiplier * jlimit (0.0f, 0.25f, elapsedSeconds) : 0.0f; - scrollOffset = jlimit (-1.0f, 0.0f, scrollOffset + advance); + + if (rowRate <= 0.0f || scrollSpeedMultiplier <= 0.0f) + return; // Paused: the offset is held where it is. + + scrollOffset += rowRate * scrollSpeedMultiplier * jlimit (0.0f, 0.25f, elapsedSeconds); + + // The offset is anchored to the newest row written into the history: it starts one row above the top + // edge when that row is written and slides into place over one row period. Clamping it at the ends of + // that window would stop the motion dead until the next row arrived - which is what used to make the + // waterfall advance exactly one row per repaint - so the excess is pulled back with a fixed time + // constant instead. The scroll therefore stays continuous and independent of the frame rate and of + // the jitter of the row arrivals; the absolute bound below only matters when the analysis stalls. + constexpr float pullBackTimeConstantSeconds = 0.1f; + const float pullBackGain = elapsedSeconds > 0.0f + ? 1.0f - std::exp (-elapsedSeconds / pullBackTimeConstantSeconds) + : 0.0f; + + if (scrollOffset > 0.0f) + scrollOffset -= pullBackGain * scrollOffset; + else if (scrollOffset < -1.0f) + scrollOffset += pullBackGain * (-1.0f - scrollOffset); + + scrollOffset = jlimit (-1.5f, 0.5f, scrollOffset); } void SpectrogramComponent::setScrollSpeed (float scrollSpeedMultiplier) { this->scrollSpeedMultiplier = jmax (0.0f, scrollSpeedMultiplier); + + // Start animating immediately when the scroll is (re)enabled. + if (this->scrollSpeedMultiplier > 0.0f) + repaint(); +} + +bool SpectrogramComponent::isAnimationRunning() const noexcept +{ + const float rowRate = getRowRate(); + + if (scrollSpeedMultiplier <= 0.0f || rowRate <= 0.0f) + return false; + + if (! pendingRows.empty()) + return true; + + // Once the analysis stalls the offset settles at the end of its window, so only a recent row needs + // further repaints. + if (lastRowTimeMs == 0) + return false; + + const float secondsSinceLastRow = static_cast (Time::getMillisecondCounter() - lastRowTimeMs) / 1000.0f; + + return secondsSinceLastRow * rowRate < 2.0f; } void SpectrogramComponent::refreshDisplay (double lastFrameTimeSeconds) @@ -367,7 +442,10 @@ void SpectrogramComponent::refreshDisplay (double lastFrameTimeSeconds) if (pendingRows.size() > 16) pendingRows.erase (pendingRows.begin(), pendingRows.begin() + static_cast (pendingRows.size() - 16)); - if (hasNewData) + // Repaint on every frame while the waterfall is live rather than only when a row arrives: the + // sub-row scroll offset has to be rendered at the display refresh rate, otherwise the waterfall + // only ever moves the whole rows written since the previous repaint. + if (hasNewData || isAnimationRunning()) repaint(); } @@ -479,6 +557,7 @@ bool SpectrogramComponent::ensureGpuTargets (GraphicsContext& context) scrollOffset = 0.0f; lastPaintTimeMs = 0; + lastRowTimeMs = 0; return true; } @@ -754,6 +833,7 @@ void SpectrogramComponent::setNumHistoryFrames (int numFrames) pendingRows.clear(); scrollOffset = 0.0f; lastPaintTimeMs = 0; + lastRowTimeMs = 0; gpuTargets[0] = nullptr; gpuTargets[1] = nullptr; @@ -777,6 +857,7 @@ void SpectrogramComponent::clearHistory() pendingRows.clear(); scrollOffset = 0.0f; lastPaintTimeMs = 0; + lastRowTimeMs = 0; gpuTargets[0] = nullptr; gpuTargets[1] = nullptr; diff --git a/modules/yup_audio_gui/displays/yup_SpectrogramComponent.h b/modules/yup_audio_gui/displays/yup_SpectrogramComponent.h index 7161106a7..cd00f6526 100644 --- a/modules/yup_audio_gui/displays/yup_SpectrogramComponent.h +++ b/modules/yup_audio_gui/displays/yup_SpectrogramComponent.h @@ -282,6 +282,7 @@ class YUP_API SpectrogramComponent : public Component void ensureWaterfallPipeline(); void applyPendingRows(); void advanceScroll(); + bool isAnimationRunning() const noexcept; float getRowRate() const noexcept; void initializeFFTBuffers(); void generateWindow(); @@ -318,6 +319,7 @@ class YUP_API SpectrogramComponent : public Component int pingPongIndex = 0; float scrollOffset = 0.0f; uint32 lastPaintTimeMs = 0; + uint32 lastRowTimeMs = 0; // Cached frequency grid (frequency lines + labels). GpuCanvas::Ptr gridCanvas; diff --git a/tests/yup_audio_gui/yup_SpectrogramComponent.cpp b/tests/yup_audio_gui/yup_SpectrogramComponent.cpp index ab23a8639..f1466a364 100644 --- a/tests/yup_audio_gui/yup_SpectrogramComponent.cpp +++ b/tests/yup_audio_gui/yup_SpectrogramComponent.cpp @@ -388,6 +388,47 @@ TEST_F (SpectrogramComponentTests, PaintAfterRefreshDisplayDoesNotCrash) EXPECT_TRUE (true); } +TEST_F (SpectrogramComponentTests, FrameProducingMoreRowsThanOnePassKeepsEveryRow) +{ + // refreshDisplay() only processes FFTs while the component is showing. + auto parent = std::make_unique ("parent"); + parent->setVisible (true); + parent->addAndMakeVisible (*spectrogram); + + spectrogram->setNumHistoryFrames (32); + spectrogram->setFFTSize (512); + spectrogram->setOverlapFactor (0.75f); // hop = fftSize / 4 + + // Enough audio for more analysis windows than a single GPU pass can write. + const auto testData = createSineBuffer (spectrogram->getFFTSize() * 5, 100.0f); + state->pushSamples (testData.data(), static_cast (testData.size())); + + spectrogram->refreshDisplay (1.0 / 60.0); + + auto context = yup_constructHeadlessGraphicsContext ({}, {}); + auto renderer = context->makeRenderer (800, 400); + Graphics g (*context, *renderer); + + spectrogram->paint (g); + + const auto image = spectrogram->getSpectrogramImage(); + + if (! image.isValid()) + GTEST_SKIP() << "No GPU context available for the waterfall texture"; + + // The rows written by this frame sit at the top of the history, above the cleared rows. A single + // pass only writes defaultSpectrogramMagnitudes / defaultSpectrogramWidth of them, so the frame has + // to drain its rows over several passes instead of dropping the surplus, which is what made how + // much of the waterfall survived depend on the frame rate. + int writtenRows = 0; + + while (writtenRows < image.getHeight() + && ! spectrogramRowIsColor (image, writtenRows, 0xff0a0a0au)) + ++writtenRows; + + EXPECT_GE (writtenRows, 3); +} + TEST_F (SpectrogramComponentTests, ResizedDoesNotCrash) { spectrogram->setBounds (0.0f, 0.0f, 1000.0f, 600.0f); From 8eb0331f10c1d3e289f024d5181b0f505ef5cac1 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Sun, 13 Sep 2026 20:10:47 +0200 Subject: [PATCH 5/6] Fix aligned buffer in PFFFT --- CHANGELOG.md | 2 + .../yup_dsp/frequency/yup_FFTProcessor.cpp | 129 +++++++++++------- tests/yup_dsp/yup_FFTProcessor.cpp | 32 +++++ 3 files changed, 117 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4fdc232c..c10f05a74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - `FFTProcessor` is now templated on the sample type - `FFTProcessor` (the default) or `FFTProcessor` - and every backend (PFFFT, Apple vDSP, Intel IPP, FFTW3 and the Ooura fallback, which now ships both a `float` and a `double` implementation) gained a native double-precision path. References to the nested scaling enum need qualifying, e.g. `FFTProcessor::FFTScaling::asymmetric` +- Fixed the PFFFT-backed `FFTProcessor` requiring its input and output buffers to be SIMD aligned: the transforms are now staged through buffers owned by the PFFFT backend and allocated with PFFFT's own aligned allocator, so the public API accepts buffers with any alignment (the double-precision real transform previously hit PFFFT's `VALIGNED` assertion when handed a plain `std::vector`) + ### Graphics - `Image::getWidth()` and `Image::getHeight()` now return 0 on an invalid image instead of asserting and dereferencing null. Other accessors and pixel access still assert, as documented diff --git a/modules/yup_dsp/frequency/yup_FFTProcessor.cpp b/modules/yup_dsp/frequency/yup_FFTProcessor.cpp index eb55182e8..7fc9520ab 100644 --- a/modules/yup_dsp/frequency/yup_FFTProcessor.cpp +++ b/modules/yup_dsp/frequency/yup_FFTProcessor.cpp @@ -52,6 +52,40 @@ class FFTEngine // PFFFT implementation #if YUP_FFT_USING_PFFFT +namespace detail +{ + +/** @internal Releases a buffer obtained from the PFFFT aligned allocator. */ +template +struct PFFTAlignedDeleter +{ + void operator() (SampleType* ptr) const noexcept + { + if constexpr (std::is_same_v) + pffftd_aligned_free (ptr); + else + pffft_aligned_free (ptr); + } +}; + +/** @internal Buffer with the SIMD alignment that PFFFT requires for its operands. */ +template +using PFFTAlignedBuffer = std::unique_ptr>; + +/** @internal Allocates a buffer that PFFFT can operate on directly. */ +template +PFFTAlignedBuffer makePFFTAlignedBuffer (size_t numElements) +{ + const auto numBytes = numElements * sizeof (SampleType); + + if constexpr (std::is_same_v) + return PFFTAlignedBuffer (static_cast (pffftd_aligned_malloc (numBytes))); + + return PFFTAlignedBuffer (static_cast (pffft_aligned_malloc (numBytes))); +} + +} // namespace detail + template class PFFTEngine : public detail::FFTEngine { @@ -75,11 +109,13 @@ class PFFTEngine : public detail::FFTEngine complexSetup = pffft_new_setup (this->fftSize, PFFFT_COMPLEX); } - tempBuffer.resize (static_cast (this->fftSize * 2)); + // PFFFT only accepts SIMD aligned buffers, so every transform is staged through + // buffers owned by this engine instead of touching the caller's memory directly. + const auto bufferSize = static_cast (this->fftSize) * 2; - // Allocate work buffers - PFFFT uses stack for small sizes, heap for larger - if (this->fftSize >= 16384) - workBuffer.resize (static_cast (this->fftSize)); + inputBuffer = detail::makePFFTAlignedBuffer (bufferSize); + outputBuffer = detail::makePFFTAlignedBuffer (bufferSize); + workBuffer = detail::makePFFTAlignedBuffer (bufferSize); } void cleanup() override @@ -108,84 +144,85 @@ class PFFTEngine : public detail::FFTEngine complexSetupD = nullptr; } - workBuffer.clear(); - tempBuffer.clear(); + inputBuffer.reset(); + outputBuffer.reset(); + workBuffer.reset(); } void performRealFFTForward (const SampleType* realInput, SampleType* complexOutput) override { - SampleType* workPtr = workBuffer.empty() ? nullptr : workBuffer.data(); + std::copy_n (realInput, this->fftSize, inputBuffer.get()); - if constexpr (std::is_same_v) - pffftd_transform_ordered (realSetupD, realInput, complexOutput, workPtr, PFFFT_FORWARD); - else - pffft_transform_ordered (realSetup, realInput, complexOutput, workPtr, PFFFT_FORWARD); + performTransform (PFFFT_FORWARD); - convertFromPFFTPacked (complexOutput, this->fftSize); + // PFFFT packed: [DC_real, Nyquist_real, bin1_real, bin1_imag, bin2_real, bin2_imag, ...] + // Standard: [DC_real, DC_imag, bin1_real, bin1_imag, ..., Nyquist_real, Nyquist_imag] + std::copy_n (outputBuffer.get(), this->fftSize, complexOutput); + + complexOutput[this->fftSize] = outputBuffer[1]; // Nyquist real (from packed[1]) + complexOutput[this->fftSize + 1] = SampleType (0); // Nyquist imaginary (always 0) + complexOutput[1] = SampleType (0); // DC imaginary (always 0) } void performRealFFTInverse (const SampleType* complexInput, SampleType* realOutput) override { - SampleType* workPtr = workBuffer.empty() ? nullptr : workBuffer.data(); + // Standard: [DC_real, DC_imag, bin1_real, bin1_imag, ..., Nyquist_real, Nyquist_imag] + // PFFFT packed: [DC_real, Nyquist_real, bin1_real, bin1_imag, bin2_real, bin2_imag, ...] + inputBuffer[0] = complexInput[0]; // DC real + inputBuffer[1] = complexInput[this->fftSize]; // Nyquist real (to packed[1]) + std::memcpy (inputBuffer.get() + 2, complexInput + 2, static_cast (this->fftSize - 2) * sizeof (SampleType)); - convertToPFFTPacked (complexInput, tempBuffer.data(), this->fftSize); + performTransform (PFFFT_BACKWARD); - if constexpr (std::is_same_v) - pffftd_transform_ordered (realSetupD, tempBuffer.data(), realOutput, workPtr, PFFFT_BACKWARD); - else - pffft_transform_ordered (realSetup, tempBuffer.data(), realOutput, workPtr, PFFFT_BACKWARD); + std::copy_n (outputBuffer.get(), this->fftSize, realOutput); } void performComplexFFTForward (const SampleType* complexInput, SampleType* complexOutput) override { - SampleType* workPtr = workBuffer.empty() ? nullptr : workBuffer.data(); + std::copy_n (complexInput, this->fftSize * 2, inputBuffer.get()); - if constexpr (std::is_same_v) - pffftd_transform_ordered (complexSetupD, complexInput, complexOutput, workPtr, PFFFT_FORWARD); - else - pffft_transform_ordered (complexSetup, complexInput, complexOutput, workPtr, PFFFT_FORWARD); + performTransform (PFFFT_FORWARD); + + std::copy_n (outputBuffer.get(), this->fftSize * 2, complexOutput); } void performComplexFFTInverse (const SampleType* complexInput, SampleType* complexOutput) override { - SampleType* workPtr = workBuffer.empty() ? nullptr : workBuffer.data(); + std::copy_n (complexInput, this->fftSize * 2, inputBuffer.get()); - if constexpr (std::is_same_v) - pffftd_transform_ordered (complexSetupD, complexInput, complexOutput, workPtr, PFFFT_BACKWARD); - else - pffft_transform_ordered (complexSetup, complexInput, complexOutput, workPtr, PFFFT_BACKWARD); + performTransform (PFFFT_BACKWARD); + + std::copy_n (outputBuffer.get(), this->fftSize * 2, complexOutput); } String getBackendName() const override { return "PFFFT"; } private: - // Convert from PFFFT packed format to standard interleaved format - void convertFromPFFTPacked (SampleType* interleaved, int size) + enum class TransformKind { - // PFFFT packed: [DC_real, Nyquist_real, bin1_real, bin1_imag, bin2_real, bin2_imag, ...] - // Standard: [DC_real, DC_imag, bin1_real, bin1_imag, ..., Nyquist_real, Nyquist_imag] + real, + complex + }; - interleaved[size] = std::exchange (interleaved[1], SampleType (0)); // Nyquist real (from packed[1]) - interleaved[size + 1] = SampleType (0); // Nyquist imaginary (always 0) - } - - // Convert from standard interleaved format to PFFFT packed format - void convertToPFFTPacked (const SampleType* interleaved, SampleType* packed, int size) + // Runs a transform using the aligned buffers owned by this engine + template + void performTransform (pffft_direction_t direction) { - // Standard: [DC_real, DC_imag, bin1_real, bin1_imag, ..., Nyquist_real, Nyquist_imag] - // PFFFT packed: [DC_real, Nyquist_real, bin1_real, bin1_imag, bin2_real, bin2_imag, ...] - - packed[0] = interleaved[0]; // DC real - packed[1] = interleaved[size]; // Nyquist real (to packed[1]) - std::memcpy (&packed[2], &interleaved[2], static_cast (size - 2) * sizeof (SampleType)); + if constexpr (std::is_same_v) + pffftd_transform_ordered (kind == TransformKind::real ? realSetupD : complexSetupD, + inputBuffer.get(), outputBuffer.get(), workBuffer.get(), direction); + else + pffft_transform_ordered (kind == TransformKind::real ? realSetup : complexSetup, + inputBuffer.get(), outputBuffer.get(), workBuffer.get(), direction); } PFFFT_Setup* realSetup = nullptr; PFFFT_Setup* complexSetup = nullptr; PFFFTD_Setup* realSetupD = nullptr; PFFFTD_Setup* complexSetupD = nullptr; - std::vector workBuffer; - std::vector tempBuffer; + detail::PFFTAlignedBuffer inputBuffer; + detail::PFFTAlignedBuffer outputBuffer; + detail::PFFTAlignedBuffer workBuffer; }; #endif diff --git a/tests/yup_dsp/yup_FFTProcessor.cpp b/tests/yup_dsp/yup_FFTProcessor.cpp index 3ae30c732..c93bb7dba 100644 --- a/tests/yup_dsp/yup_FFTProcessor.cpp +++ b/tests/yup_dsp/yup_FFTProcessor.cpp @@ -599,6 +599,38 @@ TEST_F (FFTProcessorDoubleValidation, BackendIdentification) EXPECT_NE (backendName, "Unknown") << "Backend should be identified"; } +TEST_F (FFTProcessorDoubleValidation, HandlesUnalignedBuffers) +{ + // The PFFFT backend needs SIMD aligned buffers internally, which must not leak into + // the public API: callers may pass buffers with any alignment. + for (int order = 6; order <= 9; ++order) + { + const int size = 1 << order; + ProcessorType processor (size); + + std::vector inputStorage (size + 1); + std::vector outputStorage (size * 2 + 1); + std::vector referenceInput (size); + std::vector referenceOutput (size * 2); + + // Offset by one sample so the pointers are not aligned for vectorized access + SampleType* input = inputStorage.data() + 1; + SampleType* output = outputStorage.data() + 1; + + generateRandomReal (input, size); + + for (int i = 0; i < size; ++i) + referenceInput[i] = input[i]; + + computeReferenceRealDFT (referenceInput.data(), referenceOutput.data(), size); + processor.performRealFFTForward (input, output); + + const int numBins = size / 2 + 1; + EXPECT_TRUE (areArraysClose (output, referenceOutput.data(), numBins * 2, tightTolerance)) + << "Unaligned double real forward FFT failed for size " << size; + } +} + TEST_F (FFTProcessorDoubleValidation, RealForwardTransformAccuracy) { for (int order = 6; order <= 9; ++order) From e65ea98f88f30a6aebd09875852e99e0ae46abb2 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Sun, 13 Sep 2026 20:17:26 +0200 Subject: [PATCH 6/6] Fix double precision ooura --- CHANGELOG.md | 2 + .../frequency/yup_OouraFFT8g_double.cpp | 186 ++++++++++-------- 2 files changed, 103 insertions(+), 85 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c10f05a74..dc043d12f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - Fixed the PFFFT-backed `FFTProcessor` requiring its input and output buffers to be SIMD aligned: the transforms are now staged through buffers owned by the PFFFT backend and allocated with PFFFT's own aligned allocator, so the public API accepts buffers with any alignment (the double-precision real transform previously hit PFFFT's `VALIGNED` assertion when handed a plain `std::vector`) +- Fixed the double-precision Ooura FFT translation unit only building on GCC/Clang: it declared every internal helper (`makewt`, `cftfsub`, `bitrv2`, ...) inside the body of the functions that call them, and a block-scope declaration inside `namespace yup` declares a *global* function, so `yup::cdft` referenced a `::makewt` that no one defined and the Windows link failed with 30 unresolved externals. The declarations now sit at namespace scope, matching `yup_OouraFFT8g_float.cpp` + ### Graphics - `Image::getWidth()` and `Image::getHeight()` now return 0 on an invalid image instead of asserting and dereferencing null. Other accessors and pixel access still assert, as documented diff --git a/modules/yup_dsp/frequency/yup_OouraFFT8g_double.cpp b/modules/yup_dsp/frequency/yup_OouraFFT8g_double.cpp index fa2fd4d7f..9641ed2c5 100644 --- a/modules/yup_dsp/frequency/yup_OouraFFT8g_double.cpp +++ b/modules/yup_dsp/frequency/yup_OouraFFT8g_double.cpp @@ -313,11 +313,12 @@ Appendix : namespace yup { +void makewt (int nw, int* ip, double* w); +void cftfsub (int n, double* a, int* ip, int nw, double* w); +void cftbsub (int n, double* a, int* ip, int nw, double* w); + void cdft (int n, int isgn, double* a, int* ip, double* w) { - void makewt (int nw, int* ip, double* w); - void cftfsub (int n, double* a, int* ip, int nw, double* w); - void cftbsub (int n, double* a, int* ip, int nw, double* w); int nw; nw = ip[0]; @@ -336,14 +337,15 @@ void cdft (int n, int isgn, double* a, int* ip, double* w) } } +void makewt (int nw, int* ip, double* w); +void makect (int nc, int* ip, double* c); +void cftfsub (int n, double* a, int* ip, int nw, double* w); +void cftbsub (int n, double* a, int* ip, int nw, double* w); +void rftfsub (int n, double* a, int nc, double* c); +void rftbsub (int n, double* a, int nc, double* c); + void rdft (int n, int isgn, double* a, int* ip, double* w) { - void makewt (int nw, int* ip, double* w); - void makect (int nc, int* ip, double* c); - void cftfsub (int n, double* a, int* ip, int nw, double* w); - void cftbsub (int n, double* a, int* ip, int nw, double* w); - void rftfsub (int n, double* a, int nc, double* c); - void rftbsub (int n, double* a, int nc, double* c); int nw, nc; double xi; @@ -390,15 +392,16 @@ void rdft (int n, int isgn, double* a, int* ip, double* w) } } +void makewt (int nw, int* ip, double* w); +void makect (int nc, int* ip, double* c); +void cftfsub (int n, double* a, int* ip, int nw, double* w); +void cftbsub (int n, double* a, int* ip, int nw, double* w); +void rftfsub (int n, double* a, int nc, double* c); +void rftbsub (int n, double* a, int nc, double* c); +void dctsub (int n, double* a, int nc, double* c); + void ddct (int n, int isgn, double* a, int* ip, double* w) { - void makewt (int nw, int* ip, double* w); - void makect (int nc, int* ip, double* c); - void cftfsub (int n, double* a, int* ip, int nw, double* w); - void cftbsub (int n, double* a, int* ip, int nw, double* w); - void rftfsub (int n, double* a, int nc, double* c); - void rftbsub (int n, double* a, int nc, double* c); - void dctsub (int n, double* a, int nc, double* c); int j, nw, nc; double xr; @@ -457,15 +460,16 @@ void ddct (int n, int isgn, double* a, int* ip, double* w) } } +void makewt (int nw, int* ip, double* w); +void makect (int nc, int* ip, double* c); +void cftfsub (int n, double* a, int* ip, int nw, double* w); +void cftbsub (int n, double* a, int* ip, int nw, double* w); +void rftfsub (int n, double* a, int nc, double* c); +void rftbsub (int n, double* a, int nc, double* c); +void dstsub (int n, double* a, int nc, double* c); + void ddst (int n, int isgn, double* a, int* ip, double* w) { - void makewt (int nw, int* ip, double* w); - void makect (int nc, int* ip, double* c); - void cftfsub (int n, double* a, int* ip, int nw, double* w); - void cftbsub (int n, double* a, int* ip, int nw, double* w); - void rftfsub (int n, double* a, int nc, double* c); - void rftbsub (int n, double* a, int nc, double* c); - void dstsub (int n, double* a, int nc, double* c); int j, nw, nc; double xr; @@ -524,13 +528,14 @@ void ddst (int n, int isgn, double* a, int* ip, double* w) } } +void makewt (int nw, int* ip, double* w); +void makect (int nc, int* ip, double* c); +void cftfsub (int n, double* a, int* ip, int nw, double* w); +void rftfsub (int n, double* a, int nc, double* c); +void dctsub (int n, double* a, int nc, double* c); + void dfct (int n, double* a, double* t, int* ip, double* w) { - void makewt (int nw, int* ip, double* w); - void makect (int nc, int* ip, double* c); - void cftfsub (int n, double* a, int* ip, int nw, double* w); - void rftfsub (int n, double* a, int nc, double* c); - void dctsub (int n, double* a, int nc, double* c); int j, k, l, m, mh, nw, nc; double xr, xi, yr, yi; @@ -632,13 +637,14 @@ void dfct (int n, double* a, double* t, int* ip, double* w) } } +void makewt (int nw, int* ip, double* w); +void makect (int nc, int* ip, double* c); +void cftfsub (int n, double* a, int* ip, int nw, double* w); +void rftfsub (int n, double* a, int nc, double* c); +void dstsub (int n, double* a, int nc, double* c); + void dfst (int n, double* a, double* t, int* ip, double* w) { - void makewt (int nw, int* ip, double* w); - void makect (int nc, int* ip, double* c); - void cftfsub (int n, double* a, int* ip, int nw, double* w); - void rftfsub (int n, double* a, int nc, double* c); - void dstsub (int n, double* a, int nc, double* c); int j, k, l, m, mh, nw, nc; double xr, xi, yr, yi; @@ -731,9 +737,10 @@ void dfst (int n, double* a, double* t, int* ip, double* w) /* -------- initializing routines -------- */ +void makeiptd (int nw, int* ip); + void makewt (int nw, int* ip, double* w) { - void makeiptd (int nw, int* ip); int j, nwh, nw0, nw1; double delta, wn4r, wk1r, wk1i, wk3r, wk3i; @@ -897,23 +904,24 @@ void makect (int nc, int* ip, double* c) } #endif /* USE_CDFT_WINTHREADS */ -void cftfsub (int n, double* a, int* ip, int nw, double* w) -{ - void bitrv2 (int n, int* ip, double* a); - void bitrv216 (double* a); - void bitrv208 (double* a); - void cftf1st (int n, double* a, double* w); - void cftrec4 (int n, double* a, int nw, double* w); - void cftleaf (int n, int isplt, double* a, int nw, double* w); - void cftfx41 (int n, double* a, int nw, double* w); - void cftf161 (double* a, double* w); - void cftf081 (double* a, double* w); - void cftf040 (double* a); - void cftx020 (double* a); +void bitrv2 (int n, int* ip, double* a); +void bitrv216 (double* a); +void bitrv208 (double* a); +void cftf1st (int n, double* a, double* w); +void cftrec4 (int n, double* a, int nw, double* w); +void cftleaf (int n, int isplt, double* a, int nw, double* w); +void cftfx41 (int n, double* a, int nw, double* w); +void cftf161 (double* a, double* w); +void cftf081 (double* a, double* w); +void cftf040 (double* a); +void cftx020 (double* a); #ifdef USE_CDFT_THREADS - void cftrec4_th (int n, double* a, int nw, double* w); +void cftrec4_th (int n, double* a, int nw, double* w); #endif /* USE_CDFT_THREADS */ +void cftfsub (int n, double* a, int* ip, int nw, double* w) +{ + if (n > 8) { if (n > 32) @@ -961,23 +969,24 @@ void cftfsub (int n, double* a, int* ip, int nw, double* w) } } -void cftbsub (int n, double* a, int* ip, int nw, double* w) -{ - void bitrv2conj (int n, int* ip, double* a); - void bitrv216neg (double* a); - void bitrv208neg (double* a); - void cftb1st (int n, double* a, double* w); - void cftrec4 (int n, double* a, int nw, double* w); - void cftleaf (int n, int isplt, double* a, int nw, double* w); - void cftfx41 (int n, double* a, int nw, double* w); - void cftf161 (double* a, double* w); - void cftf081 (double* a, double* w); - void cftb040 (double* a); - void cftx020 (double* a); +void bitrv2conj (int n, int* ip, double* a); +void bitrv216neg (double* a); +void bitrv208neg (double* a); +void cftb1st (int n, double* a, double* w); +void cftrec4 (int n, double* a, int nw, double* w); +void cftleaf (int n, int isplt, double* a, int nw, double* w); +void cftfx41 (int n, double* a, int nw, double* w); +void cftf161 (double* a, double* w); +void cftf081 (double* a, double* w); +void cftb040 (double* a); +void cftx020 (double* a); #ifdef USE_CDFT_THREADS - void cftrec4_th (int n, double* a, int nw, double* w); +void cftrec4_th (int n, double* a, int nw, double* w); #endif /* USE_CDFT_THREADS */ +void cftbsub (int n, double* a, int* ip, int nw, double* w) +{ + if (n > 8) { if (n > 32) @@ -2346,10 +2355,11 @@ struct cdft_arg_st }; typedef struct cdft_arg_st cdft_arg_t; +void* cftrec1_th (void* p); +void* cftrec2_th (void* p); + void cftrec4_th (int n, double* a, int nw, double* w) { - void* cftrec1_th (void* p); - void* cftrec2_th (void* p); int i, idiv4, m, nthread; cdft_thread_t th[4]; cdft_arg_t ag[4]; @@ -2385,11 +2395,12 @@ void cftrec4_th (int n, double* a, int nw, double* w) } } +int cfttree (int n, int j, int k, double* a, int nw, double* w); +void cftleaf (int n, int isplt, double* a, int nw, double* w); +void cftmdl1 (int n, double* a, double* w); + void* cftrec1_th (void* p) { - int cfttree (int n, int j, int k, double* a, int nw, double* w); - void cftleaf (int n, int isplt, double* a, int nw, double* w); - void cftmdl1 (int n, double* a, double* w); int isplt, j, k, m, n, n0, nw; double *a, *w; @@ -2415,11 +2426,12 @@ void* cftrec1_th (void* p) return (void*) 0; } +int cfttree (int n, int j, int k, double* a, int nw, double* w); +void cftleaf (int n, int isplt, double* a, int nw, double* w); +void cftmdl2 (int n, double* a, double* w); + void* cftrec2_th (void* p) { - int cfttree (int n, int j, int k, double* a, int nw, double* w); - void cftleaf (int n, int isplt, double* a, int nw, double* w); - void cftmdl2 (int n, double* a, double* w); int isplt, j, k, m, n, n0, nw; double *a, *w; @@ -2448,11 +2460,12 @@ void* cftrec2_th (void* p) } #endif /* USE_CDFT_THREADS */ +int cfttree (int n, int j, int k, double* a, int nw, double* w); +void cftleaf (int n, int isplt, double* a, int nw, double* w); +void cftmdl1 (int n, double* a, double* w); + void cftrec4 (int n, double* a, int nw, double* w) { - int cfttree (int n, int j, int k, double* a, int nw, double* w); - void cftleaf (int n, int isplt, double* a, int nw, double* w); - void cftmdl1 (int n, double* a, double* w); int isplt, j, k, m; m = n; @@ -2471,10 +2484,11 @@ void cftrec4 (int n, double* a, int nw, double* w) } } +void cftmdl1 (int n, double* a, double* w); +void cftmdl2 (int n, double* a, double* w); + int cfttree (int n, int j, int k, double* a, int nw, double* w) { - void cftmdl1 (int n, double* a, double* w); - void cftmdl2 (int n, double* a, double* w); int i, isplt, m; if ((k & 3) != 0) @@ -2517,14 +2531,15 @@ int cfttree (int n, int j, int k, double* a, int nw, double* w) return isplt; } +void cftmdl1 (int n, double* a, double* w); +void cftmdl2 (int n, double* a, double* w); +void cftf161 (double* a, double* w); +void cftf162 (double* a, double* w); +void cftf081 (double* a, double* w); +void cftf082 (double* a, double* w); + void cftleaf (int n, int isplt, double* a, int nw, double* w) { - void cftmdl1 (int n, double* a, double* w); - void cftmdl2 (int n, double* a, double* w); - void cftf161 (double* a, double* w); - void cftf162 (double* a, double* w); - void cftf081 (double* a, double* w); - void cftf082 (double* a, double* w); if (n == 512) { @@ -2834,12 +2849,13 @@ void cftmdl2 (int n, double* a, double* w) a[j3 + 1] = y0i + y2i; } +void cftf161 (double* a, double* w); +void cftf162 (double* a, double* w); +void cftf081 (double* a, double* w); +void cftf082 (double* a, double* w); + void cftfx41 (int n, double* a, int nw, double* w) { - void cftf161 (double* a, double* w); - void cftf162 (double* a, double* w); - void cftf081 (double* a, double* w); - void cftf082 (double* a, double* w); if (n == 128) {