Full Code of maxim-zhao/SidWizPlus for AI

master d16d2affad02 cached
63 files
487.0 KB
122.9k tokens
305 symbols
1 requests
Download .txt
Showing preview only (510K chars total). Download the full file or copy to clipboard to get everything.
Repository: maxim-zhao/SidWizPlus
Branch: master
Commit: d16d2affad02
Files: 63
Total size: 487.0 KB

Directory structure:
gitextract_65uui726/

├── .editorconfig
├── .gitignore
├── .gitmodules
├── Benchmark.xlsx
├── Directory.Build.props
├── LICENSE
├── LibSidWiz/
│   ├── Channel.cs
│   ├── Extensions.cs
│   ├── LibSidWiz.csproj
│   ├── Mixer.cs
│   ├── MultiDumperWrapper.cs
│   ├── MyColorConverter.cs
│   ├── MyColorEditor.cs
│   ├── Outputs/
│   │   ├── FfmpegOutput.cs
│   │   ├── IGraphicsOutput.cs
│   │   ├── PreviewOutput.cs
│   │   ├── PreviewOutputForm.Designer.cs
│   │   ├── PreviewOutputForm.cs
│   │   └── PreviewOutputForm.resx
│   ├── ProcessWrapper.cs
│   ├── SampleBuffer.cs
│   ├── Triggers/
│   │   ├── AutoCorrelationTrigger.cs
│   │   ├── BiggestPositiveWaveAreaTrigger.cs
│   │   ├── BiggestWaveAreaTrigger.cs
│   │   ├── ITriggerAlgorithm.cs
│   │   ├── MiddleWidest.cs
│   │   ├── NullTrigger.cs
│   │   ├── PeakSpeedTrigger.cs
│   │   ├── RisingEdgeTrigger.cs
│   │   └── WidestWaveTrigger.cs
│   └── WaveformRenderer.cs
├── LibVgm/
│   ├── Gd3Tag.cs
│   ├── LibVgm.csproj
│   ├── OptionalGzipStream.cs
│   └── VgmFile.cs
├── README.md
├── SidWiz/
│   ├── ColorButton.Designer.cs
│   ├── ColorButton.cs
│   ├── HighDpiHelper.cs
│   ├── MultiDumperForm.Designer.cs
│   ├── MultiDumperForm.cs
│   ├── MultiDumperForm.resx
│   ├── Program.cs
│   ├── Properties/
│   │   ├── Resources.Designer.cs
│   │   ├── Resources.resx
│   │   └── app.manifest
│   ├── SidPlayForm.Designer.cs
│   ├── SidPlayForm.cs
│   ├── SidPlayForm.resx
│   ├── SidWizPlusGUI.Designer.cs
│   ├── SidWizPlusGUI.cs
│   ├── SidWizPlusGUI.csproj
│   ├── SidWizPlusGUI.resx
│   └── app.config
├── SidWiz.sln
├── SidWiz.sln.DotSettings
├── SidWizPlus/
│   ├── App.config
│   ├── BackgroundRenderer.cs
│   ├── ImageInfo.cs
│   ├── Program.cs
│   ├── SidWizPlus.csproj
│   └── TextInfo.cs
└── appveyor.yml

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
[*.cs]

# IDE0290: Use primary constructor
dotnet_diagnostic.IDE0290.severity = none


================================================
FILE: .gitignore
================================================
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore

# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates

# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/

# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/

# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*

# NUNIT
*.VisualState.xml
TestResult.xml

# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c

# Benchmark Results
BenchmarkDotNet.Artifacts/

# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json

*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc

# Chutzpah Test files
_Chutzpah*

# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb

# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap

# Visual Studio Trace Files
*.e2e

# TFS 2012 Local Workspace
$tf/

# Guidance Automation Toolkit
*.gpState

# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user

# JustCode is a .NET coding add-in
.JustCode

# TeamCity is a build add-in
_TeamCity*

# DotCover is a Code Coverage Tool
*.dotCover

# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json

# Visual Studio code coverage results
*.coverage
*.coveragexml

# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*

# MightyMoose
*.mm.*
AutoTest.Net/

# Web workbench (sass)
.sass-cache/

# Installshield output folder
[Ee]xpress/

# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html

# Click-Once directory
publish/

# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj

# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/

# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets

# Microsoft Azure Build Output
csx/
*.build.csdef

# Microsoft Azure Emulator
ecf/
rcf/

# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx

# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/

# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs

# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/

# RIA/Silverlight projects
Generated_Code/

# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm

# SQL Server files
*.mdf
*.ldf
*.ndf

# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings

# Microsoft Fakes
FakesAssemblies/

# GhostDoc plugin setting file
*.GhostDoc.xml

# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/

# Typescript v1 declaration files
typings/

# Visual Studio 6 build log
*.plg

# Visual Studio 6 workspace options file
*.opt

# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw

# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions

# Paket dependency manager
.paket/paket.exe
paket-files/

# FAKE - F# Make
.fake/

# JetBrains Rider
.idea/
*.sln.iml

# CodeRush
.cr/

# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc

# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config

# Tabs Studio
*.tss

# Telerik's JustMock configuration file
*.jmconfig

# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs

# OpenCover UI analysis results
OpenCover/

/SizWizPlus/Resources/ClientSecret.json


================================================
FILE: .gitmodules
================================================
[submodule "wiki"]
	path = wiki
	url = https://github.com/maxim-zhao/SidWizPlus.wiki.git
	branch = master


================================================
FILE: Directory.Build.props
================================================
<Project>
    <PropertyGroup>
        <LangVersion>latest</LangVersion>
        <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    </PropertyGroup>
</Project>

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2018 Maxim

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: LibSidWiz/Channel.cs
================================================
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Drawing.Design;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using LibSidWiz.Triggers;
using NAudio.Wave;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace LibSidWiz
{
    /// <summary>
    /// Wraps a single "voice", and also deals with loading the data into memory
    /// </summary>
    [SuppressMessage("ReSharper", "UnusedMember.Global")]
    public class Channel: IDisposable
    {
        private readonly bool _autoReloadOnSettingChanged;
        private SampleBuffer _samples;
        private SampleBuffer _samplesForTrigger;
        private string _filename;
        private string _externalTriggerFilename;
        private ITriggerAlgorithm _algorithm;
        private int _triggerLookaheadFrames; // Default to current frame only
        private int _triggerLookaheadOnFailureFrames = 2; // Default to 2 frames ahead
        private Color _lineColor = Color.White;
        private string _label = "";
        private float _lineWidth = 3;
        private float _scale = 1.0f;
        private int _viewWidthInSamples = 1500;
        private Color _fillColor = Color.Transparent;
        private float _zeroLineWidth;
        private Color _zeroLineColor = Color.Transparent;
        private Font _labelFont;
        private Color _labelColor = Color.Transparent;
        private Color _borderColor = Color.Transparent;
        private float _borderWidth;
        private ContentAlignment _labelAlignment = ContentAlignment.TopLeft;
        private Padding _labelMargins = new(0, 0, 0, 0);
        private bool _invertedTrigger;
        private bool _borderEdges = true;
        private Color _backgroundColor = Color.Transparent;
        private bool _clip;
        private Sides _side = Sides.Mix;
        private bool _smoothLines = true;
        private bool _filter;
        private bool _renderIfSilent;
        private double _fillBase;

        public Channel(bool autoReloadOnSettingChanged)
        {
            _autoReloadOnSettingChanged = autoReloadOnSettingChanged;
        }

        public enum Sides
        {
            Left,
            Right,
            Mix
        }

        public event Action<Channel, bool> Changed;

        public Task<bool> LoadDataAsync(CancellationToken token = new())
        {
            return Task.Factory.StartNew(() =>
            {
                try
                {
                    ErrorMessage = "";

                    if (string.IsNullOrEmpty(Filename))
                    {
                        _samples = null;
                        SampleCount = 0;
                        Max = 0;
                        Length = TimeSpan.Zero;
                        Loading = false;
                        IsEmpty = true;
                        return false;
                    }

                    IsEmpty = false;
                    Loading = true;

                    Console.WriteLine($"- Reading {Filename}");
                    _samples = new SampleBuffer(Filename, Side, HighPassFilter);
                    SampleRate = _samples.SampleRate;
                    Length = _samples.Length;

                    token.ThrowIfCancellationRequested();

                    _samples.Analyze();

                    SampleCount = _samples.Count;

                    token.ThrowIfCancellationRequested();

                    Max = Math.Max(Math.Abs(_samples.Max), Math.Abs(_samples.Min));

                    // This is a bit arbitrary. Nuked OPLL emits a DC offset of about 0.0008..0.0018 for an unused channel,
                    // and a fade out (or low pass filter) will pull that down to 0.
                    Console.WriteLine($"- Sample range for {Filename} is {_samples.Min}..{_samples.Max} = range {Math.Abs(_samples.Max - _samples.Min)}");
                    IsSilent = Math.Abs(_samples.Max - _samples.Min) < SilenceThreshold;

                    // Point at the same SampleBuffer
                    _samplesForTrigger = string.IsNullOrEmpty(ExternalTriggerFilename) 
                        ? _samples 
                        : new SampleBuffer(ExternalTriggerFilename, Side, HighPassFilter);

                    Loading = false;
                    return true;
                }
                catch (TaskCanceledException)
                {
                    // Blank out if cancelled
                    Max = 0;
                    SampleRate = 0;
                    Length = TimeSpan.Zero;
                    if (_samplesForTrigger != _samples)
                    {
                        _samplesForTrigger?.Dispose();
                    }
                    _samplesForTrigger = null;
                    _samples?.Dispose();
                    _samples = null;
                    Loading = false;
                    return false;
                }
                catch (Exception ex)
                {
                    ErrorMessage = ex.ToString();
                    Max = 0;
                    SampleRate = 0;
                    Length = TimeSpan.Zero;
                    if (_samplesForTrigger != _samples)
                    {
                        _samplesForTrigger?.Dispose();
                    }
                    _samplesForTrigger = null;
                    _samples?.Dispose();
                    _samples = null;
                    Loading = false;
                    return false;
                }
                finally
                {
                    Changed?.Invoke(this, false);
                }
            }, token);
        }

        [Category("Data")]
        [Description("The full text of any error message when loading the file")]
        [JsonIgnore]
        public string ErrorMessage { get; private set; }

        [Category("Data")]
        [Editor(typeof(FileNameEditor), typeof(UITypeEditor))]
        [Description("The filename to be rendered")]
        public string Filename
        {
            get => _filename;
            set
            {
                bool needReload = value != _filename;
                _filename = value;
                Changed?.Invoke(this, needReload);
                if (_filename != "" && string.IsNullOrEmpty(_label))
                {
                    Label = GuessNameFromMultidumperFilename(_filename);
                }
            }
        }

        [Category("Triggering")]
        [Editor(typeof(FileNameEditor), typeof(UITypeEditor))]
        [Description("The filename to use for oscilloscope triggering. Leave blank to use the channel's sound data.")]
        public string ExternalTriggerFilename
        {
            get => _externalTriggerFilename;
            set
            {
                bool needReload = value != _externalTriggerFilename;
                _externalTriggerFilename = value;
                // Change algorithm to RisingEdgeTrigger when using an external trigger
                _algorithm = new RisingEdgeTrigger();
                Changed?.Invoke(this, needReload);
            }
        }

        [Category("Data")]
        [Description("The channel to use from the file (if stereo)")]
        public Sides Side
        {
            get => _side;
            set
            {
                bool needReload = value != _side;
                _side = value;
                Changed?.Invoke(this, needReload);
                if (_autoReloadOnSettingChanged)
                {
                    LoadDataAsync();
                }
            }
        }

        [Category("Data")]
        [Description("If enabled, high pass filtering will be used to remove DC offsets")]
        public bool HighPassFilter
        {
            get => _filter;
            set
            {
                bool needReload = value != _filter;
                _filter = value;
                Changed?.Invoke(this, needReload);
                if (_autoReloadOnSettingChanged)
                {
                    LoadDataAsync();
                }
            }
        }

        [Category("Data")]
        [Description("The amplitude range at which a channel is considered silent")]
        public float SilenceThreshold { get; set; } = 0.01f;

        [Category("Triggering")]
        [Description("The algorithm to use for rendering")]
        [TypeConverter(typeof(TriggerAlgorithmTypeConverter))]
        [JsonConverter(typeof(TriggerAlgorithmJsonConverter))]
        public ITriggerAlgorithm Algorithm
        {
            get => _algorithm;
            set
            {
                _algorithm = value;
                Changed?.Invoke(this, false);
            }
        }

        [Category("Triggering")]
        [Description("How many frames to allow the triggering algorithm to look ahead. Zero means only look within the current frame. Set to larger numbers to support sync to low frequencies, but too large numbers can cause erroneous matches.")]
        public int TriggerLookaheadFrames
        {
            get => _triggerLookaheadFrames;
            set
            {
                _triggerLookaheadFrames = value;
                Changed?.Invoke(this, false);
            }
        }

        [Category("Triggering")]
        [Description("How many frames to allow the triggering algorithm to look ahead, when nothing is found with the default lookahead.")]
        public int TriggerLookaheadOnFailureFrames
        {
            get => _triggerLookaheadOnFailureFrames;
            set
            {
                _triggerLookaheadOnFailureFrames = value;
                Changed?.Invoke(this, false);
            }
        }

        [Category("Appearance")]
        [Description("The line colour")]
        [Editor(typeof(MyColorEditor), typeof(UITypeEditor))]
        [TypeConverter(typeof(MyColorConverter))]
        public Color LineColor
        {
            get => _lineColor;
            set
            {
                _lineColor = value;
                Changed?.Invoke(this, false);
            }
        }

        [Category("Appearance")]
        [Description("The line width, in pixels. Fractional values are supported.")]
        public float LineWidth
        {
            get => _lineWidth;
            set
            {
                _lineWidth = value;
                Changed?.Invoke(this, false);
            }
        }

        [Category("Appearance")]
        [Description("The fill colour. Set to transparent to have no fill.")]
        [Editor(typeof(MyColorEditor), typeof(UITypeEditor))]
        [TypeConverter(typeof(MyColorConverter))]
        public Color FillColor
        {
            get => _fillColor;
            set
            {
                _fillColor = value;
                Changed?.Invoke(this, false);
            }
        }

        [Category("Appearance")]
        [Description("The base of the fill. Set to 0 for the centre line, -1 to fill from the bottom and 1 for the top. Other values also work.")]
        public double FillBase
        {
            get => _fillBase;
            set
            {
                _fillBase = value;
                Changed?.Invoke(this, false);
            }
        }

        [Category("Appearance")]
        [Description("Whether to draw lines pixelated (false) or anti-aliased (true)")]
        public bool SmoothLines
        {
            get => _smoothLines;
            set
            {
                _smoothLines = value;
                Changed?.Invoke(this, false);
            }
        }

        [Category("Appearance")]
        [Description("The width of the zero line")]
        public float ZeroLineWidth
        {
            get => _zeroLineWidth;
            set
            {
                _zeroLineWidth = value;
                Changed?.Invoke(this, false);
            }
        }

        [Category("Appearance")]
        [Description("The color of the zero line")]
        [Editor(typeof(MyColorEditor), typeof(UITypeEditor))]
        [TypeConverter(typeof(MyColorConverter))]
        public Color ZeroLineColor
        {
            get => _zeroLineColor;
            set
            {
                _zeroLineColor = value;
                Changed?.Invoke(this, false);
            }
        }

        [Category("Appearance")]
        [Description("The color of the border")]
        [Editor(typeof(MyColorEditor), typeof(UITypeEditor))]
        [TypeConverter(typeof(MyColorConverter))]
        public Color BorderColor
        {
            get => _borderColor;
            set
            {
                _borderColor = value;
                Changed?.Invoke(this, false);
            }
        }

        [Category("Appearance")]
        [Description("The width of the border")]
        public float BorderWidth
        {
            get => _borderWidth;
            set
            {
                _borderWidth = value;
                Changed?.Invoke(this, false);
            }
        }

        [Category("Appearance")]
        [Description("Whether to draw the outer edges of any border boxes")]
        public bool BorderEdges
        {
            get => _borderEdges;
            set
            {
                _borderEdges = value;
                Changed?.Invoke(this, false);
            }
        }

        [Category("Appearance")]
        [Description("A background colour for the channel. This is layered above any background image, and can be transparent.")]
        [Editor(typeof(MyColorEditor), typeof(UITypeEditor))]
        [TypeConverter(typeof(MyColorConverter))]
        public Color BackgroundColor
        {
            get => _backgroundColor;
            set
            {
                _backgroundColor = value;
                Changed?.Invoke(this, false);
            }
        }

        [Category("Appearance")]
        [Description("The label for the channel")]
        [Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))]
        public string Label
        {
            get => _label;
            set
            {
                _label = value;
                Changed?.Invoke(this, false);
            }
        }

        [Category("Appearance")]
        [Description("The font for the channel label")]
        public Font LabelFont
        {
            get => _labelFont;
            set
            {
                _labelFont = value;
                Changed?.Invoke(this, false);
            }
        }

        [Category("Appearance")]
        [Description("The color for the channel label")]
        [Editor(typeof(MyColorEditor), typeof(UITypeEditor))]
        [TypeConverter(typeof(MyColorConverter))]
        public Color LabelColor
        {
            get => _labelColor;
            set
            {
                _labelColor = value;
                Changed?.Invoke(this, false);
            }
        }

        [Category("Appearance")]
        [Description("The alignment for the channel label")]
        public ContentAlignment LabelAlignment
        {
            get => _labelAlignment;
            set
            {
                _labelAlignment = value;
                Changed?.Invoke(this, false);
            }
        }

        [Category("Appearance")]
        [Description("The margins for the chanel label")]
        public Padding LabelMargins
        {
            get => _labelMargins;
            set
            {
                _labelMargins = value;
                Changed?.Invoke(this, false);
            }
        }

        [Category("Adjustment")]
        [Description("Vertical scaling. This may be set by the auto-scaler.")]
        public float Scale
        {
            get => _scale;
            set
            {
                _scale = value;
                Changed?.Invoke(this, false);
            }
        }

        [Category("Adjustment")]
        [Description("Whether to constrain the waveform to its screen area when scaled past 100%")]
        public bool Clip
        {
            get => _clip;
            set
            {
                _clip = value;
                Changed?.Invoke(this, false);
            }
        }

        [Category("Adjustment")]
        [Description("View width, in ms")]
        [JsonIgnore]
        public float ViewWidthInMilliseconds
        {
            get => SampleRate == 0 ? 0 : (float)_viewWidthInSamples * 1000 / SampleRate;
            set
            {
                _viewWidthInSamples = (int) (value / 1000 * SampleRate);
                Changed?.Invoke(this, false);
            }
        }

        [Category("Adjustment")]
        [Description("View width, in samples")]
        public int ViewWidthInSamples
        {
            get => _viewWidthInSamples;
            set
            {
                _viewWidthInSamples = value;
                Changed?.Invoke(this, false);
            }
        }

        [Category("Triggering")]
        [Description("Set to true to trigger in the opposite direction")]
        // ReSharper disable once MemberCanBePrivate.Global
        public bool InvertedTrigger
        {
            get => _invertedTrigger;
            set
            {
                _invertedTrigger = value;
                Changed?.Invoke(this, false);
            }
        }

        [Category("Data")]
        [Description("Peak amplitude for the channel")]
        [JsonIgnore]
        public float Max { get; private set; }

        [Browsable(false)]
        [JsonIgnore]
        public long SampleCount { get; private set; }

        [Category("Data")]
        [Description("Duration of the channel")]
        [JsonIgnore]
        public TimeSpan Length { get; private set; }

        [Category("Data")]
        [Description("Sampling rate of the channel")]
        [JsonIgnore]
        public int SampleRate { get; private set; }

        [Category("Appearance")]
        [Description("Whether to render silent channels normally. If false, a warning message is shown instead.")]
        public bool RenderIfSilent
        {
            get => _renderIfSilent;
            set
            {
                _renderIfSilent = value;
                Changed?.Invoke(this, false);
            }
        }

        // ReSharper disable once CompareOfFloatsByEqualityOperator
        [Browsable(false)]
        [JsonIgnore]
        public bool IsSilent { get; private set; }

        [Browsable(false)]
        [JsonIgnore]
        public bool Loading { get; private set; } = true;

        [Browsable(false)]
        [JsonIgnore]
        public bool IsEmpty { get; private set; }

        [Browsable(false)]
        [JsonIgnore]
        internal Rectangle Bounds { get; set; }

        internal float GetSample(int sampleIndex, bool forTrigger = true)
        {
            var source = forTrigger ? _samplesForTrigger : _samples;
            return sampleIndex < 0 || sampleIndex >= source.Count ? 0 : source[sampleIndex] * Scale * (forTrigger && InvertedTrigger ? -1 : 1);
        }

        internal int GetTriggerPoint(int frameIndexSamples, int frameSamples, int previousTriggerPoint)
        {
            // Try at default settings
            var result = Algorithm.GetTriggerPoint(this, frameIndexSamples, frameIndexSamples + frameSamples * (TriggerLookaheadFrames + 1), previousTriggerPoint);

            if (result < frameIndexSamples)
            {
                // Try again
                result = Algorithm.GetTriggerPoint(this, frameIndexSamples, frameIndexSamples + frameSamples * (TriggerLookaheadOnFailureFrames + 1), previousTriggerPoint);
            }

            if (result < frameIndexSamples)
            {
                // Default on failure
                result = frameIndexSamples;
            }

            return result;
        }

        public static string GuessNameFromMultidumperFilename(string filename)
        {
            var namePart = Path.GetFileNameWithoutExtension(filename);
            try
            {
                if (namePart == null)
                {
                    return filename;
                }

                var index = namePart.IndexOf(" - YM2413 #", StringComparison.Ordinal);
                if (index > -1)
                {
                    index = int.Parse(namePart.Substring(index + 11));
                    if (index < 9)
                    {
                        return $"YM2413 Tone {index + 1}";
                    }

                    switch (index)
                    {
                        case 9: return "YM2413 Bass Drum";
                        case 10: return "YM2413 Snare Drum";
                        case 11: return "YM2413 Tom-Tom";
                        case 12: return "YM2413 Cymbal";
                        case 13: return "YM2413 Hi-Hat";
                    }
                }

                index = namePart.IndexOf(" - SEGA PSG #", StringComparison.Ordinal);
                if (index > -1)
                {
                    if (int.TryParse(namePart.Substring(index + 13), out index))
                    {
                        switch (index)
                        {
                            case 0:
                            case 1:
                            case 2:
                                return $"Sega PSG Square {index + 1}";
                            case 3:
                                return "Sega PSG Noise";
                        }
                    }
                }

                index = namePart.IndexOf(" - SN76489 #", StringComparison.Ordinal);
                if (index > -1)
                {
                    if (int.TryParse(namePart.Substring(index + 12), out index))
                    {
                        switch (index)
                        {
                            case 0:
                            case 1:
                            case 2:
                                return $"SN76489 Square {index + 1}";
                            case 3:
                                return "SN76489 Noise";
                        }
                    }
                }

                // Guess it's the bit after the last " - "
                index = namePart.LastIndexOf(" - ", StringComparison.Ordinal);
                if (index > -1)
                {
                    return namePart.Substring(index + 3);
                }
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine($"Error guessing channel name for {filename}: {ex}");
            }

            // Default to just the filename
            return namePart;
        }

        /// <summary>
        /// This allows us to use a property grid to select a trigger algorithm
        /// </summary>
        // ReSharper disable once MemberCanBePrivate.Global
        public class TriggerAlgorithmTypeConverter: StringConverter
        {
            public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
            {
                return true;
            }

            public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
            {
                return true;
            }

            public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
            {
                return new StandardValuesCollection(
                    Assembly.GetExecutingAssembly()
                        .GetTypes()
                        .Where(t => typeof(ITriggerAlgorithm).IsAssignableFrom(t) && t != typeof(ITriggerAlgorithm))
                        .Select(t => t.Name)
                        .ToList());
            }

            public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
            {
                return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
            }

            public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
            {
                if (value is string)
                {
                    var type = Assembly.GetExecutingAssembly()
                        .GetTypes()
                        .FirstOrDefault(t => typeof(ITriggerAlgorithm).IsAssignableFrom(t) && t.Name.ToLowerInvariant().Equals(value.ToString().ToLowerInvariant()));
                    if (type != null)
                    {
                        return Activator.CreateInstance(type) as ITriggerAlgorithm;
                    }
                }

                return base.ConvertFrom(context, culture, value);
            }
        }

        // ReSharper disable once MemberCanBePrivate.Global
        public class TriggerAlgorithmJsonConverter: JsonConverter<ITriggerAlgorithm>
        {
            public override void WriteJson(JsonWriter writer, ITriggerAlgorithm value, JsonSerializer serializer)
            {
                writer.WriteValue(value.GetType().Name);
            }

            public override ITriggerAlgorithm ReadJson(JsonReader reader, Type objectType, ITriggerAlgorithm existingValue, bool hasExistingValue, JsonSerializer serializer)
            {
                var type = Assembly.GetExecutingAssembly()
                    .GetTypes()
                    .FirstOrDefault(t => 
                        typeof(ITriggerAlgorithm).IsAssignableFrom(t) && 
                        t.Name.ToLowerInvariant().Equals(reader.Value?.ToString().ToLowerInvariant()));
                if (type != null)
                {
                    return Activator.CreateInstance(type) as ITriggerAlgorithm;
                }

                return existingValue;
            }
        }

        public void Dispose()
        {
            _samples?.Dispose();
            if (_samplesForTrigger != _samples)
            {
                _samplesForTrigger.Dispose();
            }
            _labelFont?.Dispose();
        }

        public string ToJson()
        {
            return JsonConvert.SerializeObject(this, new JsonSerializerSettings
            {
                Formatting = Formatting.Indented,
            });
        }

        public void FromJson(string json, bool preserveSource)
        {
            if (preserveSource)
            {
                JsonConvert.PopulateObject(json, this, new JsonSerializerSettings
                {
                    ContractResolver = new PreservingContractResolver()
                });
            }
            else
            {
                JsonConvert.PopulateObject(json, this);
            }
        }

        private class PreservingContractResolver : DefaultContractResolver
        {
            protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
            {
                var property = base.CreateProperty(member, memberSerialization);
                if (property.PropertyName == nameof(Filename) ||
                    property.PropertyName == nameof(Label) ||
                    property.PropertyName == nameof(ExternalTriggerFilename))
                {
                    property.Ignored = true;
                }
                return property;
            }
        }

        public bool IsMono()
        {
            if (Side == Sides.Left || Side == Sides.Right)
            {
                return true;
            }

            using var reader = new WaveFileReader(_filename);
            var sp = reader.ToSampleProvider().ToStereo();
            if (sp.WaveFormat.Channels == 1)
            {
                return true;
            }

            int bufferSize = sp.WaveFormat.SampleRate * 10;
            var buffer = new float[bufferSize];
            sp.Read(buffer, 0, bufferSize);
            for (int i = 0; i < bufferSize; i += 2)
            {
                // ReSharper disable once CompareOfFloatsByEqualityOperator
                if (buffer[i] != buffer[i + 1])
                {
                    return false;
                }
            }

            return true;
        }
    }
}

================================================
FILE: LibSidWiz/Extensions.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace LibSidWiz
{
    public static class Extensions
    {
        public static IOrderedEnumerable<T> OrderByAlphaNumeric<T>(this IEnumerable<T> source, Func<T, string> selector)
        {
            // Materialise the collection if necessary
            var list = source.ToList();
            // Find the longest sequence of digits
            var max = list
                .SelectMany(i => Regex
                    .Matches(selector(i), @"\d+")
                    .Cast<Match>()
                    .Select(m => (int?)m.Value.Length))
                .Max() ?? 0;

            // Pad all number sequences to that length, then order by this padded string
            return list.OrderBy(i => Regex.Replace(selector(i), @"\d+", m => m.Value.PadLeft(max, '0')));
        }

    }
}


================================================
FILE: LibSidWiz/LibSidWiz.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net48</TargetFramework>
    <UseWPF>true</UseWPF>
    <UseWindowsForms>true</UseWindowsForms>
    <LangVersion>latest</LangVersion>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="NReplayGain">
      <HintPath>..\NReplayGain.dll</HintPath>
    </Reference>
    <Reference Include="System.Design" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="Cyotek.Windows.Forms.ColorPicker" Version="1.7.2" />
    <PackageReference Include="Microsoft-WindowsAPICodePack-Shell" Version="1.1.5" />
    <PackageReference Include="NAudio" Version="2.2.1" />
    <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
    <PackageReference Include="SkiaSharp" Version="2.88.8" />
    <PackageReference Include="SkiaSharp.Views.WindowsForms" Version="2.88.8" />
  </ItemGroup>
</Project>

================================================
FILE: LibSidWiz/Mixer.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using NAudio.Wave;
using NAudio.Wave.SampleProviders;
using NReplayGain;

namespace LibSidWiz
{
    /// <summary>
    /// Deals with mixing audio to a "master file"
    /// </summary>
    public static class Mixer
    {
        public static void MixToFile(IList<Channel> channels, string filename, bool applyReplayGain)
        {
            Console.WriteLine("Mixing per-channel data...");

            // We make new readers...
            var readers = new List<WaveFileReader>();
            try
            {
                readers.AddRange(channels
                    .Where(c => !string.IsNullOrEmpty(c.Filename))
                    .Select(c => new WaveFileReader(c.Filename)));

                if (applyReplayGain)
                {
                    Console.WriteLine("Computing ReplayGain...");
                    // We read it in a second at a time, to calculate Replay Gains
                    var mixer = new MixingSampleProvider(readers.Select(x => x.ToSampleProvider().ToStereo()));
                    const int sampleRate = 44100;
                    var resampler = new WdlResamplingSampleProvider(mixer, sampleRate);
                    // We use a 1s buffer...
                    var buffer = new float[sampleRate * 2]; // *2 for stereo
                    var replayGain = new TrackGain(sampleRate);
                    for (;;)
                    {
                        int numRead = resampler.Read(buffer, 0, buffer.Length);
                        if (numRead == 0)
                        {
                            break;
                        }

                        // And analyze
                        replayGain.AnalyzeSamples(buffer, numRead);
                    }

                    // The +3 is to make it at "YouTube loudness", which is a lot louder than ReplayGain defaults to.
                    // TODO make this configurable?
                    var gain = replayGain.GetGain() + 3;

                    Console.WriteLine($"Applying ReplayGain ({gain:N} dB) and saving to {filename}");

                    // Reset the readers
                    foreach (var reader in readers)
                    {
                        reader.Position = 0;
                    }

                    // We make a new mixer just in case resetting the previous one is problematic...
                    mixer = new MixingSampleProvider(readers.Select(x => x.ToSampleProvider().ToStereo()));
                    var amplifier = new VolumeSampleProvider(mixer) {Volume = (float) Math.Pow(10, gain / 20)};
                    WaveFileWriter.CreateWaveFile(filename, amplifier.ToWaveProvider());
                }
                else
                {
                    var mixer = new MixingSampleProvider(readers.Select(x => x.ToSampleProvider().ToStereo()));
                    WaveFileWriter.CreateWaveFile(filename, mixer.ToWaveProvider());
                }
            }
            finally
            {
                foreach (var reader in readers)
                {
                    reader.Dispose();
                }
            }
        }
    }
}


================================================
FILE: LibSidWiz/MultiDumperWrapper.cs
================================================
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace LibSidWiz
{
    public class MultiDumperWrapper: IDisposable
    {
        private readonly string _multiDumperPath;
        private readonly int _samplingRate;
        private readonly int _loopCount;
        private readonly int _fadeMs;
        private readonly int _gapMs;
        private readonly string _extraOptions;
        private ProcessWrapper _processWrapper;
        private readonly HashSet<string> _allowedParameters;

        public MultiDumperWrapper(string multiDumperPath, int samplingRate, int loopCount, int fadeMs, int gapMs, string extraOptions)
        {
            _multiDumperPath = multiDumperPath;
            _samplingRate = samplingRate;
            _loopCount = loopCount;
            _fadeMs = fadeMs;
            _gapMs = gapMs;
            _extraOptions = extraOptions;

            // We parse the usage info first to check for allowed parameters, in the form --name or --name=
            var helpText = GetOutputText("", true);
            _allowedParameters =
            [
                ..Regex.Matches(helpText, "--[^= ]+")
                    .Cast<Match>()
                    .Select(x => x.Value)
            ];
        }

        public class Song
        {
            // ReSharper disable UnusedAutoPropertyAccessor.Global
            public string Name { get; set; }
            public string Author { get; set; }
            public string Comment { get; set; }
            public string Copyright { get; set; }
            public string Dumper { get; set; }
            public string Game { get; set; }
            public string System { get; set; }
            public List<string> Channels { get; set; }
            public int Index { get; set; }
            public string Filename { get; set; }
            public TimeSpan IntroLength { get; set; }
            public TimeSpan LoopLength { get; set; }
            // ReSharper restore UnusedAutoPropertyAccessor.Global
            public int LoopCount { get; set; }
            public TimeSpan ForceLength { get; set; } = TimeSpan.Zero;

            public override string ToString()
            {
                var length = GetLength();

                var sb = new StringBuilder();
                sb.Append($"#{Index}: ")
                    .Append(string.IsNullOrWhiteSpace(Game) ? "Unknown game" : Game)
                    .Append(" - ")
                    .Append(string.IsNullOrWhiteSpace(Name) ? "Unknown title" : Name)
                    .Append(" - ")
                    .Append(string.IsNullOrWhiteSpace(Author) ? "Unknown author" : Author)
                    .Append(string.IsNullOrWhiteSpace(Comment) ? "" : $" ({Comment})")
                    .Append(length <= TimeSpan.Zero ? " (Unknown length)" : $" ({length})");
                return sb.ToString();
            }

            public TimeSpan GetLength()
            {
                if (ForceLength > TimeSpan.Zero)
                {
                    return ForceLength;
                }

                var length = TimeSpan.Zero;
                if (IntroLength > TimeSpan.Zero)
                {
                    length += IntroLength;
                }

                if (LoopLength > TimeSpan.Zero)
                {
                    length += TimeSpan.FromTicks(LoopLength.Ticks * LoopCount);
                }

                return length;
            }
        }

        public IEnumerable<Song> GetSongs(string filename)
        {
            filename = Path.GetFullPath(filename);

            if (!File.Exists(filename))
            {
                throw new FileNotFoundException("Cannot find VGM file", filename);
            }

            // Don't check the --json parameter as it may not have mentioned it for an old build
            var json = GetOutputText($"\"{filename}\" --json", false);

            if (string.IsNullOrEmpty(json))
            {
                throw new Exception("Failed to get song data from MultiDumper");
            }

            // Extract metadata
            // Example result:
            // {
            //  "channels":[
            //      "SEGA PSG #0","SEGA PSG #1","SEGA PSG #2","SEGA PSG #3",
            //      "YM2413 #0","YM2413 #1","YM2413 #2","YM2413 #3","YM2413 #4","YM2413 #5","YM2413 #6",
            //      "YM2413 #7","YM2413 #8","YM2413 #9","YM2413 #10","YM2413 #11","YM2413 #12","YM2413 #13"],
            //  "containerinfo":
            //  {
            //      "copyright":"1988/08/14",
            //      "dumper":"sherpa",
            //      "game":"Golvellius - Valley of Doom",
            //      "system":"Sega Master System"
            //  },
            //  "songs":[
            //      {
            //          "author":"Masatomo Miyamoto, Takeshi Santo, Shin-kun, Pazu",
            //          "comment":"",
            //          "name":"Title Screen",
            //          "length":"12345"
            //      }],
            //  "subsongCount":1
            // }
            // or:
            // { "error": "some error message" }
            dynamic metadata = JsonConvert.DeserializeObject(json) 
                ?? throw new Exception("Failed to parse song metadata");

            if (metadata["error"] != null)
            {
                throw new Exception($"Failed to parse song metadata: {metadata.error}");
            }

            var channels = metadata.channels.ToObject<List<string>>();
            var songs = (JArray)metadata.songs;
            var i = 0;

            return songs.Cast<dynamic>().Select(s => new Song
            {
                Filename = filename,
                Index = i++,
                Name = Clean(s.name),
                Author = Clean(s.author),
                Channels = channels,
                Comment = Clean(s.comment),
                Copyright = Clean(metadata.containerinfo.copyright),
                Dumper = Clean(metadata.containerinfo.dumper),
                Game = Clean(metadata.containerinfo.game),
                System = Clean(metadata.containerinfo.system),
                IntroLength = TimeSpan.FromMilliseconds((int)(s.intro_length ?? 0)),
                LoopLength = TimeSpan.FromMilliseconds((int)(s.loop_length ?? 0)),
                LoopCount = _loopCount
            });

            // This helps us reject any junk strings MultiDumper gives us for empty tags
            static string Clean(string s) => string.IsNullOrEmpty(s) || s.Any(char.IsControl) ? string.Empty : s;
        }

        private string GetOutputText(string args, bool includeStdErr)
        {
            using var p = new ProcessWrapper(
                _multiDumperPath,
                args,
                includeStdErr);
            string text = string.Join("", p.Lines());
            // Try to decode any UTF-8 in there
            try
            {
                text = Encoding.UTF8.GetString(Encoding.Default.GetBytes(text));
            }
            catch (Exception)
            {
                // Ignore it, use unfixed string
            }
            return text;
        }

        public IEnumerable<string> Dump(Song song, Action<double> onProgress)
        {
            var args = new StringBuilder($"\"{song.Filename}\" {song.Index}");
            AddArgIfSupported(args, "sampling_rate", _samplingRate);
            AddArgIfSupported(args, "fade_length", _fadeMs);
            AddArgIfSupported(args, "loop_count", _loopCount);
            AddArgIfSupported(args, "gap_length", _gapMs);
            if (song.ForceLength > TimeSpan.Zero)
            {
                AddArgIfSupported(args, "play_length", (long)song.ForceLength.TotalMilliseconds);
            }

            if (!string.IsNullOrEmpty(_extraOptions))
            {
                args.Append($" {_extraOptions}");
            }

            _processWrapper = new ProcessWrapper(
                _multiDumperPath,
                args.ToString(),
                showConsole:true);
            var progressParts = Enumerable.Repeat(0.0, song.Channels.Count).ToList();
            var r = new Regex(@"(?<channel>\d+)\|(?<position>\d+)\|(?<total>\d+)");
            var stopwatch = Stopwatch.StartNew();
            foreach (var match in _processWrapper.Lines().Select(l => r.Match(l)).Where(m => m.Success))
            {
                var channel = Convert.ToInt32(match.Groups["channel"].Value);
                if (channel < 0 || channel > song.Channels.Count)
                {
                    continue;
                }

                var position = Convert.ToDouble(match.Groups["position"].Value);
                var total = Convert.ToDouble(match.Groups["total"].Value);
                progressParts[channel] = position / total;
                if (stopwatch.Elapsed.TotalMilliseconds > 100)
                {
                    // Update the progress every 100ms
                    onProgress?.Invoke(progressParts.Average());
                    stopwatch.Restart();
                }
            }
            _processWrapper.Dispose();
            _processWrapper = null;

            onProgress?.Invoke(1.0);

            var baseName = Path.Combine(
                Path.GetDirectoryName(song.Filename) ?? "",
                Path.GetFileNameWithoutExtension(song.Filename));
            return song.Channels.Select(channel => $"{baseName} - {channel}.wav");
        }

        private void AddArgIfSupported(StringBuilder args, string name, object value)
        {
            if (_allowedParameters.Contains($"--{name}"))
            {
                args.Append($" --{name}={value}");
            }
            else
            {
                Console.Error.WriteLine($"Arg not supported: {name}");
            }
        }

        public void Dispose()
        {
            _processWrapper?.Dispose();
        }
    }
}

================================================
FILE: LibSidWiz/MyColorConverter.cs
================================================
using System.ComponentModel;
using System.Drawing;

namespace LibSidWiz;

/// <summary>
/// This overrides ColorConverter which interferes with the colour editor.
/// </summary>
public class MyColorConverter : ColorConverter
{
    public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
    {
        return false;
    }
}

================================================
FILE: LibSidWiz/MyColorEditor.cs
================================================
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Windows.Forms;
using System.Windows.Forms.Design;

namespace LibSidWiz;

/// <summary>
/// This allows us to implement a custom colour picker
/// </summary>
public class MyColorEditor : UITypeEditor
{
    public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
    {
        return UITypeEditorEditStyle.Modal;
    }

    public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
    {
        if (value is not Color c || provider == null)
        {
            return value;
        }

        var svc = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));

        if (svc == null)
        {
            return c;
        }

        using var form = new Cyotek.Windows.Forms.ColorPickerDialog();
        form.Color = c;
        form.ShowAlphaChannel = true;
        return svc.ShowDialog(form) == DialogResult.OK ? form.Color : c;
    }

    public override bool GetPaintValueSupported(ITypeDescriptorContext context)
    {
        return true;
    }

    public override void PaintValue(PaintValueEventArgs e)
    {
        // This is just for the little rectangle on the left
        // TODO: indicate transparency?
        var color = (Color)e.Value;
        if (color.A < 255)
        {
            // Draw a checkerboard of 4x4 using the system colours
            e.Graphics.FillRectangle(SystemBrushes.Window, e.Bounds);
            for (var x = 0; x < e.Bounds.Width; x += 4)
            for (var y = 0; y < e.Bounds.Height; y += 4)
                if (((x/4 ^ y/4) & 1) == 0)
                {
                    e.Graphics.FillRectangle(SystemBrushes.WindowText, x, y, 4, 4);
                }
        }
        using (var brush = new SolidBrush(color))
        {
            e.Graphics.FillRectangle(brush, e.Bounds);
        }

        e.Graphics.DrawRectangle(SystemPens.WindowText, e.Bounds);
    }
}

================================================
FILE: LibSidWiz/Outputs/FfmpegOutput.cs
================================================
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using SkiaSharp;

namespace LibSidWiz.Outputs
{
    public class FfmpegOutput : IGraphicsOutput
    {
        private readonly bool _throwStandardError;
        private readonly Process _process;
        private readonly BinaryWriter _writer;

        public FfmpegOutput(string pathToExe, string filename, int width, int height, int fps, string extraArgs, string masterAudioFilename, string videoCodec, string audioCodec, bool throwStandardError)
        {
            _throwStandardError = throwStandardError;
            // Build the FFMPEG commandline
            var arguments = "-y -hide_banner"; // Overwrite, don't show banner at startup

            // Audio input
            if (File.Exists(masterAudioFilename))
            {
                arguments += $" -i \"{masterAudioFilename}\"";
            }

            // Video input
            arguments += $" -f rawvideo -pixel_format bgr0 -video_size {width}x{height} -framerate {fps} -i pipe:";
            
            // Audio output
            arguments += $" -codec:a {audioCodec}";

            // Video output
            arguments += $" -codec:v {videoCodec} -movflags +faststart";

            // Extra args
            arguments += $" {extraArgs} \"{filename}\"";

            Console.WriteLine($"Starting FFMPEG: {pathToExe} {arguments}");

            // We don't want a BOM to be injected if the system code page is set to UTF-8.
            // This fails sometimes, so we swallow the error...
            try
            {
                Console.InputEncoding = Encoding.ASCII;
            }
            catch (Exception e)
            {
                Console.WriteLine($"Failed to change console encoding to ASCII. You may get video corruption. Exception said: {e.Message}");
            }

            // Start it up
            _process = Process.Start(
                new ProcessStartInfo
                {
                    FileName = pathToExe,
                    Arguments = arguments,
                    UseShellExecute = false,
                    RedirectStandardInput = true,
                    RedirectStandardError = throwStandardError,
                    RedirectStandardOutput = false,
                    CreateNoWindow = throwStandardError // makes it inline in console mode
                }
            );

            if (_process == null)
            {
                throw new Exception($"Couldn't start FFMPEG with commandline {pathToExe} {arguments}");
            }

            _writer = new BinaryWriter(_process.StandardInput.BaseStream);
        }

        public void Write(SKImage _, byte[] data, double __, TimeSpan ___)
        {
            try
            {
                _writer.Write(data);
            }
            catch (Exception e)
            {
                if (_throwStandardError)
                {
                    if (_process.HasExited)
                    {
                        throw new Exception(
                            $"""
                             FFMPEG has exited unexpectedly.
                             Exit code was {_process.ExitCode}.
                             Standard error was:
                             {_process.StandardError.ReadToEnd()}
                             """, e);
                    }

                    throw new Exception(
                        $"""
                         Cannot write to FFMPEG.
                         Standard error was:
                         {_process.StandardError.ReadToEnd()}
                         """, e);
                }

                throw;
            }
        }

        public void Dispose()
        {
            // This triggers a shutdown
            _process?.StandardInput.BaseStream.Close();
            if (_throwStandardError)
            {
                _process?.StandardError.Close();
            }
            // And we wait for it to finish...
            _process?.WaitForExit();
            _process?.Dispose();
            _writer?.Dispose();
        }
    }
}

================================================
FILE: LibSidWiz/Outputs/IGraphicsOutput.cs
================================================
using System;
using SkiaSharp;

namespace LibSidWiz.Outputs
{
    public interface IGraphicsOutput: IDisposable
    {
        void Write(SKImage image, byte[] data, double fractionComplete, TimeSpan length);
    }
}


================================================
FILE: LibSidWiz/Outputs/PreviewOutput.cs
================================================
using System;
using System.Diagnostics;
using System.Windows.Forms;
using Microsoft.WindowsAPICodePack.Taskbar;
using SkiaSharp;
using SkiaSharp.Views.Desktop;

namespace LibSidWiz.Outputs
{
    public class PreviewOutput : IGraphicsOutput
    {
        private readonly int _frameSkip;
        private readonly bool _pumpMessageQueue;
        private readonly PreviewOutputForm _form;
        private int _frameIndex;
        private readonly Stopwatch _stopwatch;
        private TimeSpan _lastFpsUpdateTime = TimeSpan.Zero;

        public PreviewOutput(int frameSkip, bool pumpMessageQueue = false)
        {
            _frameSkip = frameSkip;
            _pumpMessageQueue = pumpMessageQueue;
            _form = new PreviewOutputForm();
            _form.Show();
            _form.SetDesktopLocation(0, 0);
            _stopwatch = Stopwatch.StartNew();
        }

        public void Write(SKImage image, byte[] data, double fractionComplete, TimeSpan length)
        {
            if (!_form.Visible)
            {
                throw new Exception("Preview window closed");
            }

            // Post-increment so we take frame 0
            var showFrame = _frameIndex++ % _frameSkip == 0;
            var showFps = showFrame || (_stopwatch.Elapsed - _lastFpsUpdateTime).TotalMilliseconds > 100;
            if (showFps)
            {
                var elapsedSeconds = _stopwatch.Elapsed.TotalSeconds;
                var fps = _frameIndex / elapsedSeconds;
                var eta = TimeSpan.FromSeconds(elapsedSeconds / fractionComplete - elapsedSeconds);
                _form.BeginInvoke(new Action(() =>
                {
                    if (_form.IsDisposed || !_form.Visible)
                    {
                        return;
                    }
                    _form.toolStripStatusLabel2.Text = $"{fractionComplete:P} of {length} @ {fps:F}fps, ETA {eta:g}";
                    TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.Normal, _form.Handle);
                    TaskbarManager.Instance.SetProgressValue((int)(fractionComplete * 100), 100, _form.Handle);
                }));
                _lastFpsUpdateTime = _stopwatch.Elapsed;
            }

            if (showFrame)
            {
                // Copy the bitmap for use on the GUI thread
                var copy = image.ToBitmap();
                _form.BeginInvoke(new Action(() =>
                {
                    if (_form.IsDisposed || !_form.Visible)
                    {
                        return;
                    }

                    _form.pictureBox1.Image = copy;
                }));
            }

            if (_pumpMessageQueue)
            {
                Application.DoEvents();
            }
        }

        public void Dispose()
        {
            _stopwatch.Stop();
            try
            {
                _form?.BeginInvoke(() =>
                    {
                        _form?.Close();
                        _form?.Dispose();
                    }
                );
            }
            catch (Exception)
            {
                // We might get this if exiting the program
            }
        }
    }
}

================================================
FILE: LibSidWiz/Outputs/PreviewOutputForm.Designer.cs
================================================
namespace LibSidWiz.Outputs
{
    partial class PreviewOutputForm
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(PreviewOutputForm));
            this.pictureBox1 = new System.Windows.Forms.PictureBox();
            this.statusStrip1 = new System.Windows.Forms.StatusStrip();
            this.toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel();
            this.toolStripStatusLabel2 = new System.Windows.Forms.ToolStripStatusLabel();
            ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit();
            this.statusStrip1.SuspendLayout();
            this.SuspendLayout();
            // 
            // pictureBox1
            // 
            this.pictureBox1.Dock = System.Windows.Forms.DockStyle.Fill;
            this.pictureBox1.Location = new System.Drawing.Point(0, 0);
            this.pictureBox1.Name = "pictureBox1";
            this.pictureBox1.Size = new System.Drawing.Size(368, 103);
            this.pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize;
            this.pictureBox1.TabIndex = 3;
            this.pictureBox1.TabStop = false;
            // 
            // statusStrip1
            // 
            this.statusStrip1.ImageScalingSize = new System.Drawing.Size(32, 32);
            this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
            this.toolStripStatusLabel1,
            this.toolStripStatusLabel2});
            this.statusStrip1.Location = new System.Drawing.Point(0, 103);
            this.statusStrip1.Name = "statusStrip1";
            this.statusStrip1.Size = new System.Drawing.Size(368, 22);
            this.statusStrip1.TabIndex = 4;
            this.statusStrip1.Text = "Ready!";
            // 
            // toolStripStatusLabel1
            // 
            this.toolStripStatusLabel1.Name = "toolStripStatusLabel1";
            this.toolStripStatusLabel1.Size = new System.Drawing.Size(0, 17);
            // 
            // toolStripStatusLabel2
            // 
            this.toolStripStatusLabel2.Name = "toolStripStatusLabel2";
            this.toolStripStatusLabel2.Size = new System.Drawing.Size(39, 17);
            this.toolStripStatusLabel2.Text = "Ready";
            // 
            // PreviewOutputForm
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.AutoSize = true;
            this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
            this.ClientSize = new System.Drawing.Size(368, 125);
            this.Controls.Add(this.pictureBox1);
            this.Controls.Add(this.statusStrip1);
            this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
            this.Name = "PreviewOutputForm";
            ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit();
            this.statusStrip1.ResumeLayout(false);
            this.statusStrip1.PerformLayout();
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        public System.Windows.Forms.PictureBox pictureBox1;
        private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel1;
        public System.Windows.Forms.StatusStrip statusStrip1;
        public System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel2;






    }
}

================================================
FILE: LibSidWiz/Outputs/PreviewOutputForm.cs
================================================
using System.Windows.Forms;

namespace LibSidWiz.Outputs
{
    public partial class PreviewOutputForm : Form
    {
        
        public PreviewOutputForm()
        {
            InitializeComponent();
        }
    }

}


================================================
FILE: LibSidWiz/Outputs/PreviewOutputForm.resx
================================================
<?xml version="1.0" encoding="utf-8"?>
<root>
  <!-- 
    Microsoft ResX Schema 
    
    Version 2.0
    
    The primary goals of this format is to allow a simple XML format 
    that is mostly human readable. The generation and parsing of the 
    various data types are done through the TypeConverter classes 
    associated with the data types.
    
    Example:
    
    ... ado.net/XML headers & schema ...
    <resheader name="resmimetype">text/microsoft-resx</resheader>
    <resheader name="version">2.0</resheader>
    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
        <value>[base64 mime encoded serialized .NET Framework object]</value>
    </data>
    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
        <comment>This is a comment</comment>
    </data>
                
    There are any number of "resheader" rows that contain simple 
    name/value pairs.
    
    Each data row contains a name, and value. The row also contains a 
    type or mimetype. Type corresponds to a .NET class that support 
    text/value conversion through the TypeConverter architecture. 
    Classes that don't support this are serialized and stored with the 
    mimetype set.
    
    The mimetype is used for serialized objects, and tells the 
    ResXResourceReader how to depersist the object. This is currently not 
    extensible. For a given mimetype the value must be set accordingly:
    
    Note - application/x-microsoft.net.object.binary.base64 is the format 
    that the ResXResourceWriter will generate, however the reader can 
    read any of the formats listed below.
    
    mimetype: application/x-microsoft.net.object.binary.base64
    value   : The object must be serialized with 
            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
            : and then encoded with base64 encoding.
    
    mimetype: application/x-microsoft.net.object.soap.base64
    value   : The object must be serialized with 
            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
            : and then encoded with base64 encoding.

    mimetype: application/x-microsoft.net.object.bytearray.base64
    value   : The object must be serialized into a byte array 
            : using a System.ComponentModel.TypeConverter
            : and then encoded with base64 encoding.
    -->
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
    <xsd:element name="root" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="metadata">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" />
              </xsd:sequence>
              <xsd:attribute name="name" use="required" type="xsd:string" />
              <xsd:attribute name="type" type="xsd:string" />
              <xsd:attribute name="mimetype" type="xsd:string" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="assembly">
            <xsd:complexType>
              <xsd:attribute name="alias" type="xsd:string" />
              <xsd:attribute name="name" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="data">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="resheader">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" />
            </xsd:complexType>
          </xsd:element>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
  <resheader name="resmimetype">
    <value>text/microsoft-resx</value>
  </resheader>
  <resheader name="version">
    <value>2.0</value>
  </resheader>
  <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name="writer">
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
    <value>17, 17</value>
  </metadata>
  <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
  <data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
    <value>
        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
        4f2trf//p6f1/5eX5f9ubqv/Tk6b/QsLLOlYWL73jY3i/zIyUbgAAAAOAAAAAAAAAAAAAAAAAAAAAAAA
        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJENDY8iiou//r6///6Ki
        //+Njf//q6v//62t//+trf//rq7//6+v//+Bgbz/dHS9/5eX8P9FRWPJAAAAIAAAAAAAAAAAAAAAAAAA
        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAA
        AAMAAAABAAAAAQAAAAMAYAQfHwAAH/4AAD/8AAA//AAAP/gAAB/4AAAf+AAAH/gAAB/4AAAf4AAAB+AA
        AAfgAAAH4AAAB+AAAAfgAAAH4AAAB/4AAH/+AAB/fgAAfz8AAP8eAAD/BAAE/wAAf/8AAH//AAD//ygA
        AAAwAAAAYAAAAAEAIAAAAAAAACQAABILAAASCwAAAAAAAAAAAAAAra3/AK2t/wCtrf8Ara3/AK2t/wCt
        rf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCtrf8Ara3/AK2t/wAA
        AP8Ara3/AK2t/wCtrf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCtrf8Ara3/AAAA/1JS//+trf//ra3//62t
        //+trf//ra3//62t//+trf//UlL//wAAAP8Ara3/AK2t/wAAAP//AAAA/wAAAP8AAAAA////AK2t/wD/
        //8A////AP///wD///8Ara3/AP///wD///8A////AP///wCtrf8A////AP///wD///8A////AK2t/wD/
        //8A////AP///wD///8AAAD/AP///wD///8A////AP///wCtrf8A////AP///wD///8AAAD/UlL//62t
        //+trf//ra3//62t//+trf//ra3//62t//9SUv//AAAA/wCtrf8A////AK2t/wAAAP//AAAA/wAAAP8A
        AAAA////AK2t/wD///8A////AP///wD///8Ara3/AP///wD///8A////AP///wCtrf8A////AP///wD/
        //8A////AK2t/wD///8A////AP///wD///8Ara3/AAAA/wD///8A////AP///wCtrf8A////AP///wD/
        //8AAAD/ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//UlL//wAAAP8Ara3/AAAA//8A
        AAD/AAAA/wAAAP8AAAAA////AK2t/wD///8A////AP///wD///8Ara3/AP///wD///8A////AP///wCt
        rf8A////AP///wD///8A////AK2t/wD///8A////AP///wD///8Ara3/AP///wAAAP8A////AP///wCt
        rf8A////AP///wAAAP9SUv//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//1JS
        //8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAAA////AK2t/wD///8A////AP///wD///8Ara3/AP///wD/
        //8A////AP///wCtrf8A////AP///wD///8A////AK2t/wD///8A////AP///wD///8Ara3/AP///wAA
        AP8AAAD/AP///wCtrf8A////AP///wAAAP+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t
        //+trf//ra3//62t//9SUv//AAAA//8AAAD/AAAA/wAAAP8AAAAAra3/AK2t/wCtrf8Ara3/AK2t/wCt
        rf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCtrf8Ara3/AK2t/wAAAP8Ara3/AK2t/wCt
        rf8Ara3/AAAA/1JS//9SUv//AAAA/wCtrf8Ara3/AAAA/1JS//+trf//ra3//wAAAP9SUv//ra3//62t
        //+trf//ra3//62t//+trf//AAAA/62t//+trf//UlL//wAAAP//AAAA/wAAAP8AAAAA////AK2t/wD/
        //8A////AP///wD///8Ara3/AP///wD///8A////AP///wCtrf8A////AAAA/wD///8Ara3/AAAA/wAA
        AP8A////AP///wD///8AAAD/UlL//62t//+trf//UlL//wAAAP8A////AAAA/62t//+trf//AAAA/1JS
        //+trf//AAAA/62t//+trf//ra3//62t//+trf//AAAA/wAAAP+trf//ra3//wAAAP//AAAA/wAAAP8A
        AAAA////AK2t/wD///8A////AP///wD///8Ara3/AP///wAAAP8AAAD/AP///wCtrf8Ara3/AAAA/wAA
        AP8AAAD//wAAAAAAAP8A////AP///wAAAP8AAAD/ra3//62t//+trf//ra3//wAAAP8AAAD/UlL//62t
        //8AAAD//wAAAAAAAP8AAAD/UlL//62t//8AAAD/UlL//62t//8AAAD//wAAAP8AAAAAAAD/AAAA//8A
        AAD/AAAA/wAAAP8AAAAA////AK2t/wD///8A////AP///wD///8AAAD/AAAA//8AAAD/AAAAAAAA/wAA
        AP8AAAD//wAAAP8AAAD/AAAA/wAAAAAAAP8A////AAAA//8AAAAAAAD/ra3//62t//+trf//ra3//wAA
        AP8AAAD/ra3//62t//8AAAD//wAAAP8AAAD/AAAAAAAA/wAAAP8AAAD/UlL//62t//8AAAD//wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAA////AK2t/wD///8AAAD/AAAA/wAAAP//AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD//wAAAP8AAAAAAAD/UlL//1JS
        //9SUv//UlL//wAAAP9SUv//ra3//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAAAAAP9SUv//ra3//wAA
        AP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/AAAA/wAAAP//AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAP8AAAD/AAAA/wAA
        AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP+trf//ra3//wAAAP8AAAD//wAAAP8AAAD/AAAA/wAAAAAA
        AP+trf//ra3//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/AAAA/1JS
        //9SUv//UlL//1JS//9SUv//UlL//1JS//9SUv//UlL//1JS//8AAAD/AAAA/1JS//9SUv//AAAA/wAA
        AP8AAAD/AAAA/1JS//+trf//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/wAA
        AP9SUv//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t
        //+trf//UlL//1JS//9SUv//AAAA/62t//+trf//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAAAAAD/UlL//62t//+trf//ra3//62t//+trf//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA
        AP8AAAD/ra3//62t//+trf//ra3//62t//+trf//UlL//wAAAP8AAAD//wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAAAAAP9SUv//ra3//62t//+trf//ra3//wAAAP8AAAD/AAAA////////////////////
        /////////////wAAAP8AAK3/AAAA/wAAAP8AAAD/ra3//62t//+trf//ra3//1JS//8AAAD//wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAAAAAA/1JS//+trf//ra3//62t//8AAAD/AAAA/wAArf8AAP//AAAA////
        /////////////////////////////wAAAP8AAP//AAD//wAA//8AAK3/AAAA/wAAAP+trf//ra3//62t
        //9SUv//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/62t//+trf//ra3//wAAAP8AAK3/AAD//wAA
        //8AAP//AAAA/////////////////////////////////wAAAP8AAP//AAD//wAA//8AAP//AAD//wAA
        rf8AAAD/ra3//62t//9SUv//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//62t//+trf//ra3//wAA
        AP8AAP//AAD//wAA//8AAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAP//AAD//wAA
        //8AAP//AAD//wAA//8AAAD/ra3//62t//+trf//UlL//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//62t
        //+trf//AAAA/wAArf8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA
        //8AAP//AAD//wAA//8AAP//AAD//wAA//8AAK3/AAAA/62t//+trf//UlL//wAAAP//AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAAAAAD/ra3//62t//+trf//AAAA/wAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAP//AAD//wAA
        //8AAP//AAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAP//AAAA/62t//+trf//UlL//wAA
        AP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAAAAAD/ra3//62t//+trf//AAAA/wAA//8AAAD//////////////////////wAA
        AP8AAP//AAD//wAA//8AAP//AAD//wAAAP///////////////////////////wAAAP8AAP//AAAA/62t
        //+trf//UlL//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//62t//+trf//ra3//wAAAP8AAAD/////////
        /////////////wAAAP8AAK3/AACt/wAArf8AAK3/AACt/wAAAP///////////////////////////wAA
        AP8AAAD/ra3//62t//+trf//UlL//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//62t//+trf//ra3//62t
        //8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA
        AP8AAAD/AAAA/wAAAP+trf//ra3//62t//+trf//UlL//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/62t
        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t
        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//9SUv//AAAA//8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAAAAAA/1JS//+trf//ra3//62t//8AUq3/AFKt/wBSrf8AUq3/AFKt/wBSrf8AUq3/AFKt/wBS
        rf8AUq3/AFKt/wBSrf8AUq3/AFKt/wBSrf8AUq3/AFKt/62t//+trf//ra3//62t//9SUv//AAAA//8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAAAAAD/AAAA/wAAAP//AAAA/wAAAAAAAP9SUv//ra3//62t//+trf//ra3//62t//+trf//ra3//62t
        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//1JS
        //8AAAD//wAAAP8AAAAAAAD/AAAA/wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAAAAAP+trf//ra3//1JS//8AAAD//wAAAP8AAAAAAAD/ra3//62t//8AUq3/AFKt/wBS
        rf8AUq3/AFKt/wBSrf8AUq3/AFKt/wBSrf8AUq3/AFKt/wBSrf8AUq3/AFKt/wBSrf8AUq3/AFKt/62t
        //+trf//UlL//wAAAP//AAAA/wAAAAAAAP+trf//ra3//1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAP+trf//AAAA/1JS//8AAAD/AAAA/wAAAP8AAAD/ra3//62t
        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t
        //+trf//ra3//62t//+trf//UlL//wAAAP8AAAD/AAAA/wAAAP+trf//AAAA/1JS//8AAAD//wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAP+trf//AAAA/1JS//8AAAD/ra3//1JS
        //8AAAD/ra3//62t//+trf//ra3//wAAAP8AAAD/AAAA/62t//+trf//ra3//62t//+trf//ra3//62t
        //8AAAD/AAAA/wAAAP+trf//ra3//62t//+trf//UlL//wAAAP9SUv//ra3//wAAAP+trf//AAAA/1JS
        //8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAP+trf//AAAA/1JS
        //8AAAD/ra3//1JS//8AAAD/ra3//62t//+trf//AAAA/wAAAP8AAAD/AAAA/wAAAP+trf//ra3//62t
        //+trf//ra3//wAAAP8AAAD/AAAA/wAAAP8AAAD/ra3//62t//+trf//UlL//wAAAP9SUv//ra3//wAA
        AP+trf//AAAA/1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAA
        AP+trf//AAAA/1JS//8AAAD/AAAA/wAAAP8AAAD/ra3//62t//+trf//AAAA/wAAAP8AAAD/AAAA/wAA
        AP+trf//ra3//62t//+trf//ra3//wAAAP8AAAD/AAAA/wAAAP8AAAD/ra3//62t//+trf//UlL//wAA
        AP8AAAD/AAAA/wAAAP+trf//AAAA/1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAAAAAP+trf//ra3//1JS//8AAAD//wAAAP8AAAAAAAD/ra3//62t//+trf//ra3//62t
        //8AAAD/AAAA/wAAAP+trf//ra3//62t//+trf//ra3//62t//+trf//AAAA/wAAAP8AAAD/ra3//62t
        //+trf//UlL//wAAAP//AAAA/wAAAAAAAP+trf//ra3//1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/AAAA/wAAAP//AAAA/wAAAP8AAAAAAAD/ra3//62t
        //+trf//ra3//62t//8AAAD/AAAA/wAAAP+trf//ra3//62t//+trf//ra3//62t//+trf//AAAA/wAA
        AP8AAAD/ra3//62t//+trf//UlL//wAAAP//AAAA/wAAAP8AAAAAAAD/AAAA/wAAAP//AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAAAAAD/ra3//62t//+trf//AAAA/wAAAP8AAAD/AAAA/wAAAP+trf//ra3//62t//+trf//ra3//wAA
        AP8AAAD/AAAA/wAAAP8AAAD/ra3//62t//+trf//UlL//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAAAAAD/ra3//62t//+trf//AAAA/wAAAP8AAAD/AAAA/wAAAP+trf//ra3//62t
        //+trf//ra3//wAAAP8AAAD/AAAA/wAAAP8AAAD/ra3//62t//+trf//UlL//wAAAP//AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//62t//+trf//ra3//wAAAP8AAAD/AAAA/62t
        //+trf//ra3//62t//+trf//ra3//62t//8AAAD/AAAA/wAAAP+trf//ra3//62t//+trf//UlL//wAA
        AP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//62t//+trf//ra3//62t
        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t
        //+trf//UlL//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/UlL//62t
        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t
        //+trf//ra3//62t//9SUv//UlL//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAAAAAA/1JS//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t
        //+trf//ra3//62t//+trf//ra3//1JS//9SUv//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAP8AAAD/UlL//1JS//9SUv//AAAA/1JS//9SUv//UlL//wAA
        AP9SUv//UlL//1JS//8AAAD/UlL//1JS//8AAAD/UlL//wAAAP8AAAD//wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAABSUv//AAAA//8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/1JS//9SUv//AAAA/wAAAP8AAAD/AAAA/wAA
        AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA//8AAAAAAAD//wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAACtrf//UlL//wAA
        AP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/UlL//62t//+trf//AAAA//8A
        AAD/AAAAAAAA//8AAAD/AAAA/wAAAAAAAP//AAAA/wAAAP8AAAAAAAD//wAAAP8AAAAAAAD//wAAAP8A
        AAD/AAAAAAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AACtrf//ra3//1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAP9SUv//ra3//62t
        //8AAAD//wAAAP8AAAD/AAAAAAAA//8AAAD/AAAA/wAAAAAAAP//AAAA/wAAAP8AAAAAAAD//wAAAP8A
        AAAAAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAACtrf//ra3//62t//9SUv//AAAA/wAAAP//AAAA/wAAAP8AAAD/AAAAAAAA/1JS
        //+trf//ra3//wAAAP8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/AAAA//8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAACtrf//ra3//62t//+trf//ra3//1JS//8AAAD//wAAAP8A
        AAAAAAD/UlL//62t//+trf//AAAA/1JS//9SUv//AAAA/wAAAP//AAAA/wAAAAAAAP9SUv//UlL//wAA
        AP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//62t//+trf//ra3//62t
        //9SUv//AAAA/wAAAP9SUv//ra3//62t//+trf//ra3//62t//8AAAD/ra3//1JS//8AAAD/AAAA/1JS
        //+trf//ra3//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/62t
        //+trf//ra3//62t//+trf//UlL//1JS//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//wAA
        AP8AAAD/UlL//62t//+trf//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAAAAAP+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t
        //+trf//ra3//62t//+trf//ra3//62t//8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAAAAAAAAAcAAAAAAAAABwAAAAAAAAAPAAAAAAAAAB8AAAAAAAAADwAAAAAAAAAH
        AAAAAAAAAAcAAAAAgAEAzwAAAMeIAcD/AAAD/9gD4f8AAB//gAHh/wAA//4AAAP/AAD/+AAAA/8AAP/w
        AAAH/wAA/+AAAAf/AAD/wAAAA/8AAP/AAAAD/wAA/4AAAAH/AAD/gAAAAf8AAP+AAAAB/wAA/4AAAAH/
        AAD/gAAAAf8AAP+AAAAB/wAA/8AAAAP/AAD/wAAAA/8AAPxgAAAGPwAA+DAAAAwfAAD4AAAAAB8AAPgA
        AAAAHwAA+AAAAAAfAAD4AAAAAB8AAPgwAAAMHwAA/HAAAA4/AAD/8AAAD/8AAP/wAAAP/wAA//AAAA//
        AAD/8AAAD/8AAP/wAAAP/wAA//gAAB//AAB//AAAP/8AAD/4AAC//wAAH/Bu7d//AAAP4O7t//8AAAPA
        +f///wAAAYAw////AAAAAAD///8AAIAAAf///wAAwAAD////AAAoAAAAMAAAAGAAAAABACAAAAAAAAAk
        AAASCwAAEgsAAAAAAAAAAAAAAK2t/wCtrf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCt
        rf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCtrf8AAAD/AK2t/wCtrf8Ara3/AK2t/wCt
        rf8Ara3/AK2t/wCtrf8Ara3/AK2t/wAAAP9SUv//ra3//62t//+trf//ra3//62t//+trf//ra3//1JS
        //8AAAD/AK2t/wCtrf8AAAD//wAAAP8AAAD/AAAAAP///wCtrf8A////AP///wD///8A////AK2t/wD/
        //8A////AP///wD///8Ara3/AP///wD///8A////AP///wCtrf8A////AP///wD///8A////AAAA/wD/
        //8A////AP///wD///8Ara3/AP///wD///8A////AAAA/1JS//+trf//ra3//62t//+trf//ra3//62t
        //+trf//UlL//wAAAP8Ara3/AP///wCtrf8AAAD//wAAAP8AAAD/AAAAAP///wCtrf8A////AP///wD/
        //8A////AK2t/wD///8A////AP///wD///8Ara3/AP///wD///8A////AP///wCtrf8A////AP///wD/
        //8A////AK2t/wAAAP8A////AP///wD///8Ara3/AP///wD///8A////AAAA/62t//+trf//ra3//62t
        //+trf//ra3//62t//+trf//ra3//1JS//8AAAD/AK2t/wAAAP//AAAA/wAAAP8AAAD/AAAAAP///wCt
        rf8A////AP///wD///8A////AK2t/wD///8A////AP///wD///8Ara3/AP///wD///8A////AP///wCt
        rf8A////AP///wD///8A////AK2t/wD///8AAAD/AP///wD///8Ara3/AP///wD///8AAAD/UlL//62t
        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//9SUv//AAAA//8AAAD/AAAA/wAAAP8A
        AAD/AAAAAP///wCtrf8A////AP///wD///8A////AK2t/wD///8A////AP///wD///8Ara3/AP///wD/
        //8A////AP///wCtrf8A////AP///wD///8A////AK2t/wD///8AAAD/AAAA/wD///8Ara3/AP///wD/
        //8AAAD/ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//UlL//wAA
        AP//AAAA/wAAAP8AAAD/AAAAAK2t/wCtrf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCt
        rf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCtrf8AAAD/AK2t/wCtrf8Ara3/AK2t/wAAAP9SUv//UlL//wAA
        AP8Ara3/AK2t/wAAAP9SUv//ra3//62t//8AAAD/UlL//62t//+trf//ra3//62t//+trf//ra3//wAA
        AP+trf//ra3//1JS//8AAAD//wAAAP8AAAD/AAAAAP///wCtrf8A////AP///wD///8A////AK2t/wD/
        //8A////AP///wD///8Ara3/AP///wAAAP8A////AK2t/wAAAP8AAAD/AP///wD///8A////AAAA/1JS
        //+trf//ra3//1JS//8AAAD/AP///wAAAP+trf//ra3//wAAAP9SUv//ra3//wAAAP+trf//ra3//62t
        //+trf//ra3//wAAAP8AAAD/ra3//62t//8AAAD//wAAAP8AAAD/AAAAAP///wCtrf8A////AP///wD/
        //8A////AK2t/wD///8AAAD/AAAA/wD///8Ara3/AK2t/wAAAP8AAAD/AAAA//8AAAAAAAD/AP///wD/
        //8AAAD/AAAA/62t//+trf//ra3//62t//8AAAD/AAAA/1JS//+trf//AAAA//8AAAAAAAD/AAAA/1JS
        //+trf//AAAA/1JS//+trf//AAAA//8AAAD/AAAAAAAA/wAAAP//AAAA/wAAAP8AAAD/AAAAAP///wCt
        rf8A////AP///wD///8A////AAAA/wAAAP//AAAA/wAAAAAAAP8AAAD/AAAA//8AAAD/AAAA/wAAAP8A
        AAAAAAD/AP///wAAAP//AAAAAAAA/62t//+trf//ra3//62t//8AAAD/AAAA/62t//+trf//AAAA//8A
        AAD/AAAA/wAAAAAAAP8AAAD/AAAA/1JS//+trf//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAAAP///wCtrf8A////AAAA/wAAAP8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAAAAAA//8AAAD/AAAAAAAA/1JS//9SUv//UlL//1JS//8AAAD/UlL//62t
        //8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/UlL//62t//8AAAD//wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAAAAAA/wAAAP8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA
        AP8AAAD/ra3//62t//8AAAD/AAAA//8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//62t//8AAAD//wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/wAAAP9SUv//UlL//1JS//9SUv//UlL//1JS
        //9SUv//UlL//1JS//9SUv//AAAA/wAAAP9SUv//UlL//wAAAP8AAAD/AAAA/wAAAP9SUv//ra3//wAA
        AP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAP8AAAD/UlL//62t//+trf//ra3//62t
        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//1JS//9SUv//UlL//wAA
        AP+trf//ra3//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/1JS//+trf//ra3//62t
        //+trf//ra3//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/62t//+trf//ra3//62t
        //+trf//ra3//1JS//8AAAD/AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/UlL//62t
        //+trf//ra3//62t//8AAAD/AAAA/wAAAP////////////////////////////////8AAAD/AACt/wAA
        AP8AAAD/AAAA/62t//+trf//ra3//62t//9SUv//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAA
        AP9SUv//ra3//62t//+trf//AAAA/wAAAP8AAK3/AAD//wAAAP//////////////////////////////
        //8AAAD/AAD//wAA//8AAP//AACt/wAAAP8AAAD/ra3//62t//+trf//UlL//wAAAP//AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAAAAAP+trf//ra3//62t//8AAAD/AACt/wAA//8AAP//AAD//wAAAP//////////////
        //////////////////8AAAD/AAD//wAA//8AAP//AAD//wAA//8AAK3/AAAA/62t//+trf//UlL//wAA
        AP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAAAAAA/62t//+trf//ra3//62t//8AAAD/AAD//wAA//8AAP//AAD//wAA
        AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAD//wAA//8AAP//AAD//wAA//8AAP//AAAA/62t
        //+trf//ra3//1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/62t//+trf//ra3//wAAAP8AAK3/AAD//wAA
        //8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA
        //8AAP//AACt/wAAAP+trf//ra3//1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/62t//+trf//ra3//wAA
        AP8AAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAD//wAA//8AAP//AAD//wAA//8AAAD/AAAA/wAA
        AP8AAAD/AAAA/wAAAP8AAAD/AAD//wAAAP+trf//ra3//1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/62t
        //+trf//ra3//wAAAP8AAP//AAAA//////////////////////8AAAD/AAD//wAA//8AAP//AAD//wAA
        //8AAAD///////////////////////////8AAAD/AAD//wAAAP+trf//ra3//1JS//8AAAD//wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAAAAAA/62t//+trf//ra3//62t//8AAAD/AAAA//////////////////////8AAAD/AACt/wAA
        rf8AAK3/AACt/wAArf8AAAD///////////////////////////8AAAD/AAAA/62t//+trf//ra3//1JS
        //8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAAAAAA/62t//+trf//ra3//62t//+trf//AAAA/wAAAP8AAAD/AAAA/wAA
        AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/ra3//62t
        //+trf//ra3//1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAP+trf//ra3//62t//+trf//ra3//62t
        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t
        //+trf//ra3//62t//+trf//UlL//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAP9SUv//ra3//62t
        //+trf//AFKt/wBSrf8AUq3/AFKt/wBSrf8AUq3/AFKt/wBSrf8AUq3/AFKt/wBSrf8AUq3/AFKt/wBS
        rf8AUq3/AFKt/wBSrf+trf//ra3//62t//+trf//UlL//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/wAAAP8AAAD//wAAAP8A
        AAAAAAD/UlL//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t
        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//9SUv//AAAA//8AAAD/AAAAAAAA/wAA
        AP8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//62t
        //9SUv//AAAA//8AAAD/AAAAAAAA/62t//+trf//AFKt/wBSrf8AUq3/AFKt/wBSrf8AUq3/AFKt/wBS
        rf8AUq3/AFKt/wBSrf8AUq3/AFKt/wBSrf8AUq3/AFKt/wBSrf+trf//ra3//1JS//8AAAD//wAAAP8A
        AAAAAAD/ra3//62t//9SUv//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAAAAAD/ra3//wAAAP9SUv//AAAA/wAAAP8AAAD/AAAA/62t//+trf//ra3//62t//+trf//ra3//62t
        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//1JS
        //8AAAD/AAAA/wAAAP8AAAD/ra3//wAAAP9SUv//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAAAAAD/ra3//wAAAP9SUv//AAAA/62t//9SUv//AAAA/62t//+trf//ra3//62t
        //8AAAD/AAAA/wAAAP+trf//ra3//62t//+trf//ra3//62t//+trf//AAAA/wAAAP8AAAD/ra3//62t
        //+trf//ra3//1JS//8AAAD/UlL//62t//8AAAD/ra3//wAAAP9SUv//AAAA//8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//wAAAP9SUv//AAAA/62t//9SUv//AAAA/62t
        //+trf//ra3//wAAAP8AAAD/AAAA/wAAAP8AAAD/ra3//62t//+trf//ra3//62t//8AAAD/AAAA/wAA
        AP8AAAD/AAAA/62t//+trf//ra3//1JS//8AAAD/UlL//62t//8AAAD/ra3//wAAAP9SUv//AAAA//8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//wAAAP9SUv//AAAA/wAA
        AP8AAAD/AAAA/62t//+trf//ra3//wAAAP8AAAD/AAAA/wAAAP8AAAD/ra3//62t//+trf//ra3//62t
        //8AAAD/AAAA/wAAAP8AAAD/AAAA/62t//+trf//ra3//1JS//8AAAD/AAAA/wAAAP8AAAD/ra3//wAA
        AP9SUv//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//62t
        //9SUv//AAAA//8AAAD/AAAAAAAA/62t//+trf//ra3//62t//+trf//AAAA/wAAAP8AAAD/ra3//62t
        //+trf//ra3//62t//+trf//ra3//wAAAP8AAAD/AAAA/62t//+trf//ra3//1JS//8AAAD//wAAAP8A
        AAAAAAD/ra3//62t//9SUv//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAAAAAA/wAAAP8AAAD//wAAAP8AAAD/AAAAAAAA/62t//+trf//ra3//62t//+trf//AAAA/wAA
        AP8AAAD/ra3//62t//+trf//ra3//62t//+trf//ra3//wAAAP8AAAD/AAAA/62t//+trf//ra3//1JS
        //8AAAD//wAAAP8AAAD/AAAAAAAA/wAAAP8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/62t//+trf//ra3//wAA
        AP8AAAD/AAAA/wAAAP8AAAD/ra3//62t//+trf//ra3//62t//8AAAD/AAAA/wAAAP8AAAD/AAAA/62t
        //+trf//ra3//1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/62t
        //+trf//ra3//wAAAP8AAAD/AAAA/wAAAP8AAAD/ra3//62t//+trf//ra3//62t//8AAAD/AAAA/wAA
        AP8AAAD/AAAA/62t//+trf//ra3//1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAAAAAA/62t//+trf//ra3//62t//8AAAD/AAAA/wAAAP+trf//ra3//62t//+trf//ra3//62t
        //+trf//AAAA/wAAAP8AAAD/ra3//62t//+trf//ra3//1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAAAAAA/62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t
        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//1JS//8AAAD//wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/1JS//+trf//ra3//62t//+trf//ra3//62t
        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//UlL//1JS
        //8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAP9SUv//ra3//62t
        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t
        //9SUv//UlL//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAAAAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAAAAAD/AAAA/1JS//9SUv//UlL//wAAAP9SUv//UlL//1JS//8AAAD/UlL//1JS//9SUv//AAAA/1JS
        //9SUv//AAAA/1JS//8AAAD/AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAAUlL//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAAAAAP9SUv//UlL//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA
        AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP//AAAAAAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAra3//1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAAAAAA/1JS//+trf//ra3//wAAAP//AAAA/wAAAAAAAP//AAAA/wAAAP8A
        AAAAAAD//wAAAP8AAAD/AAAAAAAA//8AAAD/AAAAAAAA//8AAAD/AAAA/wAAAAAAAP//AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAra3//62t//9SUv//AAAA//8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/UlL//62t//+trf//AAAA//8AAAD/AAAA/wAAAAAA
        AP//AAAA/wAAAP8AAAAAAAD//wAAAP8AAAD/AAAAAAAA//8AAAD/AAAAAAAA//8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAra3//62t
        //+trf//UlL//wAAAP8AAAD//wAAAP8AAAD/AAAA/wAAAAAAAP9SUv//ra3//62t//8AAAD/AAAA//8A
        AAD/AAAA/wAAAP8AAAD/AAAAAAAA/wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAAra3//62t//+trf//ra3//62t//9SUv//AAAA//8AAAD/AAAAAAAA/1JS//+trf//ra3//wAA
        AP9SUv//UlL//wAAAP8AAAD//wAAAP8AAAAAAAD/UlL//1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAAAAAA/62t//+trf//ra3//62t//+trf//UlL//wAAAP8AAAD/UlL//62t
        //+trf//ra3//62t//+trf//AAAA/62t//9SUv//AAAA/wAAAP9SUv//ra3//62t//8AAAD//wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAP+trf//ra3//62t//+trf//ra3//1JS
        //9SUv//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//8AAAD/AAAA/1JS//+trf//ra3//wAA
        AP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//62t
        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t
        //+trf//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A
        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAAAAAH
        AAAAAAAAAAcAAAAAAAAADwAAAAAAAAAfAAAAAAAAAA8AAAAAAAAABwAAAAAAAAAHAAAAAIABAM8AAADH
        iAHA/wAAA//YA+H/AAAf/4AB4f8AAP/+AAAD/wAA//gAAAP/AAD/8AAAB/8AAP/gAAAH/wAA/8AAAAP/
        AAD/wAAAA/8AAP+AAAAB/wAA/4AAAAH/AAD/gAAAAf8AAP+AAAAB/wAA/4AAAAH/AAD/gAAAAf8AAP/A
        AAAD/wAA/8AAAAP/AAD8YAAABj8AAPgwAAAMHwAA+AAAAAAfAAD4AAAAAB8AAPgAAAAAHwAA+AAAAAAf
        AAD4MAAADB8AAPxwAAAOPwAA//AAAA//AAD/8AAAD/8AAP/wAAAP/wAA//AAAA//AAD/8AAAD/8AAP/4
        AAAf/wAAf/wAAD//AAA/+AAAv/8AAB/wbu3f/wAAD+Du7f//AAADwPn///8AAAGAMP///wAAAAAA////
        AACAAAH///8AAMAAA////wAA
</value>
  </data>
</root>

================================================
FILE: LibSidWiz/ProcessWrapper.cs
================================================
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

namespace LibSidWiz
{
    public class ProcessWrapper: IDisposable
    {
        private readonly Process _process;
        private readonly BlockingCollection<string> _lines = new(new ConcurrentQueue<string>());
        private readonly CancellationTokenSource _cancellationTokenSource;
        private int _streamCount;

        public ProcessWrapper(string filename, string arguments, bool captureStdErr = false, bool captureStdOut = true, bool showConsole = false)
        {
            _cancellationTokenSource = new CancellationTokenSource();

            _process = new Process
            {
                StartInfo = new ProcessStartInfo
                {
                    FileName = filename,
                    Arguments = arguments,
                    RedirectStandardOutput = captureStdOut,
                    RedirectStandardError = captureStdErr,
                    UseShellExecute = false,
                    CreateNoWindow = !showConsole
                },
                EnableRaisingEvents = true
            };
            if (_process == null)
            {
                throw new Exception($"Error running {filename} {arguments}");
            }

            if (captureStdOut)
            {
                _process.OutputDataReceived += OnText;
            }
            if (captureStdErr)
            {
                _process.ErrorDataReceived += OnText;
            }

            _process.Start();

            if (captureStdOut)
            {
                _process.BeginOutputReadLine();
                ++_streamCount;
            }
            if (captureStdErr)
            {
                _process.BeginErrorReadLine();
                ++_streamCount;
            }
        }

        private void OnText(object sender, DataReceivedEventArgs e)
        {
            if (_cancellationTokenSource.IsCancellationRequested)
            {
                return;
            }
            try
            {
                _lines.TryAdd(e.Data, -1, _cancellationTokenSource.Token);
            }
            catch (OperationCanceledException)
            {
                // Discard it
            }
        }

        /// <summary>
        /// Blocks while waiting for the next line...
        /// </summary>
        public IEnumerable<string> Lines()
        {
            while (!_cancellationTokenSource.IsCancellationRequested)
            {
                string line;
                try
                {
                    // Blocking take
                    line = _lines.Take(_cancellationTokenSource.Token);
                }
                catch (OperationCanceledException)
                {
                    yield break;
                }

                if (line == null)
                {
                    if (--_streamCount == 0)
                    {
                        // We see a null to indicate the end of each stream. We break on the last one.
                        yield break;
                    }
                    // Else drop it
                    continue;
                }

                yield return line;
            }
        }

        public void WaitForExit() => _process.WaitForExit();

        public void Dispose()
        {
            _cancellationTokenSource.Cancel();
            _lines.CompleteAdding();
            if (_process != null)
            {
                // Try to kill the process if it is alive
                if (!_process.HasExited)
                {
                    try
                    {
                        _process.EnableRaisingEvents = false;
                        _process.Kill();
                    }
                    catch (Exception)
                    {
                        // May throw if the process terminates first
                    }
                }

                _process.Dispose();
            }

            _cancellationTokenSource.Dispose();
            _lines.Dispose();
        }
    }
}


================================================
FILE: LibSidWiz/SampleBuffer.cs
================================================
using System;
using NAudio.Dsp;
using NAudio.Wave;

namespace LibSidWiz
{
    internal class SampleBuffer: IDisposable
    {
        private readonly WaveStream _reader;
        private readonly ISampleProvider _sampleProvider;

        private class Chunk
        {
            public long Offset;
            public long End;
            public float[] Buffer;

            public bool TryGet(long index, out float value)
            {
                if (index >= Offset && index < End)
                {
                    value = Buffer[index - Offset];
                    return true;
                }

                value = 0;
                return false;
            }
        }

        private readonly Chunk _chunk1;
        private readonly Chunk _chunk2;

        // 4 bytes per sample so this is 1MB
        // If we are rendering ~16 frames at once, we need (typically) 1323 samples per frame,
        // which is a window of 84KB. This is far from causing us trouble here.
        private const int ChunkSize = 256 * 1024 * 1;

        public long Count { get; }

        public int SampleRate { get; }

        public TimeSpan Length { get; }

        public float Max { get; private set; }

        public float Min { get; private set; }

        public SampleBuffer(string filename, Channel.Sides side, bool filter)
        {
            _reader = new AudioFileReader(filename);
            Count = _reader.Length * 8 / _reader.WaveFormat.BitsPerSample / _reader.WaveFormat.Channels;
            SampleRate = _reader.WaveFormat.SampleRate;
            Length = _reader.TotalTime;
            _sampleProvider = side switch
            {
                Channel.Sides.Left => _reader.ToSampleProvider().ToMono(1.0f, 0.0f),
                Channel.Sides.Right => _reader.ToSampleProvider().ToMono(0.0f, 1.0f),
                Channel.Sides.Mix => _reader.ToSampleProvider().ToMono(),
                _ => throw new ArgumentOutOfRangeException(nameof(side), side, null)
            };

            if (filter)
            {
                _sampleProvider = new HighPassSampleProvider(_sampleProvider);
            }

            _chunk1 = new Chunk
            {
                Buffer = new float[ChunkSize],
                Offset = -1,
                End = -1
            };
            _chunk2 = new Chunk
            {
                Buffer = new float[ChunkSize],
                Offset = -1,
                End = -1
            };
        }

        public void Dispose()
        {
            _reader.Dispose();
        }

        public float this[long index]
        {
            get
            {
                // We may be accessed from multiple threads; we therefore need to lock access to avoid concurrent access.
                lock (this)
                {
                    if (_chunk1.TryGet(index, out var value) || _chunk2.TryGet(index, out value))
                    {
                        return value;
                    }

                    // If we are asked for sample 0, reset the buffers
                    if (index == 0)
                    {
                        _chunk1.Offset = _chunk1.End = _chunk2.Offset = _chunk2.End = -1;
                    }

                    // Else pick the lower index chunk to read into
                    var chunk = _chunk1.Offset < _chunk2.Offset ? _chunk1 : _chunk2;
                    // Pick the rounded offset
                    chunk.Offset = (index / ChunkSize) * ChunkSize;
                    chunk.End = chunk.Offset + ChunkSize;
                    _reader.Position = chunk.Offset * _reader.WaveFormat.BitsPerSample / 8 * _reader.WaveFormat.Channels;
                    _sampleProvider.Read(chunk.Buffer, 0, ChunkSize);
                    return chunk.Buffer[index - chunk.Offset];
                }
            }
        }

        public void Analyze()
        {
            Min = float.MaxValue;
            Max = float.MinValue;
            for (int i = 0; i < Count; ++i)
            {
                var sample = this[i];
                Min = Math.Min(Min, sample);
                Max = Math.Max(Max, sample);
            }
        }
    }

    internal class HighPassSampleProvider(ISampleProvider sampleProvider) : ISampleProvider
    {
        private readonly BiQuadFilter _filter = BiQuadFilter.HighPassFilter(sampleProvider.WaveFormat.SampleRate, 20, 1);

        public int Read(float[] buffer, int offset, int count)
        {
            int result = sampleProvider.Read(buffer, offset, count);

            // Apply the filter
            for (int i = 0; i < result; ++i)
            {
                buffer[i] = _filter.Transform(buffer[i]);
            }

            return result;
        }

        public WaveFormat WaveFormat => sampleProvider.WaveFormat;
    }
}


================================================
FILE: LibSidWiz/Triggers/AutoCorrelationTrigger.cs
================================================
using System;
#if DEBUG
using System.Diagnostics;
using System.Text;
#endif

namespace LibSidWiz.Triggers
{
    // ReSharper disable once UnusedType.Global
    internal class AutoCorrelationTrigger: ITriggerAlgorithm
    {
        private float[] _normalDistribution;

        public int GetTriggerPoint(Channel channel, int startIndex, int endIndex, int previousIndex)
        {
            var width = endIndex - startIndex;
            if (_normalDistribution == null || _normalDistribution.Length != width)
            {
                _normalDistribution = new float[width];
                // Generate distribution
                // We fit 2 standard deviations to the width 
                double variance = width * width / 16.0;
                double mu = width / 2.0;
                var scale = Math.Sqrt(2 * Math.PI * variance);
                for (int i = 0; i < width; ++i)
                {
                    _normalDistribution[i] = (float) (Math.Exp(-(i - mu) * (i - mu) / (2 * variance)) / scale);
                }
            }

            var maxCorrelation = double.MinValue;
            var bestOffset = startIndex;
            var previousStart = previousIndex - width / 2;
            // We compute the correlation between the previous window and each possible offset in the new one,
            // weighted by a normal distribution so we prefer ones near the middle.
            // The correlation is defined as
            // sum((x - mean(x)) * (y - mean(y)) / sqrt(sum(pow(x - mean(x), 2)) * sum(pow(y - mean(y), 2)))
            // where x and y are the samples in each series. Since the reference is fixed, we can compute it once.
            float sumY = 0;
            float sumX = 0;
            for (int i = 0; i < width; ++i)
            {
                sumY += channel.GetSample(previousStart + i);
                sumX += channel.GetSample(startIndex + i);
            }
            var meanY = sumY / width;
            float sumSquaredYDiff = 0;
            for (int i = 0; i < width; ++i)
            {
                var y = channel.GetSample(previousStart + i);
                var yDiff = y - meanY;
                sumSquaredYDiff += yDiff * yDiff;
            }

            // ReSharper disable once CompareOfFloatsByEqualityOperator
            if (sumSquaredYDiff == 0.0f)
            {
                // No point continuing - we return the middle of the data
                return startIndex + width / 2;
            }

            var correlations = new double[width];
            for (int trialOffset = 0; trialOffset < width; ++trialOffset)
            {
                // We compute mean(x) as we go
                var meanX = sumX / width;
                // ...by amending the moving sum. This will accumulate floating point error but it's not significant.
                sumX -= channel.GetSample(startIndex + trialOffset);
                sumX += channel.GetSample(startIndex + trialOffset + width);

                // sum((x - mean(x)) * (y - mean(y)) / sqrt(sum(pow(x - mean(x), 2)) * sum(pow(y - mean(y), 2)))
                float sumTop = 0;
                float sumSquaredXDiff = 0;
                for (int i = 0; i < width; ++i)
                {
                    var x = channel.GetSample(startIndex + trialOffset + i);
                    var y = channel.GetSample(previousStart + i);
                    sumTop += (x - meanX) * (y - meanY);
                    sumSquaredXDiff += (x - meanX) * (x - meanX);
                }

                var correlation = sumTop / Math.Sqrt(sumSquaredXDiff * sumSquaredYDiff);

                // We then weight by our normal distribution so we will prefer points near the middle.
                correlation *= _normalDistribution[trialOffset];

                // debug
                correlations[trialOffset] = correlation;

                if (correlation > maxCorrelation)
                {
                    maxCorrelation = correlation;
                    bestOffset = trialOffset;
                }
            }

#if DEBUG
            Debug.WriteLine($"Autocorrelation: between {startIndex} and {endIndex}, max = {maxCorrelation}, offset = {bestOffset} ({(float)(bestOffset - startIndex)/(endIndex - startIndex):P})");

            var sb = new StringBuilder();
            for (int i = 0; i < width; ++i)
            {
                sb.AppendLine($"{channel.GetSample(previousStart + i)}\t{channel.GetSample(startIndex + i)}\t{correlations[i]}");
            }
#endif

            return startIndex + bestOffset;
        }
    }
}


================================================
FILE: LibSidWiz/Triggers/BiggestPositiveWaveAreaTrigger.cs
================================================
using System;

namespace LibSidWiz.Triggers
{
    /// <summary>
    /// Finds the wave with the biggest positive area (= sum of positive samples)
    /// </summary>
    // ReSharper disable once UnusedType.Global
    internal class BiggestPositiveWaveAreaTrigger : ITriggerAlgorithm
    {
        public int GetTriggerPoint(Channel channel, int startIndex, int endIndex, int previousIndex)
        {
            int bestOffset = -1;
            int lastCrossingPoint = endIndex;
            float previousSample = channel.GetSample(startIndex);
            float bestArea = 0;
            float currentArea = float.MinValue;

            // For each sample...
            for (int i = startIndex + 1; i < endIndex; ++i)
            {
                // Add on the area
                var sample = channel.GetSample(i);
                currentArea += Math.Abs(sample);
                if (sample > 0 && previousSample <= 0)
                {
                    // Positive edge - reset
                    lastCrossingPoint = i;
                    currentArea = sample;
                }
                else if (sample <= 0 && previousSample > 0)
                {
                    // Negative edge - check if it's a new biggest
                    if (currentArea > bestArea)
                    {
                        bestArea = currentArea;
                        bestOffset = lastCrossingPoint;
                    }
                }

                previousSample = sample;
            }
            return bestOffset;
        }
    }
}

================================================
FILE: LibSidWiz/Triggers/BiggestWaveAreaTrigger.cs
================================================
using System;

namespace LibSidWiz.Triggers
{
    /// <summary>
    /// Finds the positive+negative wave with the biggest area (= sum of absolute samples)
    /// </summary>
    // ReSharper disable once UnusedType.Global
    internal class BiggestWaveAreaTrigger : ITriggerAlgorithm
    {
        public int GetTriggerPoint(Channel channel, int startIndex, int endIndex, int previousIndex)
        {
            int bestOffset = -1;
            int lastCrossingPoint = endIndex;
            float previousSample = channel.GetSample(startIndex);
            float bestArea = 0;
            float currentArea = float.MinValue;

            // For each sample...
            for (int i = startIndex + 1; i < endIndex; ++i)
            {
                // Add on the area
                var sample = channel.GetSample(i);
                currentArea += Math.Abs(sample);
                if (sample > 0 && previousSample <= 0)
                {
                    // Positive edge - check if it's a new biggest
                    if (currentArea > bestArea)
                    {
                        bestArea = currentArea;
                        bestOffset = lastCrossingPoint;
                    }

                    // And reset
                    lastCrossingPoint = i;
                    currentArea = sample;
                }

                previousSample = sample;
            }
            return bestOffset;
        }
    }
}

================================================
FILE: LibSidWiz/Triggers/ITriggerAlgorithm.cs
================================================
namespace LibSidWiz.Triggers
{
    public interface ITriggerAlgorithm
    {
        /// <summary>
        /// Finds a "trigger point" within a channel's samples
        /// </summary>
        /// <param name="channel">Channel object holding samples</param>
        /// <param name="startIndex">Index of start of frame for analysis</param>
        /// <param name="endIndex">Index of end of frame for analysis</param>
        /// <param name="previousIndex">Index of previously found trigger</param>
        /// <returns>Index of the trigger point, should be between startIndex and endIndex. Return -1 for failure.</returns>
        int GetTriggerPoint(Channel channel, int startIndex, int endIndex, int previousIndex);
    }
}

================================================
FILE: LibSidWiz/Triggers/MiddleWidest.cs
================================================
using System.Collections.Generic;

namespace LibSidWiz.Triggers
{
    /// <summary>
    /// This corresponds to SidWiz's "alternate" algorithm.
    /// We measure the width of each full wave, and then select the widest ones.
    /// We then select the start point of the "middle" one, if more than one was found.
    /// </summary>
    // ReSharper disable once UnusedType.Global
    class MiddleWidest: ITriggerAlgorithm
    {
        public int GetTriggerPoint(Channel channel, int startIndex, int endIndex, int previousIndex)
        {
            var candidates = new List<int>();
            int lastCrossingPoint = endIndex;
            float previousSample = channel.GetSample(startIndex);
            int bestLength = 0;

            // For each sample...
            for (int i = startIndex + 1; i < endIndex; ++i)
            {
                var sample = channel.GetSample(i);
                if (sample > 0 && previousSample <= 0)
                {
                    // Positive edge
                    int length = i - lastCrossingPoint;
                    if (length > bestLength)
                    {
                        candidates.Clear();
                        bestLength = length;
                    }

                    if (length == bestLength)
                    {
                        candidates.Add(lastCrossingPoint);
                    }

                    lastCrossingPoint = i;
                }

                previousSample = sample;
            }

            if (candidates.Count == 0)
            {
                return -1;
            }

            // We select the "middle" one, preferring the one on the right if even
            return candidates[candidates.Count / 2];
        }
    }
}


================================================
FILE: LibSidWiz/Triggers/NullTrigger.cs
================================================
namespace LibSidWiz.Triggers
{
    /// <summary>
    /// Null algorithm just returns the first sample it's given
    /// </summary>
    // ReSharper disable once UnusedType.Global
    internal class NullTrigger: ITriggerAlgorithm
    {
        public int GetTriggerPoint(Channel channel, int startIndex, int endIndex, int previousIndex)
        {
            return startIndex;
        }
    }
}


================================================
FILE: LibSidWiz/Triggers/PeakSpeedTrigger.cs
================================================
namespace LibSidWiz.Triggers
{
    /// <summary>
    /// Finds the positive edge which most quickly reaches the peak value in the sample range.
    /// This is implemented in a slightly complicated way to make it do it with a single pass over the samples,
    /// you could implement it as:
    /// 1. Find first zero crossing
    /// 2. Find max sample value after that
    /// 4. Select the zero crossing closest to a following max value
    /// This algorithm is based code from オップナー2608.
    /// This algorithm can show good stability for waves which cross the zero point more than once.
    /// </summary>
    public class PeakSpeedTrigger : ITriggerAlgorithm
    {
        public int GetTriggerPoint(Channel channel, int startIndex, int endIndex, int previousIndex)
        {
            float peakValue = float.MinValue;
            int shortestDistance = int.MaxValue;
            int result = -1;
            int i = startIndex;
            while (i < endIndex)
            {
                // First find a positive edge crossing zero
                while (channel.GetSample(i) > 0 && i < endIndex) ++i;
                while (channel.GetSample(i) <= 0 && i < endIndex) ++i;
                // Remember this point
                int lastCrossing = i;
                // Now move forward looking for a peak
                for (var sample = channel.GetSample(i); sample > 0 && i < endIndex; ++i)
                {
                    if (sample > peakValue)
                    {
                        // It's a new high
                        peakValue = sample;
                        result = lastCrossing;
                        shortestDistance = i - lastCrossing;
                    }
                    // ReSharper disable once CompareOfFloatsByEqualityOperator
                    else if (sample == peakValue && (i - lastCrossing) < shortestDistance)
                    {
                        // It's equal to the best peak but closer to the crossing point
                        result = lastCrossing;
                        shortestDistance = i - lastCrossing;
                    }

                    sample = channel.GetSample(i);
                }
            }

            return result;
        }
    }
}

================================================
FILE: LibSidWiz/Triggers/RisingEdgeTrigger.cs
================================================
namespace LibSidWiz.Triggers
{
    /// <summary>
    /// Trigger that finds the rising edge of the wave.
    /// This is normally fine for simple waveforms but it can fall down when it sees
    /// waves which cross the centre point more than once.
    /// It also only finds the first rising edge in the sample range, rather than the
    /// one nearest the centre.
    /// </summary>
    internal class RisingEdgeTrigger : ITriggerAlgorithm
    {
        public int GetTriggerPoint(Channel channel, int startIndex, int endIndex, int previousIndex)
        {
            // We step through the sample and select the first negative -> positive transition
            int result = startIndex;
            while (channel.GetSample(result) > 0 && result < endIndex) ++result;
            while (channel.GetSample(result) <= 0 && result < endIndex) ++result;
            if (result == endIndex)
            {
                // Failed to find anything
                result = -1;
            }

            return result;
        }
    }
}

================================================
FILE: LibSidWiz/Triggers/WidestWaveTrigger.cs
================================================
namespace LibSidWiz.Triggers
{
    /// <summary>
    /// Finds the widest positive+negative wave in the range
    /// This can get confused by volume changes on SN76489 noise, which it will perceive as a wide wave
    /// </summary>
    // ReSharper disable once UnusedType.Global
    internal class WidestWaveTrigger : ITriggerAlgorithm
    {
        public int GetTriggerPoint(Channel channel, int startIndex, int endIndex, int previousIndex)
        {
            int bestOffset = -1;
            int lastCrossingPoint = endIndex;
            float previousSample = channel.GetSample(startIndex);
            int bestLength = 0;

            // For each sample...
            for (int i = startIndex + 1; i < endIndex; ++i)
            {
                var sample = channel.GetSample(i);
                if (sample > 0 && previousSample <= 0)
                {
                    // Positive edge
                    int length = i - lastCrossingPoint;
                    if (length > bestLength)
                    {
                        bestLength = length;
                        bestOffset = lastCrossingPoint;
                    }

                    lastCrossingPoint = i;
                }

                previousSample = sample;
            }

            return bestOffset;
        }
    }
}

================================================
FILE: LibSidWiz/WaveformRenderer.cs
================================================
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using LibSidWiz.Outputs;
using SkiaSharp;
using SkiaSharp.Views.Desktop;

namespace LibSidWiz
{
    /// <summary>
    /// Class responsible for rendering
    /// </summary>
    public class WaveformRenderer
    {
        private readonly List<Channel> _channels = [];

        public int Width { get; set; }
        public int Height { get; set; }
        public int Columns { get; set; }
        public int SamplingRate { get; set; }
        public int FramesPerSecond { get; set; }
        public Color BackgroundColor { get; set; } = Color.Black;
        public Image BackgroundImage { get; set; }
        public Rectangle RenderingBounds { get; set; }

        public void AddChannel(Channel channel)
        {
            _channels.Add(channel);
        }

        public void Render(IList<IGraphicsOutput> outputs, int numThreads, bool verboseLogging)
        {
            int numFrames = (int)(_channels.Max(c => c.SampleCount) * FramesPerSecond / SamplingRate);
            var length = TimeSpan.FromSeconds((double)numFrames / FramesPerSecond);

            int frameIndex = 0;
            Render(0, numFrames, (bm, rawData) =>
            {
                double fractionComplete = (double) ++frameIndex / numFrames;
                foreach (var output in outputs)
                {
                    output.Write(bm, rawData, fractionComplete, length);
                }
            }, numThreads >= 1 ? numThreads : Environment.ProcessorCount);
        }

        /// <summary>
        /// Renders a range of frames into the given destination image, calling back the handler for each one
        /// </summary>
        private void Render(int startFrame, int endFrame, Action<SKImage, byte[]> onFrame, int numThreads)
        {
            // Default rendering bounds if not set
            var renderingBounds = RenderingBounds;
            if (renderingBounds.Width == 0 || renderingBounds.Height == 0)
            {
                renderingBounds = new Rectangle(0, 0, Width, Height);
            }

            // Compute channel bounds
            var numRows = _channels.Count / Columns + (_channels.Count % Columns == 0 ? 0 : 1);
            for (int i = 0; i < _channels.Count; ++i)
            {
                int ChannelX(int column1) => column1 * renderingBounds.Width / Columns + renderingBounds.Left;
                int ChannelY(int row1) => row1 * renderingBounds.Height / numRows + renderingBounds.Top;

                var channel = _channels[i];
                var column = i % Columns;
                var row = i / Columns;
                // Compute sizes as difference to next one to avoid off-by-one errors
                var x = ChannelX(column);
                var y = ChannelY(row);
                channel.Bounds = new Rectangle(x, y, ChannelX(column + 1) - x, ChannelY(row + 1) - y);
            }

            // We generate our "base image"
            var templateData = new byte[Width * Height * 4];
            GCHandle pinnedArray = GCHandle.Alloc(templateData, GCHandleType.Pinned);
            try
            {
                using var templateImage = new Bitmap(Width, Height, Width * 4, PixelFormat.Format32bppPArgb, pinnedArray.AddrOfPinnedObject());
                GenerateTemplate(templateImage);

                // We want to do a few things in parallel:
                // 1. For each frame, for each channel, get the previous frame trigger point
                // 2. Find the next trigger point
                // 3. When a frame has all the channels' trigger points, draw it
                // 4. Call the callback in strict frame order
                // #3 is the slowest part, but we'd like to somewhat parallelize parts 1-2 as well.
                // So we do that in a parallel for, in a task, emitting into a queue...
                var queue = new BlockingCollection<FrameInfo>(numThreads);
                // Initialise the "previous trigger points"
                var frameSamples = SamplingRate / FramesPerSecond;
                var triggerPoints = new int[_channels.Count];
                for (int channelIndex = 0; channelIndex < _channels.Count; ++channelIndex)
                {
                    triggerPoints[channelIndex] =
                        (int)((long)startFrame * SamplingRate / FramesPerSecond) - frameSamples;
                }

                Task.Factory.StartNew(() =>
                {
                    for (var frameIndex = startFrame; frameIndex <= endFrame; ++frameIndex)
                    {
                        var info = GetFrameInfo(frameIndex, frameSamples, triggerPoints);

                        // Add to the queue (may block)
                        queue.Add(info);
                    }

                    // Signal that this phase is done
                    queue.CompleteAdding();
                });

                // Then we want to make a bunch of objects to consume from the queue, as it is filled up, in parallel.
                // These are tricky because they will want to emit bitmaps, which need to be passed to the renderers in order.
                // To do that, a simple FIFO is not enough; we want it to also be ordered by frame index and only consume in order.
                var renderedFrames = new ConcurrentDictionary<int, FrameInfo>();
                var frameReadySignal = new AutoResetEvent(false);
                // Then we kick off some parallel threads to do the rendering, consuming from queue and emitting to renderedFrames indexed by frame index
                foreach (var _ in Enumerable.Range(0, numThreads))
                {
                    Task.Factory.StartNew(() =>
                    {
                        // We make a thread-local image to render into
                        // This is the raw data buffer we use to store the generated image.
                        // We need it in this form, so we can pass it to FFMPEG.
                        var rawData = new byte[Width * Height * 4];
                        // We also need to "pin" it so the bitmap can be based on it.
                        var innerPinnedArray = GCHandle.Alloc(rawData, GCHandleType.Pinned);

                        // Prepare the pens and brushes we will use
                        var linePaints = _channels.Select(c => c.LineColor == Color.Transparent || c.LineWidth <= 0
                            ? null
                            : new SKPaint {
                                Color = new SKColor(c.LineColor.R, c.LineColor.G, c.LineColor.B, c.LineColor.A),
                                StrokeWidth = c.LineWidth,
                                IsAntialias = c.SmoothLines,
                                Style = SKPaintStyle.Stroke,
                                StrokeMiter = c.LineWidth,
                                StrokeJoin = SKStrokeJoin.Bevel
                            }).ToList();
                        var fillPaints = _channels.Select(c => c.FillColor == Color.Transparent
                            ? null
                            : new SKPaint {
                                Color = new SKColor(c.FillColor.R, c.FillColor.G, c.FillColor.B, c.FillColor.A),
                                IsAntialias = c.SmoothLines,
                                Style = SKPaintStyle.Fill
                            }).ToList();

                        try
                        {
                            using var pixmap = new SKPixmap(new SKImageInfo(Width, Height, SKColorType.Bgra8888, SKAlphaType.Opaque), innerPinnedArray.AddrOfPinnedObject());
                            using var image = SKImage.FromPixels(pixmap);
                            using var surface = SKSurface.Create(pixmap.Info, innerPinnedArray.AddrOfPinnedObject());

                            // Prepare buffers to hold the line coordinates
                            var path = new SKPath();
                            var fillPath = new SKPath();

                            while (!queue.IsCompleted)
                            {
                                // Get a frame to work on. This may block.
                                var frame = queue.Take();
                                var g = surface.Canvas;

                                // Copy from the template
                                Buffer.BlockCopy(templateData, 0, rawData, 0, templateData.Length);

                                // For each channel...
                                for (var channelIndex = 0; channelIndex < _channels.Count; ++channelIndex)
                                {
                                    var channel = _channels[channelIndex];
                                    if (channel.IsEmpty) continue;

                                    if (!string.IsNullOrEmpty(channel.ErrorMessage))
                                        g.DrawText(
                                            channel.ErrorMessage, 
                                            new SKPoint(channel.Bounds.Left + channel.Bounds.Width / 2.0f, channel.Bounds.Top + channel.Bounds.Height / 2.0f), 
                                            new SKPaint
                                            {
                                                Typeface = SKTypeface.Default,
                                                Color = SKColors.Red,
                                                TextAlign = SKTextAlign.Center,
                                            });
                                    else if (channel.Loading)
                                        g.DrawText(
                                            "Loading data...",
                                            new SKPoint(channel.Bounds.Left + channel.Bounds.Width / 2.0f, channel.Bounds.Top + channel.Bounds.Height / 2.0f), 
                                            new SKPaint
                                            {
                                                Typeface = SKTypeface.Default,
                                                Color = SKColors.Green,
                                                TextAlign = SKTextAlign.Center,
                                            });
                                    else if (channel.IsSilent && !channel.RenderIfSilent)
                                        g.DrawText(
                                            "This channel is silent",
                                            new SKPoint(channel.Bounds.Left + channel.Bounds.Width / 2.0f, channel.Bounds.Top + channel.Bounds.Height / 2.0f), 
                                            new SKPaint
                                            {
                                                Typeface = SKTypeface.Default,
                                                Color = SKColors.Yellow,
                                                TextAlign = SKTextAlign.Center,
                                            });
                                    else
                                        // ReSharper disable once AccessToDisposedClosure
                                        RenderWave(g, channel, frame.ChannelTriggerPoints[channelIndex],
                                            linePaints[channelIndex], fillPaints[channelIndex], path, fillPath, channel.FillBase);
                                }

                                // We "lend" the data to the frame info temporarily
                                frame.Bitmap = image;
                                frame.RawData = rawData;
                                renderedFrames.TryAdd(frame.FrameIndex, frame);
                                // Signal the consuming thread
                                frameReadySignal.Set();
                                // Wait for it to finish consuming
                                frame.BitmapConsumed.WaitOne();
                            }
                        }
                        finally
                        {
                            innerPinnedArray.Free();
                            foreach (var paint in linePaints.Concat(fillPaints))
                            {
                                paint?.Dispose();
                            }
                        }
                    }, TaskCreationOptions.LongRunning);
                }

                // Finally, we consume the queue
                for (var frameIndex = startFrame; frameIndex < endFrame; frameIndex++)
                {
                    FrameInfo frame;
                    while (!renderedFrames.TryGetValue(frameIndex, out frame))
                    {
                        // Wait for the dictionary to have this index
                        frameReadySignal.WaitOne();
                    }

                    // Emit the frame
                    onFrame(frame.Bitmap, frame.RawData);
                    frame.BitmapConsumed.Set();
                    renderedFrames.TryRemove(frameIndex, out _);
                }
            }
            finally
            {
                pinnedArray.Free();
            }
        }

        private FrameInfo GetFrameInfo(int frameIndex, int frameSamples, int[] triggerPoints)
        {
            // Compute the start of the sample window
            int frameIndexSamples = (int)((long)frameIndex * SamplingRate / FramesPerSecond);
            var info = new FrameInfo
            {
                FrameIndex = frameIndex,
                ChannelTriggerPoints = Enumerable.Repeat(frameIndexSamples, _channels.Count).ToList()
            };
            // For each channel...
            for (int channelIndex = 0; channelIndex < _channels.Count; ++channelIndex)
            {
                var channel = _channels[channelIndex];
                if (channel.IsEmpty ||
                    !string.IsNullOrEmpty(channel.ErrorMessage) ||
                    channel.Loading ||
                    channel.IsSilent)
                {
                    // No trigger to find
                    continue;
                }

                // Compute the "trigger point". This will be the centre of our rendering.
                var triggerPoint = channel.GetTriggerPoint(frameIndexSamples, frameSamples,
                    triggerPoints[channelIndex]);
                info.ChannelTriggerPoints[channelIndex] = triggerPoint;
                triggerPoints[channelIndex] = triggerPoint;
            }

            return info;
        }

        private class FrameInfo
        {
            public int FrameIndex { get; set; }
            public List<int> ChannelTriggerPoints { get; set; }
            public SKImage Bitmap { get; set; }
            public byte[] RawData { get; set; }
            public AutoResetEvent BitmapConsumed { get; } = new(false);
        }

        private void GenerateTemplate(Image template)
        {
            // Draw it
            using var g = Graphics.FromImage(template);
            g.SmoothingMode = SmoothingMode.HighQuality;
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;

            if (BackgroundImage != null)
            {
                // Fill with the background image
                using var attribute = new ImageAttributes();
                attribute.SetWrapMode(WrapMode.TileFlipXY);
                g.DrawImage(
                    BackgroundImage,
                    new Rectangle(0, 0, Width, Height),
                    0,
                    0,
                    BackgroundImage.Width,
                    BackgroundImage.Height,
                    GraphicsUnit.Pixel,
                    attribute);
            }
            else
            {
                // Fill background
                using var brush = new SolidBrush(BackgroundColor);
                g.FillRectangle(brush, -1, -1, Width + 1, Height + 1);
            }

            foreach (var channel in _channels)
            {
                if (channel.BackgroundColor != Color.Transparent)
                {
                    using var b = new SolidBrush(channel.BackgroundColor);
                    g.FillRectangle(b, channel.Bounds);
                }

                if (channel.ZeroLineColor != Color.Transparent && channel.ZeroLineWidth > 0)
                {
                    using var pen = new Pen(channel.ZeroLineColor, channel.ZeroLineWidth);
                    // Draw the zero line
                    g.DrawLine(
                        pen,
                        channel.Bounds.Left,
                        channel.Bounds.Top + channel.Bounds.Height / 2,
                        channel.Bounds.Right,
                        channel.Bounds.Top + channel.Bounds.Height / 2);
                }

                if (channel.BorderWidth > 0 && channel.BorderColor != Color.Transparent)
                {
                    using var pen = new Pen(channel.BorderColor, channel.BorderWidth);
                    if (channel.BorderEdges)
                    {
                        // We want all edges to show equally.
                        // To achieve this, we need to artificially pull the edges in 1px on the right and bottom.
                        g.DrawRectangle(
                            pen,
                            channel.Bounds.Left,
                            channel.Bounds.Top,
                            channel.Bounds.Width - (channel.Bounds.Right == RenderingBounds.Right ? 1 : 0),
                            channel.Bounds.Height -
                            (channel.Bounds.Bottom == RenderingBounds.Bottom ? 1 : 0));
                    }
                    else
                    {
                        // We want to draw all lines which are not on the rendering bounds
                        if (channel.Bounds.Left != RenderingBounds.Left)
                        {
                            g.DrawLine(pen, channel.Bounds.Left, channel.Bounds.Top, channel.Bounds.Left,
                                channel.Bounds.Bottom);
                        }

                        if (channel.Bounds.Top != RenderingBounds.Top)
                        {
                            g.DrawLine(pen, channel.Bounds.Left, channel.Bounds.Top, channel.Bounds.Right,
                                channel.Bounds.Top);
                        }

                        if (channel.Bounds.Right != RenderingBounds.Right)
                        {
                            g.DrawLine(pen, channel.Bounds.Right, channel.Bounds.Top, channel.Bounds.Right,
                                channel.Bounds.Bottom);
                        }

                        if (channel.Bounds.Bottom != RenderingBounds.Bottom)
                        {
                            g.DrawLine(pen, channel.Bounds.Left, channel.Bounds.Bottom,
                                channel.Bounds.Right, channel.Bounds.Bottom);
                        }
                    }
                }

                if (channel.LabelFont != null && channel.LabelColor != Color.Transparent)
                {
                    g.TextRenderingHint = TextRenderingHint.AntiAlias;
                    using var brush = new SolidBrush(channel.LabelColor);
                    var stringFormat = new StringFormat();
                    var layoutRectangle = new RectangleF(
                        channel.Bounds.Left + channel.LabelMargins.Left,
                        channel.Bounds.Top + channel.LabelMargins.Top,
                        channel.Bounds.Width - channel.LabelMargins.Left - channel.LabelMargins.Right,
                        channel.Bounds.Height - channel.LabelMargins.Top - channel.LabelMargins.Bottom);
                    switch (channel.LabelAlignment)
                    {
                        case ContentAlignment.TopLeft:
                            stringFormat.Alignment = StringAlignment.Near;
                            stringFormat.LineAlignment = StringAlignment.Near;
                            break;
                        case ContentAlignment.TopCenter:
                            stringFormat.Alignment = StringAlignment.Center;
                            stringFormat.LineAlignment = StringAlignment.Near;
                            break;
                        case ContentAlignment.TopRight:
                            stringFormat.Alignment = StringAlignment.Far;
                            stringFormat.LineAlignment = StringAlignment.Near;
                            break;
                        case ContentAlignment.MiddleLeft:
                            stringFormat.Alignment = StringAlignment.Near;
                            stringFormat.LineAlignment = StringAlignment.Center;
                            break;
                        case ContentAlignment.MiddleCenter:
                            stringFormat.Alignment = StringAlignment.Center;
                            stringFormat.LineAlignment = StringAlignment.Center;
                            break;
                        case ContentAlignment.MiddleRight:
                            stringFormat.Alignment = StringAlignment.Far;
                            stringFormat.LineAlignment = StringAlignment.Center;
                            break;
                        case ContentAlignment.BottomLeft:
                            stringFormat.Alignment = StringAlignment.Near;
                            stringFormat.LineAlignment = StringAlignment.Far;
                            break;
                        case ContentAlignment.BottomCenter:
                            stringFormat.Alignment = StringAlignment.Center;
                            stringFormat.LineAlignment = StringAlignment.Far;
                            break;
                        case ContentAlignment.BottomRight:
                            stringFormat.Alignment = StringAlignment.Far;
                            stringFormat.LineAlignment = StringAlignment.Far;
                            break;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }

                    g.DrawString(channel.Label, channel.LabelFont, brush, layoutRectangle, stringFormat);
                }
            }
        }

        private static void RenderWave(SKCanvas g, Channel channel, int triggerPoint, SKPaint linePaint,
            SKPaint fillPaint, SKPath path, SKPath fillPath, double fillBase)
        {
            // And the initial sample index
            var leftmostSampleIndex = triggerPoint - channel.ViewWidthInSamples / 2;

            float xOffset = channel.Bounds.Left;
            float xScale = (float) channel.Bounds.Width / channel.ViewWidthInSamples;
            float yOffset = channel.Bounds.Top + channel.Bounds.Height * 0.5f;
            float yScale = -channel.Bounds.Height * 0.5f;
            path.Rewind();
            for (var i = 0; i < channel.ViewWidthInSamples; ++i)
            {
                var sampleValue = channel.GetSample(leftmostSampleIndex + i, false);
                var x = xOffset + i * xScale;
                var y = yOffset + sampleValue * yScale;
                if (channel.Clip)
                {
                    y = Math.Min(Math.Max(y, channel.Bounds.Top), channel.Bounds.Bottom);
                }
                if (i == 0)
                {
                    path.MoveTo(x, y);
                }
                else
                {
                    path.LineTo(x, y);
                }
            }

            // Then draw them all in one go...
            // Draw the fill "under" the line
            if (fillPaint != null)
            {
                // We need to add points to complete the path
                // We compute the Y position of this line. -0.5 scales -1..1 to bottom..top.
                var baseY = (float)(yOffset + channel.Bounds.Height * -0.5 * fillBase);
                fillPath.Rewind();
                fillPath.AddPath(path);
                fillPath.LineTo(channel.Bounds.Right, baseY);
                fillPath.LineTo(channel.Bounds.Left, baseY);
                g.DrawPath(fillPath, fillPaint);
            }

            if (linePaint != null)
            {
                g.DrawPath(path, linePaint);
            }
        }

        /// <summary>
        /// Version for rendering a single frame for previewing
        /// </summary>
        public Bitmap RenderFrame(float position = 0)
        {
            var frameIndex = _channels.Count > 0
                ? (int) (position * _channels.Max(c => c.SampleCount) * FramesPerSecond / SamplingRate)
                : 0;
            Bitmap bitmap = null;
            Render(frameIndex, frameIndex + 1, (bm, _) =>
            {
                bitmap = bm.ToBitmap();
            }, 1);
            return bitmap;
        }
    }
}


================================================
FILE: LibVgm/Gd3Tag.cs
================================================
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace LibVgm
{
    public class Gd3Tag
    {
        public struct MultiLanguageTag
        {
            public string English { get; set; }
            public string Japanese { get; set; }

            public readonly override string ToString()
            {
                return English.Length > 0 ? Japanese.Length > 0 ? $"{English} ({Japanese})" : $"{English}" : "";
            }
        }

        // ReSharper disable MemberCanBePrivate.Global
        public string Ident { get; private set; }
        public decimal Version { get; private set; }
        
        public MultiLanguageTag Title { get; set; }
        public MultiLanguageTag Game { get; set; }
        public MultiLanguageTag System { get; set; }
        public MultiLanguageTag Composer { get; set; }
        public string Date { get; set; }
        public string Ripper { get; set; }
        public string Notes { get; set; }
        // ReSharper restore MemberCanBePrivate.Global

        public static Gd3Tag LoadFromVgm(string filename)
        {
            // Open the stream
            using var s = new OptionalGzipStream(filename);
            using var r = new BinaryReader(s, Encoding.ASCII);
            r.ReadBytes(0x14);
            var offset = r.ReadUInt32() + 0x14;
            if (offset == 0)
            {
                // No tag
                return null;
            }

            if (offset > s.Length - 8 - 11*2)
            {
                throw new InvalidDataException("Not enough room in file for GD3 offset");
            }

            var result = new Gd3Tag();
            result.Parse(s, offset);
            return result;
        }

        public void Parse(Stream s, uint offset)
        {
            var tags = new List<string>();
            using (var r = new BinaryReader(s, Encoding.Unicode, true))
            {
                s.Seek(offset, SeekOrigin.Begin);
                Ident = Encoding.ASCII.GetString(r.ReadBytes(4));
                if (Ident != "Gd3 ")
                {
                    throw new InvalidDataException("GD3 header not found");
                }

                var version = r.ReadUInt32();
                // BCD to integer
                int scaled = 0;
                int factor = 1;
                for (int i = 0; i < 8; ++i)
                {
                    var digit = (int) version & 0xf;
                    scaled += digit * factor;
                    version >>= 4;
                    factor *= 10;
                }

                Version = (decimal) scaled / 100;

                if (Version >= 2.00m)
                {
                    throw new Exception($"GD3 version {Version} not supported");
                }

                var length = r.ReadUInt32();
                if (s.Length - s.Position > length)
                {
                    throw new Exception("File not big enough for GD3 data");
                }

                // We read out 11 UCS-2 strings
                var str = "";
                for (int i = 0; i < 11; ++i)
                {
                    for (;;)
                    {
                        var c = r.ReadChar();
                        if (c == '\0')
                        {
                            tags.Add(str);
                            str = "";
                            break;
                        }

                        str += c;
                    }
                }
            }

            // We then put them into our properties
            Title = new MultiLanguageTag {English = tags[0], Japanese = tags[1]};
            Game = new MultiLanguageTag {English = tags[2], Japanese = tags[3]};
            System = new MultiLanguageTag {English = tags[4], Japanese = tags[5]};
            Composer = new MultiLanguageTag {English = tags[6], Japanese = tags[7]};
            Date = tags[8];
            Ripper = tags[9];
            Notes = tags[10];
        }

        public override string ToString()
        {
            var title = Title.ToString();
            var game = Game.ToString();
            var system = System.English;
            var composer = Composer.ToString();
            
            var sb = new StringBuilder();

            // Track Title - Game - System (Date)
            // Composer
            // Ripped by Ripper

            if (title.Length > 0) sb.Append(title);
            if (game.Length > 0) sb.Append($" – {game}");
            if (system.Length > 0) sb.Append($" – {system}");
            if (Date.Length > 0) sb.Append($" ({Date})");
            if (composer.Length > 0)
            {
                sb.AppendLine();
                sb.Append(composer);
            }
            if (Ripper.Length > 0)
            {
                sb.AppendLine();
                sb.Append($"Ripped by {Ripper}");
            }

            return sb.ToString();
        }
    }
}

================================================
FILE: LibVgm/LibVgm.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <LangVersion>latest</LangVersion>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
  </PropertyGroup>
</Project>

================================================
FILE: LibVgm/OptionalGzipStream.cs
================================================
using System.IO;
using System.IO.Compression;

namespace LibVgm
{
    /// <summary>
    /// Stream class which transparently supports GZipped or uncompressed files
    /// </summary>
    internal class OptionalGzipStream : Stream
    {
        private readonly Stream _stream;

        public OptionalGzipStream(string filename)
        {
            var fileStream = new FileStream(filename, FileMode.Open);
            // Check if it's GZipped
            bool needGzip = fileStream.ReadByte() == 0x1f && fileStream.ReadByte() == 0x8b;
            fileStream.Seek(0, SeekOrigin.Begin);
            if (needGzip)
            {
                using var gZipStream = new GZipStream(fileStream, CompressionMode.Decompress);
                _stream = new MemoryStream();
                gZipStream.CopyTo(_stream);
                _stream.Seek(0, SeekOrigin.Begin);
            }
            else
            {
                _stream = fileStream;
            }
        }

        public override void Flush() => _stream.Flush();
        public override long Seek(long offset, SeekOrigin origin) => _stream.Seek(offset, origin);
        public override void SetLength(long value) => _stream.SetLength(value);
        public override int Read(byte[] buffer, int offset, int count) => _stream.Read(buffer, offset, count);
        public override void Write(byte[] buffer, int offset, int count) => _stream.Write(buffer, offset, count);

        public override bool CanRead => _stream.CanRead;
        public override bool CanSeek => _stream.CanSeek;
        public override bool CanWrite => _stream.CanWrite;
        public override long Length => _stream.Length;
        public override long Position
        {
            get => _stream.Position;
            set => _stream.Position = value;
        }

        protected override void Dispose(bool disposing)
        {
            _stream?.Dispose();
            base.Dispose(disposing);
        }
    }
}


================================================
FILE: LibVgm/VgmFile.cs
================================================
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace LibVgm
{
    public class VgmFile: IDisposable
    {
        // It's painful to seek in GZipped streams, so we don't bother...
        private readonly MemoryStream _stream;

        // ReSharper disable MemberCanBePrivate.Global
        // ReSharper disable UnusedAutoPropertyAccessor.Global
        // ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
        // ReSharper disable MemberCanBeProtected.Global
        public VgmHeader Header { get; }
        public Gd3Tag Gd3Tag { get; }

        public class VgmHeader
        {
            public string Ident { get; set; }
            public uint EndOfFileOffset { get; set; }
            public decimal Version { get; set; }
            public uint Sn76489Clock { get; set; }
            public uint Ym2413Clock { get; set; }
            public uint Gd3Offset { get; set; }
            public uint TotalSamples { get; set; }
            public uint LoopOffset { get; set; }
            public uint LoopSamples { get; set; }
            public uint Rate { get; set; }
            public uint SnFeedback { get; set; }
            public uint SnWidth { get; set; }
            public uint SnFlag { get; set; }
            public uint Ym2612Clock { get; set; }
            public uint Ym2151Clock { get; set; }
            public uint DataOffset { get; set; } = 0x40;
            public uint SegaPcmClock { get; set; }
            public uint SpcmInterface { get; set; }
            public uint Rf5C68Clock { get; set; }
            public uint Ym2203Clock { get; set; }
            public uint Ym2608Clock { get; set; }
            public uint Ym2610BClock { get; set; }
            public uint Ym3812Clock { get; set; }
            public uint Ym3526Clock { get; set; }
            public uint Y8950Clock { get; set; }
            public uint Ymf262Clock { get; set; }
            public uint Ymf278BClock { get; set; }
            public uint Ymf271Clock { get; set; }
            public uint Ymz280BClock { get; set; }
            public uint Rf5C164Clock { get; set; }
            public uint PwmClock { get; set; }
            public uint Ay8910Clock { get; set; }
            public uint AyType { get; set; }
            public uint AyFlags { get; set; }
            public uint VolumeModifier { get; set; }
            public uint LoopBase { get; set; }
            public uint LoopModifier { get; set; }
            public uint GbDmgClock { get; set; }
            public uint NesApuClock { get; set; }
            public uint MultiPcmClock { get; set; }
            public uint Upd7759Clock { get; set; }
            public uint Okim6258Clock { get; set; }
            public uint Okim6258Flags { get; set; }
            public uint K054539Flags { get; set; }
            public uint C140ChipType { get; set; }
            public uint Okim6295Clock { get; set; }
            public uint K051649Clock { get; set; }
            public uint K054539Clock { get; set; }
            public uint HuC6280Clock { get; set; }
            public uint C140Clock { get; set; }
            public uint K053260Clock { get; set; }
            public uint PokeyClock { get; set; }
            public uint QSoundClock { get; set; }
            public uint ScspClock { get; set; }
            public uint ExtraHeaderOffset { get; set; }
            public uint WonderSwanClock { get; set; }
            public uint VsuClock { get; set; }
            public uint Saa1099Clock { get; set; }
            public uint Es5503Clock { get; set; }
            public uint Es5506Clock { get; set; }
            public uint Es5503Channels { get; set; }
            public uint Es5506Channels { get; set; }
            public uint X1010Clock { get; set; }
            public uint C352Clock { get; set; }
            public uint Ga20Clock { get; set; }

            internal VgmHeader()
            {
                Ident = "Vgm ";
                Version = 1.10m;
            }

            internal void Parse(Stream s)
            {
                using var r = new BinaryReader(s, Encoding.ASCII, true);
                
                // VGM 1.00
                Ident = string.Concat(r.ReadChars(4));
                EndOfFileOffset = r.ReadUInt32();
                if (EndOfFileOffset == 0)
                {
                    EndOfFileOffset = (uint) s.Length;
                }
                else
                {
                    EndOfFileOffset += 4; // Make absolute
                }
                var version = r.ReadUInt32();
                // BCD to integer
                int scaled = 0;
                int factor = 1;
                for (int i = 0; i < 8; ++i)
                {
                    var digit = (int) version & 0xf;
                    scaled += digit * factor;
                    version >>= 4;
                    factor *= 10;
                }

                Version = (decimal) scaled / 100;
                Sn76489Clock = r.ReadUInt32();
                Ym2413Clock = r.ReadUInt32();
                Gd3Offset = r.ReadUInt32();
                if (Gd3Offset != 0)
                {
                    Gd3Offset += 0x14; // Make absolute
                }
                TotalSamples = r.ReadUInt32();
                LoopOffset = r.ReadUInt32();
                if (LoopOffset > 0)
                {
                    LoopOffset += 0x1c; // Make absolute
                }
                LoopSamples = r.ReadUInt32();
                if (Version > 1.01m)
                {
                    Rate = r.ReadUInt32();
                }

                // VGM 1.10
                if (Version > 1.10m)
                {
                    SnFeedback = r.ReadUInt16();
                    SnWidth = r.ReadByte();
                    if (Version > 1.51m)
                    {
                        SnFlag = r.ReadByte();
                    }
                    else
                    {
                        r.ReadByte();
                    }

                    Ym2612Clock = r.ReadUInt32();
                    Ym2151Clock = r.ReadUInt32();

                    if (Version > 1.50m)
                    {
                        DataOffset = r.ReadUInt32();
                        if (DataOffset == 0)
                        {
                            DataOffset = 0x40; // Assume default
                        }
                        else
                        {
                            DataOffset += 0x34; // Make absolute
                        }

                        if (Version > 1.51m)
                        {
                            SegaPcmClock = r.ReadUInt32();
                            SpcmInterface = r.ReadUInt32();
                            Rf5C68Clock = r.ReadUInt32();
                            Ym2203Clock = r.ReadUInt32();
                            Ym2608Clock = r.ReadUInt32();
                            Ym2610BClock = r.ReadUInt32();
                            Ym3812Clock = r.ReadUInt32();
                            Ym3526Clock = r.ReadUInt32();
                            Y8950Clock = r.ReadUInt32();
                            Ymf262Clock = r.ReadUInt32();
                            Ymf278BClock = r.ReadUInt32();
                            Ymf271Clock = r.ReadUInt32();
                            Ymz280BClock = r.ReadUInt32();
                            Rf5C164Clock = r.ReadUInt32();
                            PwmClock = r.ReadUInt32();
                            Ay8910Clock = r.ReadUInt32();
                            var n = r.ReadUInt32();
                            AyType = n >> 24;
                            AyFlags = n & 0xffffff;

                            if (Version > 1.60m)
                            {
                                VolumeModifier = r.ReadByte();
                                r.ReadByte();
                                LoopBase = r.ReadByte();
                            }
                            else
                            {
                                r.ReadBytes(3);
                            }

                            LoopModifier = r.ReadByte();
                            if (Version > 1.61m)
                            {
                                GbDmgClock = r.ReadUInt32();
                                NesApuClock = r.ReadUInt32();
                                MultiPcmClock = r.ReadUInt32();
                                Upd7759Clock = r.ReadUInt32();
                                Okim6258Clock = r.ReadUInt32();
                                Okim6258Flags = r.ReadByte();
                                K054539Flags = r.ReadByte();
                                C140ChipType = r.ReadByte();
                                r.ReadByte();
                                Okim6295Clock = r.ReadUInt32();
                                K051649Clock = r.ReadUInt32();
                                K054539Clock = r.ReadUInt32();
                                HuC6280Clock = r.ReadUInt32();
                                C140Clock = r.ReadUInt32();
                                K053260Clock = r.ReadUInt32();
                                PokeyClock = r.ReadUInt32();
                                QSoundClock = r.ReadUInt32();
                                if (Version > 1.70m)
                                {
                                    if (Version > 1.71m)
                                    {
                                        ScspClock = r.ReadUInt32();
                                    }
                                    else
                                    {
                                        r.ReadUInt32();
                                    }

                                    ExtraHeaderOffset = (uint) r.BaseStream.Position + r.ReadUInt32();

                                    if (Version > 1.71m)
                                    {
                                        WonderSwanClock = r.ReadUInt32();
                                        VsuClock = r.ReadUInt32();
                                        Saa1099Clock = r.ReadUInt32();
                                        Es5503Clock = r.ReadUInt32();
                                        Es5506Clock = r.ReadUInt32();
                                        Es5503Channels = r.ReadUInt16();
                                        Es5506Channels = r.ReadByte();
                                        r.ReadByte();
                                        X1010Clock = r.ReadUInt32();
                                        C352Clock = r.ReadUInt32();
                                        Ga20Clock = r.ReadUInt32();
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        public interface ICommand
        {
        }

        public class GenericCommand: ICommand
        {
            public byte[] Values { get; set; }

            public GenericCommand(BinaryReader reader, int count)
            {
                Values = reader.ReadBytes(count);
            }

            public override string ToString() => $"Generic command {Values.Length} bytes";
        }

        public class AddressDataCommand: ICommand
        {
            public byte Type { get; }
            public byte Address { get; set; }
            public byte Data { get; set; }

            public AddressDataCommand(BinaryReader reader, byte type)
            {
                Type = type;
                Address = reader.ReadByte();
                Data = reader.ReadByte();
            }

            public override string ToString() => $"Type {Type:X} Address {Address:X} Data {Data:X}";
        }

        public class WaitCommand : ICommand
        {
            public int Ticks { get; set; }

            public WaitCommand(BinaryReader reader, byte type)
            {
                switch (type)
                {
                    case 0x61:
                        Ticks = reader.ReadUInt16();
                        break;
                    case 0x62: 
                        Ticks = 735; 
                        break;
                    case 0x63: 
                        Ticks = 882; 
                        break;
                    case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: 
                    case 0x78: case 0x79: case 0x7a: case 0x7b: case 0x7c: case 0x7d: case 0x7e: case 0x7f:
                        Ticks = (type & 0xf) + 1;
                        break;
                }
            }

            public override string ToString() => $"Wait {Ticks} samples";
        }

        public class SampleWaitCommand : WaitCommand
        {
            public SampleWaitCommand(BinaryReader reader, byte type) : base(reader, type)
            {
                // Sample waits are one less
                --Ticks;
            }
            public override string ToString() => $"Wait {Ticks} samples and play sample";
        }

        public class StopCommand : ICommand
        {
            public override string ToString() => "Stop";
        }

        public class DataBlock : ICommand
        {
            public byte BlockType { get; set; }
            public byte[] Data { get; set; }

            public DataBlock(BinaryReader reader)
            {
                reader.ReadByte(); // Skip the stop command
                BlockType = reader.ReadByte();
                var count = reader.ReadInt32();
                Data = reader.ReadBytes(count);
            }
            public override string ToString() => $"Data block: type {BlockType:X} size {Data}";
        }

        public class PcmRamWrite : ICommand
        {
            public byte ChipType { get; set; }
            public int ReadOffset { get; set; }
            public int Count { get; set; }
            public int WriteOffset { get; set; }

            public PcmRamWrite(BinaryReader reader)
            {
                reader.ReadByte(); // Skip the stop command
                ChipType = reader.ReadByte();
                ReadOffset = reader.ReadUInt16() + reader.ReadByte() << 16; // 24-bit read
                WriteOffset = reader.ReadUInt16() + reader.ReadByte() << 16; // 24-bit read
                Count = reader.ReadUInt16() + reader.ReadByte() << 16; // 24-bit read
                if (Count == 0)
                {
                    Count = 1 << 24;
                }
            }
        }

        public abstract class DacStreamCommand : ICommand
        {
            public byte StreamId { get; set; }

            protected DacStreamCommand(BinaryReader reader)
            {
                StreamId = reader.ReadByte();
            }
        }

        public class DacStreamSetupCommand : DacStreamCommand
        {
            public DacStreamSetupCommand(BinaryReader reader) : base(reader)
            {
                ChipType = reader.ReadByte();
                Port = reader.ReadByte();
                Command = reader.ReadByte();
            }

            public byte Command { get; set; }

            public byte Port { get; set; }

            public byte ChipType { get; set; }
        }

        public class DacStreamDataCommand: DacStreamCommand
        {
            public DacStreamDataCommand(BinaryReader reader) : base(reader)
            {
                BankId = reader.ReadByte();
                StepSize = reader.ReadByte();
                StepBase = reader.ReadByte();
            }

            public byte StepBase { get; set; }

            public byte StepSize { get; set; }

            public byte BankId { get; set; }
        }

        public class DacStreamFrequencyCommand: DacStreamCommand
        {
            public DacStreamFrequencyCommand(BinaryReader reader) : base(reader)
            {
                Frequency = reader.ReadUInt32();
            }

            public uint Frequency { get; set; }
        }

        public class DacStreamStartCommand: DacStreamCommand
        {
            public DacStreamStartCommand(BinaryReader reader) : base(reader)
            {
                Offset = reader.ReadUInt32();
                LengthMode = reader.ReadByte();
                Count = reader.ReadUInt32();
            }

            public uint Count { get; set; }

            public byte LengthMode { get; set; }

            public uint Offset { get; set; }
        }

        public class DacStreamStopCommand : DacStreamCommand
        {
            public DacStreamStopCommand(BinaryReader reader) : base(reader) {}
        }
        public class DacStreamFastStartCommand : DacStreamCommand
        {
            public DacStreamFastStartCommand(BinaryReader reader) : base(reader)
            {
                BlockId = reader.ReadUInt16();
                Flags = reader.ReadByte();
            }

            public byte Flags { get; set; }

            public ushort BlockId { get; set; }
        }
        // ReSharper restore MemberCanBeProtected.Global
        // ReSharper restore UnusedAutoPropertyAccessor.Global
        // ReSharper restore MemberCanBePrivate.Global
        // ReSharper restore AutoPropertyCanBeMadeGetOnly.Global

        // ReSharper disable once UnusedMember.Global
        public VgmFile()
        {
            // Empty file
            _stream = new MemoryStream();
            Header = new VgmHeader();
            Gd3Tag = new Gd3Tag();
        }

        public VgmFile(string filename)
        {
            _stream = new MemoryStream();
            Header = new VgmHeader();
            Gd3Tag = new Gd3Tag();

            LoadFromFile(filename);
        }

        private void LoadFromFile(string filename)
        {
            // We copy all the data into a memory stream to allow seeking
            using (var s = new OptionalGzipStream(filename))
            {
                s.CopyTo(_stream);
                _stream.Seek(0, SeekOrigin.Begin);
            }

            // We parse the header
            Header.Parse(_stream);

            // And the GD3 tag, if present
            if (Header.Gd3Offset != 0)
            {
                Gd3Tag.Parse(_stream, Header.Gd3Offset);
            }

        }

        public IEnumerable<ICommand> Commands()
        {
            // Seek to the start
            _stream.Seek(Header.DataOffset, SeekOrigin.Begin);

            using var reader = new BinaryReader(_stream, Encoding.Default, true);
            while (_stream.Position < _stream.Length)
            {
                //var b = reader.ReadByte();

                switch (reader.ReadByte())
                {
                    case <= 0x2f:
                        // Unhandled
                        continue;
                    case <= 0x3f:
                        yield return new GenericCommand(reader, 1); // Reserved range
                        break;
                    case <= 0x4e:
                        yield return new GenericCommand(reader, 2); // Reserved range
                        break;
                    case <= 0x50:
                        yield return new GenericCommand(reader, 1); // GG stereo or PSG
                        break;
                    case var b and <= 0x5f:
                        yield return new AddressDataCommand(reader, b); // FM chips
                        break;
                    case 0x60:
                        // Unhandled
                        continue;
                    case var b and <= 0x63:
                        yield return new WaitCommand(reader, b);
                        break;
                    case <= 0x65:
                        // Unhandled
                        continue;
                    case 0x66:
                        yield return new StopCommand();
                        yield break;
                    case 0x67:
                        yield return new DataBlock(reader);
                        break;
                    case 0x68:
                        yield return new PcmRamWrite(reader);
                        break;
                    case < 0x6f:
                        // Unhandled
                        continue;
                    case var b and >= 0x70 and <= 0x7f:
                        yield return new WaitCommand(reader, b);
                        break;
                    case var b and <= 0x8f:
                        yield return new SampleWaitCommand(reader, b);
                        break;
                    case 0x90:
                        yield return new DacStreamSetupCommand(reader);
                        break;
                    case 0x91:
                        yield return new DacStreamDataCommand(reader);
                        break;
                    case 0x92:
                        yield return new DacStreamFrequencyCommand(reader);
                        break;
                    case 0x93:
                        yield return new DacStreamStartCommand(reader);
                        break;
                    case 0x94:
                        yield return new DacStreamStopCommand(reader);
                        break;
                    case 0x95:
                        yield return new DacStreamFastStartCommand(reader);
                        break;
                    case <= 0x9f:
                        // Unhandled
                        continue;
                    case var b and 0xa0:
                        yield return new AddressDataCommand(reader, b);
                        break;
                    case <= 0xaf:
                        yield return new GenericCommand(reader, 2); // Reserved range
                        break;
                    case var b and <= 0xbf:
                        yield return new AddressDataCommand(reader, b);
                        break;
                    case <= 0xdf:
                        yield return new GenericCommand(reader, 3); // Reserved + some allocated
                        break;
                    default:
                        yield return new GenericCommand(reader, 4); // Reserved + some allocated
                        break;
                }
            }
        }
        public void Dispose()
        {
            _stream?.Dispose();
            GC.SuppressFinalize(this);
        }
    }
}

================================================
FILE: README.md
================================================
# SidWizPlus

![](https://github.com/maxim-zhao/SidWizPlus/wiki/logo.png)

This is a program which generates "oscilloscope view" videos from multi-track audio files. It is often used for VGM/chiptune rendering for use on video sharing sites, but can also be used for other multitrack audio files.

[![](http://img.youtube.com/vi/H-Ip9c0yjGk/0.jpg)](http://www.youtube.com/watch?v=H-Ip9c0yjGk "Sonic 3 - Ice Cap Zone - Brad Buxer")
[![](http://img.youtube.com/vi/ITQFs6-1LSg/0.jpg)](http://www.youtube.com/watch?v=ITQFs6-1LSg "Bohemian Rhapsody - Queen")

The primary goals of this project are:

1. Generating videos from VGM packs from [SMS Power!](http://www.smspower.org/Music) - [see them on YouTube](https://www.youtube.com/channel/UCCsvqzh7JjNNheYTplGvhCQ)
2. Producing a base for others to work on the features they want

Get builds from here: https://github.com/maxim-zhao/SidWizPlus/releases/

[![Build status](https://ci.appveyor.com/api/projects/status/vpa5eav7sm1n7ik6?svg=true)](https://ci.appveyor.com/project/maxim-zhao/sidwizplus) 

You can get a compatible build of MultiDumper here: https://github.com/maxim-zhao/multidumper/releases/

## Features added

* Commandline mode
* Replaced renderer with Skia, allowing simpler code and more advanced rendering:
  * Antialiasing
  * Background image
  * Line width control
  * Optional fill
  * Alpha blending
  * Multi-threaded rendering
* Added rendering features from other variants
  * Grid
  * Channel labels
* Integration with [MultiDumper](https://bitbuc
Download .txt
gitextract_65uui726/

├── .editorconfig
├── .gitignore
├── .gitmodules
├── Benchmark.xlsx
├── Directory.Build.props
├── LICENSE
├── LibSidWiz/
│   ├── Channel.cs
│   ├── Extensions.cs
│   ├── LibSidWiz.csproj
│   ├── Mixer.cs
│   ├── MultiDumperWrapper.cs
│   ├── MyColorConverter.cs
│   ├── MyColorEditor.cs
│   ├── Outputs/
│   │   ├── FfmpegOutput.cs
│   │   ├── IGraphicsOutput.cs
│   │   ├── PreviewOutput.cs
│   │   ├── PreviewOutputForm.Designer.cs
│   │   ├── PreviewOutputForm.cs
│   │   └── PreviewOutputForm.resx
│   ├── ProcessWrapper.cs
│   ├── SampleBuffer.cs
│   ├── Triggers/
│   │   ├── AutoCorrelationTrigger.cs
│   │   ├── BiggestPositiveWaveAreaTrigger.cs
│   │   ├── BiggestWaveAreaTrigger.cs
│   │   ├── ITriggerAlgorithm.cs
│   │   ├── MiddleWidest.cs
│   │   ├── NullTrigger.cs
│   │   ├── PeakSpeedTrigger.cs
│   │   ├── RisingEdgeTrigger.cs
│   │   └── WidestWaveTrigger.cs
│   └── WaveformRenderer.cs
├── LibVgm/
│   ├── Gd3Tag.cs
│   ├── LibVgm.csproj
│   ├── OptionalGzipStream.cs
│   └── VgmFile.cs
├── README.md
├── SidWiz/
│   ├── ColorButton.Designer.cs
│   ├── ColorButton.cs
│   ├── HighDpiHelper.cs
│   ├── MultiDumperForm.Designer.cs
│   ├── MultiDumperForm.cs
│   ├── MultiDumperForm.resx
│   ├── Program.cs
│   ├── Properties/
│   │   ├── Resources.Designer.cs
│   │   ├── Resources.resx
│   │   └── app.manifest
│   ├── SidPlayForm.Designer.cs
│   ├── SidPlayForm.cs
│   ├── SidPlayForm.resx
│   ├── SidWizPlusGUI.Designer.cs
│   ├── SidWizPlusGUI.cs
│   ├── SidWizPlusGUI.csproj
│   ├── SidWizPlusGUI.resx
│   └── app.config
├── SidWiz.sln
├── SidWiz.sln.DotSettings
├── SidWizPlus/
│   ├── App.config
│   ├── BackgroundRenderer.cs
│   ├── ImageInfo.cs
│   ├── Program.cs
│   ├── SidWizPlus.csproj
│   └── TextInfo.cs
└── appveyor.yml
Download .txt
SYMBOL INDEX (305 symbols across 41 files)

FILE: LibSidWiz/Channel.cs
  class Channel (line 25) | [SuppressMessage("ReSharper", "UnusedMember.Global")]
    method Channel (line 60) | public Channel(bool autoReloadOnSettingChanged)
    type Sides (line 65) | public enum Sides
    method LoadDataAsync (line 74) | public Task<bool> LoadDataAsync(CancellationToken token = new())
    method GetSample (line 593) | internal float GetSample(int sampleIndex, bool forTrigger = true)
    method GetTriggerPoint (line 599) | internal int GetTriggerPoint(int frameIndexSamples, int frameSamples, ...
    method GuessNameFromMultidumperFilename (line 619) | public static string GuessNameFromMultidumperFilename(string filename)
    class TriggerAlgorithmTypeConverter (line 702) | public class TriggerAlgorithmTypeConverter: StringConverter
      method GetStandardValuesExclusive (line 704) | public override bool GetStandardValuesExclusive(ITypeDescriptorConte...
      method GetStandardValuesSupported (line 709) | public override bool GetStandardValuesSupported(ITypeDescriptorConte...
      method GetStandardValues (line 714) | public override StandardValuesCollection GetStandardValues(ITypeDesc...
      method CanConvertFrom (line 724) | public override bool CanConvertFrom(ITypeDescriptorContext context, ...
      method ConvertFrom (line 729) | public override object ConvertFrom(ITypeDescriptorContext context, C...
    class TriggerAlgorithmJsonConverter (line 747) | public class TriggerAlgorithmJsonConverter: JsonConverter<ITriggerAlgo...
      method WriteJson (line 749) | public override void WriteJson(JsonWriter writer, ITriggerAlgorithm ...
      method ReadJson (line 754) | public override ITriggerAlgorithm ReadJson(JsonReader reader, Type o...
    method Dispose (line 770) | public void Dispose()
    method ToJson (line 780) | public string ToJson()
    method FromJson (line 788) | public void FromJson(string json, bool preserveSource)
    class PreservingContractResolver (line 803) | private class PreservingContractResolver : DefaultContractResolver
      method CreateProperty (line 805) | protected override JsonProperty CreateProperty(MemberInfo member, Me...
    method IsMono (line 818) | public bool IsMono()

FILE: LibSidWiz/Extensions.cs
  class Extensions (line 8) | public static class Extensions
    method OrderByAlphaNumeric (line 10) | public static IOrderedEnumerable<T> OrderByAlphaNumeric<T>(this IEnume...

FILE: LibSidWiz/Mixer.cs
  class Mixer (line 13) | public static class Mixer
    method MixToFile (line 15) | public static void MixToFile(IList<Channel> channels, string filename,...

FILE: LibSidWiz/MultiDumperWrapper.cs
  class MultiDumperWrapper (line 13) | public class MultiDumperWrapper: IDisposable
    method MultiDumperWrapper (line 24) | public MultiDumperWrapper(string multiDumperPath, int samplingRate, in...
    class Song (line 43) | public class Song
      method ToString (line 62) | public override string ToString()
      method GetLength (line 78) | public TimeSpan GetLength()
    method GetSongs (line 100) | public IEnumerable<Song> GetSongs(string filename)
    method GetOutputText (line 175) | private string GetOutputText(string args, bool includeStdErr)
    method Dump (line 194) | public IEnumerable<string> Dump(Song song, Action<double> onProgress)
    method AddArgIfSupported (line 247) | private void AddArgIfSupported(StringBuilder args, string name, object...
    method Dispose (line 259) | public void Dispose()

FILE: LibSidWiz/MyColorConverter.cs
  class MyColorConverter (line 9) | public class MyColorConverter : ColorConverter
    method GetStandardValuesSupported (line 11) | public override bool GetStandardValuesSupported(ITypeDescriptorContext...

FILE: LibSidWiz/MyColorEditor.cs
  class MyColorEditor (line 13) | public class MyColorEditor : UITypeEditor
    method GetEditStyle (line 15) | public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorCont...
    method EditValue (line 20) | public override object EditValue(ITypeDescriptorContext context, IServ...
    method GetPaintValueSupported (line 40) | public override bool GetPaintValueSupported(ITypeDescriptorContext con...
    method PaintValue (line 45) | public override void PaintValue(PaintValueEventArgs e)

FILE: LibSidWiz/Outputs/FfmpegOutput.cs
  class FfmpegOutput (line 9) | public class FfmpegOutput : IGraphicsOutput
    method FfmpegOutput (line 15) | public FfmpegOutput(string pathToExe, string filename, int width, int ...
    method Write (line 74) | public void Write(SKImage _, byte[] data, double __, TimeSpan ___)
    method Dispose (line 107) | public void Dispose()

FILE: LibSidWiz/Outputs/IGraphicsOutput.cs
  type IGraphicsOutput (line 6) | public interface IGraphicsOutput: IDisposable
    method Write (line 8) | void Write(SKImage image, byte[] data, double fractionComplete, TimeSp...

FILE: LibSidWiz/Outputs/PreviewOutput.cs
  class PreviewOutput (line 10) | public class PreviewOutput : IGraphicsOutput
    method PreviewOutput (line 19) | public PreviewOutput(int frameSkip, bool pumpMessageQueue = false)
    method Write (line 29) | public void Write(SKImage image, byte[] data, double fractionComplete,...
    method Dispose (line 78) | public void Dispose()

FILE: LibSidWiz/Outputs/PreviewOutputForm.Designer.cs
  class PreviewOutputForm (line 3) | partial class PreviewOutputForm
    method Dispose (line 14) | protected override void Dispose(bool disposing)
    method InitializeComponent (line 29) | private void InitializeComponent()

FILE: LibSidWiz/Outputs/PreviewOutputForm.cs
  class PreviewOutputForm (line 5) | public partial class PreviewOutputForm : Form
    method PreviewOutputForm (line 8) | public PreviewOutputForm()

FILE: LibSidWiz/ProcessWrapper.cs
  class ProcessWrapper (line 9) | public class ProcessWrapper: IDisposable
    method ProcessWrapper (line 16) | public ProcessWrapper(string filename, string arguments, bool captureS...
    method OnText (line 61) | private void OnText(object sender, DataReceivedEventArgs e)
    method Lines (line 80) | public IEnumerable<string> Lines()
    method WaitForExit (line 110) | public void WaitForExit() => _process.WaitForExit();
    method Dispose (line 112) | public void Dispose()

FILE: LibSidWiz/SampleBuffer.cs
  class SampleBuffer (line 7) | internal class SampleBuffer: IDisposable
    class Chunk (line 12) | private class Chunk
      method TryGet (line 18) | public bool TryGet(long index, out float value)
    method SampleBuffer (line 49) | public SampleBuffer(string filename, Channel.Sides side, bool filter)
    method Dispose (line 82) | public void Dispose()
    method Analyze (line 117) | public void Analyze()
  class HighPassSampleProvider (line 130) | internal class HighPassSampleProvider(ISampleProvider sampleProvider) : ...
    method Read (line 134) | public int Read(float[] buffer, int offset, int count)

FILE: LibSidWiz/Triggers/AutoCorrelationTrigger.cs
  class AutoCorrelationTrigger (line 10) | internal class AutoCorrelationTrigger: ITriggerAlgorithm
    method GetTriggerPoint (line 14) | public int GetTriggerPoint(Channel channel, int startIndex, int endInd...

FILE: LibSidWiz/Triggers/BiggestPositiveWaveAreaTrigger.cs
  class BiggestPositiveWaveAreaTrigger (line 9) | internal class BiggestPositiveWaveAreaTrigger : ITriggerAlgorithm
    method GetTriggerPoint (line 11) | public int GetTriggerPoint(Channel channel, int startIndex, int endInd...

FILE: LibSidWiz/Triggers/BiggestWaveAreaTrigger.cs
  class BiggestWaveAreaTrigger (line 9) | internal class BiggestWaveAreaTrigger : ITriggerAlgorithm
    method GetTriggerPoint (line 11) | public int GetTriggerPoint(Channel channel, int startIndex, int endInd...

FILE: LibSidWiz/Triggers/ITriggerAlgorithm.cs
  type ITriggerAlgorithm (line 3) | public interface ITriggerAlgorithm
    method GetTriggerPoint (line 13) | int GetTriggerPoint(Channel channel, int startIndex, int endIndex, int...

FILE: LibSidWiz/Triggers/MiddleWidest.cs
  class MiddleWidest (line 11) | class MiddleWidest: ITriggerAlgorithm
    method GetTriggerPoint (line 13) | public int GetTriggerPoint(Channel channel, int startIndex, int endInd...

FILE: LibSidWiz/Triggers/NullTrigger.cs
  class NullTrigger (line 7) | internal class NullTrigger: ITriggerAlgorithm
    method GetTriggerPoint (line 9) | public int GetTriggerPoint(Channel channel, int startIndex, int endInd...

FILE: LibSidWiz/Triggers/PeakSpeedTrigger.cs
  class PeakSpeedTrigger (line 13) | public class PeakSpeedTrigger : ITriggerAlgorithm
    method GetTriggerPoint (line 15) | public int GetTriggerPoint(Channel channel, int startIndex, int endInd...

FILE: LibSidWiz/Triggers/RisingEdgeTrigger.cs
  class RisingEdgeTrigger (line 10) | internal class RisingEdgeTrigger : ITriggerAlgorithm
    method GetTriggerPoint (line 12) | public int GetTriggerPoint(Channel channel, int startIndex, int endInd...

FILE: LibSidWiz/Triggers/WidestWaveTrigger.cs
  class WidestWaveTrigger (line 8) | internal class WidestWaveTrigger : ITriggerAlgorithm
    method GetTriggerPoint (line 10) | public int GetTriggerPoint(Channel channel, int startIndex, int endInd...

FILE: LibSidWiz/WaveformRenderer.cs
  class WaveformRenderer (line 21) | public class WaveformRenderer
    method AddChannel (line 34) | public void AddChannel(Channel channel)
    method Render (line 39) | public void Render(IList<IGraphicsOutput> outputs, int numThreads, boo...
    method Render (line 58) | private void Render(int startFrame, int endFrame, Action<SKImage, byte...
    method GetFrameInfo (line 262) | private FrameInfo GetFrameInfo(int frameIndex, int frameSamples, int[]...
    class FrameInfo (line 294) | private class FrameInfo
    method GenerateTemplate (line 303) | private void GenerateTemplate(Image template)
    method RenderWave (line 453) | private static void RenderWave(SKCanvas g, Channel channel, int trigge...
    method RenderFrame (line 506) | public Bitmap RenderFrame(float position = 0)

FILE: LibVgm/Gd3Tag.cs
  class Gd3Tag (line 8) | public class Gd3Tag
    type MultiLanguageTag (line 10) | public struct MultiLanguageTag
      method ToString (line 15) | public readonly override string ToString()
    method LoadFromVgm (line 34) | public static Gd3Tag LoadFromVgm(string filename)
    method Parse (line 57) | public void Parse(Stream s, uint offset)
    method ToString (line 123) | public override string ToString()

FILE: LibVgm/OptionalGzipStream.cs
  class OptionalGzipStream (line 9) | internal class OptionalGzipStream : Stream
    method OptionalGzipStream (line 13) | public OptionalGzipStream(string filename)
    method Flush (line 32) | public override void Flush() => _stream.Flush();
    method Seek (line 33) | public override long Seek(long offset, SeekOrigin origin) => _stream.S...
    method SetLength (line 34) | public override void SetLength(long value) => _stream.SetLength(value);
    method Read (line 35) | public override int Read(byte[] buffer, int offset, int count) => _str...
    method Write (line 36) | public override void Write(byte[] buffer, int offset, int count) => _s...
    method Dispose (line 48) | protected override void Dispose(bool disposing)

FILE: LibVgm/VgmFile.cs
  class VgmFile (line 8) | public class VgmFile: IDisposable
    class VgmHeader (line 20) | public class VgmHeader
      method VgmHeader (line 88) | internal VgmHeader()
      method Parse (line 94) | internal void Parse(Stream s)
    type ICommand (line 258) | public interface ICommand
    class GenericCommand (line 262) | public class GenericCommand: ICommand
      method GenericCommand (line 266) | public GenericCommand(BinaryReader reader, int count)
      method ToString (line 271) | public override string ToString() => $"Generic command {Values.Lengt...
    class AddressDataCommand (line 274) | public class AddressDataCommand: ICommand
      method AddressDataCommand (line 280) | public AddressDataCommand(BinaryReader reader, byte type)
      method ToString (line 287) | public override string ToString() => $"Type {Type:X} Address {Addres...
    class WaitCommand (line 290) | public class WaitCommand : ICommand
      method WaitCommand (line 294) | public WaitCommand(BinaryReader reader, byte type)
      method ToString (line 314) | public override string ToString() => $"Wait {Ticks} samples";
    class SampleWaitCommand (line 317) | public class SampleWaitCommand : WaitCommand
      method SampleWaitCommand (line 319) | public SampleWaitCommand(BinaryReader reader, byte type) : base(read...
      method ToString (line 324) | public override string ToString() => $"Wait {Ticks} samples and play...
    class StopCommand (line 327) | public class StopCommand : ICommand
      method ToString (line 329) | public override string ToString() => "Stop";
    class DataBlock (line 332) | public class DataBlock : ICommand
      method DataBlock (line 337) | public DataBlock(BinaryReader reader)
      method ToString (line 344) | public override string ToString() => $"Data block: type {BlockType:X...
    class PcmRamWrite (line 347) | public class PcmRamWrite : ICommand
      method PcmRamWrite (line 354) | public PcmRamWrite(BinaryReader reader)
    class DacStreamCommand (line 368) | public abstract class DacStreamCommand : ICommand
      method DacStreamCommand (line 372) | protected DacStreamCommand(BinaryReader reader)
    class DacStreamSetupCommand (line 378) | public class DacStreamSetupCommand : DacStreamCommand
      method DacStreamSetupCommand (line 380) | public DacStreamSetupCommand(BinaryReader reader) : base(reader)
    class DacStreamDataCommand (line 394) | public class DacStreamDataCommand: DacStreamCommand
      method DacStreamDataCommand (line 396) | public DacStreamDataCommand(BinaryReader reader) : base(reader)
    class DacStreamFrequencyCommand (line 410) | public class DacStreamFrequencyCommand: DacStreamCommand
      method DacStreamFrequencyCommand (line 412) | public DacStreamFrequencyCommand(BinaryReader reader) : base(reader)
    class DacStreamStartCommand (line 420) | public class DacStreamStartCommand: DacStreamCommand
      method DacStreamStartCommand (line 422) | public DacStreamStartCommand(BinaryReader reader) : base(reader)
    class DacStreamStopCommand (line 436) | public class DacStreamStopCommand : DacStreamCommand
      method DacStreamStopCommand (line 438) | public DacStreamStopCommand(BinaryReader reader) : base(reader) {}
    class DacStreamFastStartCommand (line 440) | public class DacStreamFastStartCommand : DacStreamCommand
      method DacStreamFastStartCommand (line 442) | public DacStreamFastStartCommand(BinaryReader reader) : base(reader)
    method VgmFile (line 458) | public VgmFile()
    method VgmFile (line 466) | public VgmFile(string filename)
    method LoadFromFile (line 475) | private void LoadFromFile(string filename)
    method Commands (line 495) | public IEnumerable<ICommand> Commands()
    method Dispose (line 588) | public void Dispose()

FILE: SidWiz/ColorButton.Designer.cs
  class ColorButton (line 3) | partial class ColorButton
    method Dispose (line 14) | protected override void Dispose(bool disposing)
    method InitializeComponent (line 29) | private void InitializeComponent()

FILE: SidWiz/ColorButton.cs
  class ColorButton (line 12) | public partial class ColorButton : Button
    method ColorButton (line 31) | public ColorButton()
    method ColorButton (line 36) | public ColorButton(IContainer container)
    method OnClick (line 45) | private void OnClick(object sender, EventArgs e)

FILE: SidWiz/HighDpiHelper.cs
  class HighDpiHelper (line 10) | public static class HighDpiHelper
    method AdjustControlImagesDpiScale (line 12) | public static void AdjustControlImagesDpiScale(Control container)
    method AdjustControlImagesDpiScale (line 23) | private static void AdjustControlImagesDpiScale(IEnumerable controls, ...
    method ScaleToolStrip (line 70) | private static void ScaleToolStrip(float dpiScale, ToolStrip toolStrip)
    method CloseToOne (line 79) | private static bool CloseToOne(float dpiScale)
    method GetDpiScale (line 84) | private static Lazy<float> GetDpiScale(Control control)
    method ScaleImage (line 93) | private static Image ScaleImage(Image image, float dpiScale)
    method ScaleSize (line 110) | private static Size ScaleSize(Size size, float scale)

FILE: SidWiz/MultiDumperForm.Designer.cs
  class MultiDumperForm (line 6) | partial class MultiDumperForm
    method Dispose (line 17) | protected override void Dispose(bool disposing)
    method InitializeComponent (line 32) | private void InitializeComponent()

FILE: SidWiz/MultiDumperForm.cs
  class MultiDumperForm (line 10) | public partial class MultiDumperForm : Form
    method MultiDumperForm (line 17) | public MultiDumperForm(string filename, string multiDumperPath, int sa...
    method OkButtonClick (line 24) | private void OkButtonClick(object sender, EventArgs e)
    method SubSongSelectionForm_Load (line 74) | private void SubSongSelectionForm_Load(object sender, EventArgs e)
    method SubsongSelectionForm_Closing (line 111) | private void SubsongSelectionForm_Closing(object sender, EventArgs e)
    method Subsongs_SelectedIndexChanged (line 116) | private void Subsongs_SelectedIndexChanged(object sender, EventArgs e)

FILE: SidWiz/Program.cs
  class Program (line 6) | internal static class Program
    method Main (line 11) | [STAThread]

FILE: SidWiz/Properties/Resources.Designer.cs
  class Resources (line 22) | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resource...
    method Resources (line 31) | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Mic...

FILE: SidWiz/SidPlayForm.Designer.cs
  class SidPlayForm (line 5) | partial class SidPlayForm
    method Dispose (line 16) | protected override void Dispose(bool disposing)
    method InitializeComponent (line 31) | private void InitializeComponent()

FILE: SidWiz/SidPlayForm.cs
  class SidPlayForm (line 12) | public partial class SidPlayForm : Form
    class SidFile (line 14) | public class SidFile
      method SidFile (line 31) | public SidFile(string filename)
    class SidPlayWrapper (line 69) | public class SidPlayWrapper : IDisposable
      method SidPlayWrapper (line 74) | public SidPlayWrapper(string sidPlayPath)
      class Song (line 79) | public class Song
        method ToString (line 84) | public override string ToString()
      method GetSongs (line 90) | public IEnumerable<Song> GetSongs(string filename)
      method Dump (line 103) | public IEnumerable<string> Dump(Song song)
      method Dispose (line 134) | public void Dispose()
    method SidPlayForm (line 149) | public SidPlayForm(string filename, string sidPlayPath)
    method OkButtonClick (line 156) | private void OkButtonClick(object sender, EventArgs e)
    method SubSongSelectionForm_Load (line 190) | private void SubSongSelectionForm_Load(object sender, EventArgs e)
    method SubSongSelectionForm_Closing (line 216) | private void SubSongSelectionForm_Closing(object sender, EventArgs e)

FILE: SidWiz/SidWizPlusGUI.Designer.cs
  class SidWizPlusGui (line 5) | partial class SidWizPlusGui
    method Dispose (line 16) | protected override void Dispose(bool disposing)
    method InitializeComponent (line 31) | private void InitializeComponent()

FILE: SidWiz/SidWizPlusGUI.cs
  class SidWizPlusGui (line 25) | public partial class SidWizPlusGui : Form
    class ProgramSettings (line 27) | [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
    class Settings (line 89) | private class Settings
      class MasterAudioSettings (line 110) | public class MasterAudioSettings
      class EncodeSettings (line 117) | public class EncodeSettings
      class PreviewSettings (line 125) | public class PreviewSettings
      method FromControls (line 131) | public void FromControls(SidWizPlusGui form)
      method ToControls (line 160) | public void ToControls(SidWizPlusGui form)
      method GetBounds (line 187) | public Rectangle GetBounds()
    method SidWizPlusGui (line 211) | public SidWizPlusGui()
    class FileTypeHandler (line 216) | private class FileTypeHandler
      method FileTypeHandler (line 221) | public FileTypeHandler(string name, Action<string> handler, params s...
      method TryHandle (line 232) | public bool TryHandle(string filename)
    method AddAFileClick (line 255) | private void AddAFileClick(object sender, EventArgs e)
    method AddChannelButton_Click (line 304) | private void AddChannelButton_Click(object sender, EventArgs e)
    method CloneChannelButton_Click (line 309) | private void CloneChannelButton_Click(object sender, EventArgs e)
    method AddChannel (line 334) | private void AddChannel(string filename)
    method ChannelOnChanged (line 351) | private void ChannelOnChanged(Channel channel, bool filenameChanged)
    method LoadMultiDumper (line 376) | private void LoadMultiDumper(string filename)
    method LoadSid (line 402) | private void LoadSid(string filename)
    method LocateProgram (line 425) | private void LocateProgram(string filename, string currentValue, Actio...
    method AutoScale_Click (line 455) | private void AutoScale_Click(object sender, EventArgs e)
    method Render (line 473) | private void Render()
    method CreateWaveformRenderer (line 545) | private WaveformRenderer CreateWaveformRenderer()
    method LeftButton_Click (line 578) | private void LeftButton_Click(object sender, EventArgs e)
    method RightButton_Click (line 583) | private void RightButton_Click(object sender, EventArgs e)
    method MoveChannel (line 588) | private void MoveChannel(Channel channel, int delta)
    method RemoveButton_Click (line 611) | private void RemoveButton_Click(object sender, EventArgs e)
    method Preview_MouseClick (line 629) | private void Preview_MouseClick(object sender, MouseEventArgs e)
    method GetClickedChannel (line 638) | private Channel GetClickedChannel(int clickX, int clickY)
    method CopySettingsButton_Click (line 710) | private void CopySettingsButton_Click(object sender, EventArgs e)
    method ControlValueChanged (line 727) | private void ControlValueChanged(object sender, EventArgs e)
    method BackgroundImageControl_Click (line 744) | private void BackgroundImageControl_Click(object sender, EventArgs e)
    method RenderButton_Click (line 769) | private void RenderButton_Click(object sender, EventArgs e)
    class MainFormProgressOutput (line 869) | private class MainFormProgressOutput : IGraphicsOutput
      method MainFormProgressOutput (line 878) | public MainFormProgressOutput(SidWizPlusGui form)
      method Cancel (line 884) | public void Cancel()
      method Dispose (line 889) | public void Dispose()
      method Write (line 902) | public void Write(SKImage image, byte[] data, double fractionComplet...
    method AutogenerateMasterMix_CheckedChanged (line 934) | private void AutogenerateMasterMix_CheckedChanged(object sender, Event...
    method SetMasterAudioPath (line 939) | private void SetMasterAudioPath(object sender, EventArgs e)
    method LoadProgramSettings (line 952) | private void LoadProgramSettings(string settingsPath)
    method SaveProgramSettings (line 964) | private void SaveProgramSettings()
    method GetSettingsPath (line 980) | private static string GetSettingsPath()
    method Initialize (line 988) | private void Initialize(object sender, EventArgs e)
    method RemoveSilentChannels (line 1052) | private void RemoveSilentChannels(object sender, EventArgs e)
    method RemoveAllChannels (line 1071) | private void RemoveAllChannels(object sender, EventArgs e)
    method SaveProject (line 1087) | private void SaveProject(object sender, EventArgs e)
    method LoadProject (line 1105) | private void LoadProject(object sender, EventArgs e)
    method LoadSettingsFromFile (line 1117) | private void LoadSettingsFromFile(string path)
    method PrintFilenames (line 1279) | private static IEnumerable<string> PrintFilenames(List<Channel> channe...
    method AddDialogButton (line 1306) | private static void AddDialogButton(TaskDialog dialog, string text, st...
    method CopyChannelSettings (line 1322) | private void CopyChannelSettings(object sender, EventArgs e)
    method PasteChannelSettings (line 1332) | private void PasteChannelSettings(object sender, EventArgs e)
    method SplitChannel (line 1349) | private void SplitChannel(object sender, EventArgs e)
    method RemoveAllLabels (line 1394) | private void RemoveAllLabels(object sender, EventArgs e)
    method HandleClosing (line 1405) | private void HandleClosing(object sender, FormClosingEventArgs e)
    method SaveAsDefaultSettings (line 1411) | private void SaveAsDefaultSettings(object sender, EventArgs e)
    method ResetToDefaultSettings (line 1422) | private void ResetToDefaultSettings(object sender, EventArgs e)
    method PropertyGrid_SelectedObjectsChanged (line 1435) | private void PropertyGrid_SelectedObjectsChanged(object sender, EventA...
    method VideoCodec_DropDown (line 1441) | private void VideoCodec_DropDown(object sender, EventArgs e)
    type Codec (line 1476) | private record Codec(string Type, string Description, string Name)

FILE: SidWizPlus/BackgroundRenderer.cs
  class BackgroundRenderer (line 10) | class BackgroundRenderer: IDisposable
    method BackgroundRenderer (line 20) | public BackgroundRenderer(int width, int height, Color backgroundColor)
    method Dispose (line 37) | public void Dispose()
    method Add (line 44) | public void Add(ImageInfo imageInfo)
    method Add (line 83) | public void Add(TextInfo textInfo)
    method AlignedRect (line 108) | private Rectangle AlignedRect(ContentAlignment alignment, int width, i...
    method Constrain (line 125) | private void Constrain(Rectangle source, DockStyle dockStyle)

FILE: SidWizPlus/ImageInfo.cs
  class ImageInfo (line 6) | internal class ImageInfo
    method ImageInfo (line 14) | public ImageInfo(Image image, ContentAlignment alignment, bool stretch...

FILE: SidWizPlus/Program.cs
  class Program (line 31) | internal class Program
    class Settings (line 34) | private class Settings
    method Main (line 279) | static int Main(string[] args)
    method Run (line 305) | private static void Run(Settings settings)
    class InstrumentState (line 464) | private class InstrumentState
      method ToString (line 491) | public override string ToString() => $"{Name} ({TimeSpan.FromSeconds...
      method AddTime (line 493) | public void AddTime(int ticks)
    class ChannelState (line 499) | private class ChannelState
      method SetInstrument (line 506) | public void SetInstrument(int instrument)
      method AddTime (line 518) | public void AddTime(int ticks)
      method ToString (line 528) | public override string ToString() => string.Join(", ", Instruments.W...
    method TryGuessLabelsFromVgm (line 531) | private static void TryGuessLabelsFromVgm(List<Channel> channels, stri...
    method ParseColor (line 601) | private static Color ParseColor(string value)
    method RunMultiDumper (line 635) | private static void RunMultiDumper(Settings settings)
    method LogVerbose (line 686) | private static void LogVerbose(Settings settings, string message)
    method Render (line 695) | private static void Render(Settings settings, List<Channel> channels)
    method FindExecutable (line 836) | private static string FindExecutable(string name)
    method CreateTriggerAlgorithm (line 857) | private static ITriggerAlgorithm CreateTriggerAlgorithm(string name)
    method UploadToYouTube (line 869) | private static async Task<string> UploadToYouTube(Settings settings)
    method UploadVideo (line 1030) | private static async Task UploadVideo(string filename, YouTubeService ...
    method GetYouTubeService (line 1105) | private static async Task<YouTubeService> GetYouTubeService(Settings s...
    method UploadMergedToYouTube (line 1130) | private static async Task UploadMergedToYouTube(Settings settings)
    method MergeGd3Tags (line 1273) | private static Gd3Tag MergeGd3Tags(IList<Gd3Tag> tags)
    method MergeTags (line 1302) | private static string MergeTags(IEnumerable<Gd3Tag> tags, Func<Gd3Tag,...
    method GetGd3 (line 1313) | private static Gd3Tag GetGd3(string path)
    method GetVideoDuration (line 1325) | private static TimeSpan GetVideoDuration(string path, Settings settings)
    method RemoveAngledBrackets (line 1340) | private static string RemoveAngledBrackets(string s)
    method FormatFromGd3 (line 1345) | private static string FormatFromGd3(string pattern, Gd3Tag gd3)

FILE: SidWizPlus/TextInfo.cs
  class TextInfo (line 6) | internal class TextInfo
    method TextInfo (line 16) | public TextInfo(string text, string fontName, float fontSize, ContentA...
Condensed preview — 63 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (527K chars).
[
  {
    "path": ".editorconfig",
    "chars": 86,
    "preview": "[*.cs]\n\n# IDE0290: Use primary constructor\ndotnet_diagnostic.IDE0290.severity = none\n"
  },
  {
    "path": ".gitignore",
    "chars": 5113,
    "preview": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## G"
  },
  {
    "path": ".gitmodules",
    "chars": 106,
    "preview": "[submodule \"wiki\"]\n\tpath = wiki\n\turl = https://github.com/maxim-zhao/SidWizPlus.wiki.git\n\tbranch = master\n"
  },
  {
    "path": "Directory.Build.props",
    "chars": 163,
    "preview": "<Project>\n    <PropertyGroup>\n        <LangVersion>latest</LangVersion>\n        <TreatWarningsAsErrors>true</TreatWarnin"
  },
  {
    "path": "LICENSE",
    "chars": 1062,
    "preview": "MIT License\n\nCopyright (c) 2018 Maxim\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof t"
  },
  {
    "path": "LibSidWiz/Channel.cs",
    "chars": 29058,
    "preview": "using System;\r\nusing System.ComponentModel;\r\nusing System.ComponentModel.Design;\r\nusing System.Diagnostics.CodeAnalysis"
  },
  {
    "path": "LibSidWiz/Extensions.cs",
    "chars": 925,
    "preview": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Text.RegularExpressions;\r\n\r\nnamespac"
  },
  {
    "path": "LibSidWiz/LibSidWiz.csproj",
    "chars": 999,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\r\n  <PropertyGroup>\r\n    <TargetFramework>net48</TargetFramework>\r\n    <UseWPF>true</U"
  },
  {
    "path": "LibSidWiz/Mixer.cs",
    "chars": 3270,
    "preview": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing NAudio.Wave;\r\nusing NAudio.Wave.SampleProvi"
  },
  {
    "path": "LibSidWiz/MultiDumperWrapper.cs",
    "chars": 10302,
    "preview": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Diagnostics;\r\nusing System.IO;\r\nusing System.Linq;\r\nusin"
  },
  {
    "path": "LibSidWiz/MyColorConverter.cs",
    "chars": 347,
    "preview": "using System.ComponentModel;\nusing System.Drawing;\n\nnamespace LibSidWiz;\n\n/// <summary>\n/// This overrides ColorConvert"
  },
  {
    "path": "LibSidWiz/MyColorEditor.cs",
    "chars": 2032,
    "preview": "using System;\nusing System.ComponentModel;\nusing System.Drawing;\nusing System.Drawing.Design;\nusing System.Windows.Form"
  },
  {
    "path": "LibSidWiz/Outputs/FfmpegOutput.cs",
    "chars": 4223,
    "preview": "using System;\r\nusing System.Diagnostics;\r\nusing System.IO;\r\nusing System.Text;\r\nusing SkiaSharp;\r\n\r\nnamespace LibSidWiz"
  },
  {
    "path": "LibSidWiz/Outputs/IGraphicsOutput.cs",
    "chars": 227,
    "preview": "using System;\r\nusing SkiaSharp;\r\n\r\nnamespace LibSidWiz.Outputs\r\n{\r\n    public interface IGraphicsOutput: IDisposable\r\n "
  },
  {
    "path": "LibSidWiz/Outputs/PreviewOutput.cs",
    "chars": 3305,
    "preview": "using System;\r\nusing System.Diagnostics;\r\nusing System.Windows.Forms;\r\nusing Microsoft.WindowsAPICodePack.Taskbar;\r\nusi"
  },
  {
    "path": "LibSidWiz/Outputs/PreviewOutputForm.Designer.cs",
    "chars": 4441,
    "preview": "namespace LibSidWiz.Outputs\n{\n    partial class PreviewOutputForm\n    {\n        /// <summary>\n        /// Required desi"
  },
  {
    "path": "LibSidWiz/Outputs/PreviewOutputForm.cs",
    "chars": 238,
    "preview": "using System.Windows.Forms;\r\n\r\nnamespace LibSidWiz.Outputs\r\n{\r\n    public partial class PreviewOutputForm : Form\r\n    {"
  },
  {
    "path": "LibSidWiz/Outputs/PreviewOutputForm.resx",
    "chars": 47115,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<root>\r\n  <!-- \r\n    Microsoft ResX Schema \r\n    \r\n    Version 2.0\r\n    \r\n    T"
  },
  {
    "path": "LibSidWiz/ProcessWrapper.cs",
    "chars": 4252,
    "preview": "using System;\r\nusing System.Collections.Concurrent;\r\nusing System.Collections.Generic;\r\nusing System.Diagnostics;\r\nusin"
  },
  {
    "path": "LibSidWiz/SampleBuffer.cs",
    "chars": 4965,
    "preview": "using System;\r\nusing NAudio.Dsp;\r\nusing NAudio.Wave;\r\n\r\nnamespace LibSidWiz\r\n{\r\n    internal class SampleBuffer: IDispo"
  },
  {
    "path": "LibSidWiz/Triggers/AutoCorrelationTrigger.cs",
    "chars": 4686,
    "preview": "using System;\r\n#if DEBUG\r\nusing System.Diagnostics;\r\nusing System.Text;\r\n#endif\r\n\r\nnamespace LibSidWiz.Triggers\r\n{\r\n   "
  },
  {
    "path": "LibSidWiz/Triggers/BiggestPositiveWaveAreaTrigger.cs",
    "chars": 1599,
    "preview": "using System;\r\n\r\nnamespace LibSidWiz.Triggers\r\n{\r\n    /// <summary>\r\n    /// Finds the wave with the biggest positive a"
  },
  {
    "path": "LibSidWiz/Triggers/BiggestWaveAreaTrigger.cs",
    "chars": 1491,
    "preview": "using System;\r\n\r\nnamespace LibSidWiz.Triggers\r\n{\r\n    /// <summary>\r\n    /// Finds the positive+negative wave with the "
  },
  {
    "path": "LibSidWiz/Triggers/ITriggerAlgorithm.cs",
    "chars": 741,
    "preview": "namespace LibSidWiz.Triggers\r\n{\r\n    public interface ITriggerAlgorithm\r\n    {\r\n        /// <summary>\r\n        /// Find"
  },
  {
    "path": "LibSidWiz/Triggers/MiddleWidest.cs",
    "chars": 1806,
    "preview": "using System.Collections.Generic;\r\n\r\nnamespace LibSidWiz.Triggers\r\n{\r\n    /// <summary>\r\n    /// This corresponds to Si"
  },
  {
    "path": "LibSidWiz/Triggers/NullTrigger.cs",
    "chars": 411,
    "preview": "namespace LibSidWiz.Triggers\r\n{\r\n    /// <summary>\r\n    /// Null algorithm just returns the first sample it's given\r\n  "
  },
  {
    "path": "LibSidWiz/Triggers/PeakSpeedTrigger.cs",
    "chars": 2302,
    "preview": "namespace LibSidWiz.Triggers\r\n{\r\n    /// <summary>\r\n    /// Finds the positive edge which most quickly reaches the peak"
  },
  {
    "path": "LibSidWiz/Triggers/RisingEdgeTrigger.cs",
    "chars": 1063,
    "preview": "namespace LibSidWiz.Triggers\r\n{\r\n    /// <summary>\r\n    /// Trigger that finds the rising edge of the wave.\r\n    /// Th"
  },
  {
    "path": "LibSidWiz/Triggers/WidestWaveTrigger.cs",
    "chars": 1355,
    "preview": "namespace LibSidWiz.Triggers\r\n{\r\n    /// <summary>\r\n    /// Finds the widest positive+negative wave in the range\r\n    /"
  },
  {
    "path": "LibSidWiz/WaveformRenderer.cs",
    "chars": 25628,
    "preview": "using System;\r\nusing System.Collections.Concurrent;\r\nusing System.Collections.Generic;\r\nusing System.Drawing;\r\nusing Sy"
  },
  {
    "path": "LibVgm/Gd3Tag.cs",
    "chars": 5125,
    "preview": "using System;\r\nusing System.Collections.Generic;\r\nusing System.IO;\r\nusing System.Text;\r\n\r\nnamespace LibVgm\r\n{\r\n    publi"
  },
  {
    "path": "LibVgm/LibVgm.csproj",
    "chars": 235,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\r\n  <PropertyGroup>\r\n    <TargetFramework>netstandard2.0</TargetFramework>\r\n    <LangVe"
  },
  {
    "path": "LibVgm/OptionalGzipStream.cs",
    "chars": 2011,
    "preview": "using System.IO;\r\nusing System.IO.Compression;\r\n\r\nnamespace LibVgm\r\n{\r\n    /// <summary>\r\n    /// Stream class which tr"
  },
  {
    "path": "LibVgm/VgmFile.cs",
    "chars": 23057,
    "preview": "using System;\r\nusing System.Collections.Generic;\r\nusing System.IO;\r\nusing System.Text;\r\n\r\nnamespace LibVgm\r\n{\r\n    publ"
  },
  {
    "path": "README.md",
    "chars": 2623,
    "preview": "# SidWizPlus\n\n![](https://github.com/maxim-zhao/SidWizPlus/wiki/logo.png)\n\nThis is a program which generates \"oscillosco"
  },
  {
    "path": "SidWiz/ColorButton.Designer.cs",
    "chars": 1063,
    "preview": "namespace SidWizPlusGUI\r\n{\r\n    partial class ColorButton\r\n    {\r\n        /// <summary>\r\n        /// Required designer "
  },
  {
    "path": "SidWiz/ColorButton.cs",
    "chars": 1448,
    "preview": "using System;\r\nusing System.ComponentModel;\r\nusing System.Drawing;\r\nusing System.Windows.Forms;\r\n\r\nnamespace SidWizPlus"
  },
  {
    "path": "SidWiz/HighDpiHelper.cs",
    "chars": 4072,
    "preview": "using System;\r\nusing System.Collections;\r\nusing System.Drawing;\r\nusing System.Drawing.Drawing2D;\r\nusing System.Linq;\r\nu"
  },
  {
    "path": "SidWiz/MultiDumperForm.Designer.cs",
    "chars": 6147,
    "preview": "using System.ComponentModel;\r\nusing System.Windows.Forms;\r\n\r\nnamespace SidWizPlusGUI\r\n{\r\n    partial class MultiDumperF"
  },
  {
    "path": "SidWiz/MultiDumperForm.cs",
    "chars": 4282,
    "preview": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Threading.Tasks;\r\nusing System.Windo"
  },
  {
    "path": "SidWiz/MultiDumperForm.resx",
    "chars": 5815,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<root>\r\n  <!-- \r\n    Microsoft ResX Schema \r\n    \r\n    Version 2.0\r\n    \r\n    T"
  },
  {
    "path": "SidWiz/Program.cs",
    "chars": 454,
    "preview": "using System;\nusing System.Windows.Forms;\n\nnamespace SidWizPlusGUI\n{\n    internal static class Program\n    {\n        //"
  },
  {
    "path": "SidWiz/Properties/Resources.Designer.cs",
    "chars": 4369,
    "preview": "//------------------------------------------------------------------------------\r\n// <auto-generated>\r\n//     This code"
  },
  {
    "path": "SidWiz/Properties/Resources.resx",
    "chars": 6811,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The prim"
  },
  {
    "path": "SidWiz/Properties/app.manifest",
    "chars": 3091,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<assembly manifestVersion=\"1.0\" xmlns=\"urn:schemas-microsoft-com:asm.v1\">\r\n  <a"
  },
  {
    "path": "SidWiz/SidPlayForm.Designer.cs",
    "chars": 4908,
    "preview": "using System.Windows.Forms;\r\n\r\nnamespace SidWizPlusGUI\r\n{\r\n    partial class SidPlayForm\r\n    {\r\n        /// <summary>\r"
  },
  {
    "path": "SidWiz/SidPlayForm.cs",
    "chars": 7626,
    "preview": "using System;\r\nusing System.Collections.Generic;\r\nusing System.IO;\r\nusing System.Linq;\r\nusing System.Text;\r\nusing Syste"
  },
  {
    "path": "SidWiz/SidPlayForm.resx",
    "chars": 5815,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<root>\r\n  <!-- \r\n    Microsoft ResX Schema \r\n    \r\n    Version 2.0\r\n    \r\n    T"
  },
  {
    "path": "SidWiz/SidWizPlusGUI.Designer.cs",
    "chars": 76654,
    "preview": "using System.Windows.Forms;\r\n\r\nnamespace SidWizPlusGUI\r\n{\r\n    partial class SidWizPlusGui\r\n    {\r\n        /// <summary"
  },
  {
    "path": "SidWiz/SidWizPlusGUI.cs",
    "chars": 58175,
    "preview": "using System;\r\nusing System.Collections.Generic;\r\nusing System.ComponentModel;\r\nusing System.Diagnostics;\r\nusing System"
  },
  {
    "path": "SidWiz/SidWizPlusGUI.csproj",
    "chars": 853,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\r\n  <PropertyGroup>\r\n    <TargetFramework>net48</TargetFramework>\r\n    <OutputType>Win"
  },
  {
    "path": "SidWiz/SidWizPlusGUI.resx",
    "chars": 32751,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<root>\r\n  <!-- \r\n    Microsoft ResX Schema \r\n    \r\n    Version 2.0\r\n    \r\n    T"
  },
  {
    "path": "SidWiz/app.config",
    "chars": 443,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<configuration>\r\n  <runtime>\r\n    <gcAllowVeryLargeObjects enabled=\"true\"/>\r\n   "
  },
  {
    "path": "SidWiz.sln",
    "chars": 2860,
    "preview": "Microsoft Visual Studio Solution File, Format Version 12.00\r\n# Visual Studio Version 17\r\nVisualStudioVersion = 17.10.350"
  },
  {
    "path": "SidWiz.sln.DotSettings",
    "chars": 1453,
    "preview": "<wpf:ResourceDictionary xml:space=\"preserve\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" xmlns:s=\"clr-namesp"
  },
  {
    "path": "SidWizPlus/App.config",
    "chars": 184,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<configuration>\r\n    <startup> \r\n        <supportedRuntime version=\"v4.0\" sku=\"."
  },
  {
    "path": "SidWizPlus/BackgroundRenderer.cs",
    "chars": 6334,
    "preview": "using System;\r\nusing System.Drawing;\r\nusing System.Drawing.Drawing2D;\r\nusing System.Drawing.Imaging;\r\nusing System.Draw"
  },
  {
    "path": "SidWizPlus/ImageInfo.cs",
    "chars": 673,
    "preview": "using System.Drawing;\r\nusing System.Windows.Forms;\r\n\r\nnamespace SidWizPlus\r\n{\r\n    internal class ImageInfo\r\n    {\r\n   "
  },
  {
    "path": "SidWizPlus/Program.cs",
    "chars": 63140,
    "preview": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Diagnostics;\r\nusing System.Drawing;\r\nusing System.IO;\r\nu"
  },
  {
    "path": "SidWizPlus/SidWizPlus.csproj",
    "chars": 645,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\r\n  <PropertyGroup>\r\n    <TargetFramework>net48</TargetFramework>\r\n    <OutputType>Exe"
  },
  {
    "path": "SidWizPlus/TextInfo.cs",
    "chars": 849,
    "preview": "using System.Drawing;\r\nusing System.Windows.Forms;\r\n\r\nnamespace SidWizPlus\r\n{\r\n    internal class TextInfo\r\n    {\r\n    "
  },
  {
    "path": "appveyor.yml",
    "chars": 1361,
    "preview": "version: '{build}'\nskip_tags: true\nimage: Visual Studio 2022\nconfiguration: Release\nenvironment:\n  secret:\n    secure: m"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the maxim-zhao/SidWizPlus GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 63 files (487.0 KB), approximately 122.9k tokens, and a symbol index with 305 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!